| // Copyright 2018 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build amd64 | |
| // +build amd64 | |
| package runtime | |
| import "unsafe" | |
| const ( | |
| debugCallSystemStack = "executing on Go runtime stack" | |
| debugCallUnknownFunc = "call from unknown function" | |
| debugCallRuntime = "call from within the Go runtime" | |
| debugCallUnsafePoint = "call not at safe point" | |
| ) | |
| func debugCallV1() | |
| func debugCallPanicked(val interface{}) | |
| // debugCallCheck checks whether it is safe to inject a debugger | |
| // function call with return PC pc. If not, it returns a string | |
| // explaining why. | |
| // | |
| //go:nosplit | |
| func debugCallCheck(pc uintptr) string { | |
| // No user calls from the system stack. | |
| if getg() != getg().m.curg { | |
| return debugCallSystemStack | |
| } | |
| if sp := getcallersp(); !(getg().stack.lo < sp && sp <= getg().stack.hi) { | |
| // Fast syscalls (nanotime) and racecall switch to the | |
| // g0 stack without switching g. We can't safely make | |
| // a call in this state. (We can't even safely | |
| // systemstack.) | |
| return debugCallSystemStack | |
| } | |
| // Switch to the system stack to avoid overflowing the user | |
| // stack. | |
| var ret string | |
| systemstack(func() { | |
| f := findfunc(pc) | |
| if !f.valid() { | |
| ret = debugCallUnknownFunc | |
| return | |
| } | |
| name := funcname(f) | |
| switch name { | |
| case "debugCall32", | |
| "debugCall64", | |
| "debugCall128", | |
| "debugCall256", | |
| "debugCall512", | |
| "debugCall1024", | |
| "debugCall2048", | |
| "debugCall4096", | |
| "debugCall8192", | |
| "debugCall16384", | |
| "debugCall32768", | |
| "debugCall65536": | |
| // These functions are allowed so that the debugger can initiate multiple function calls. | |
| // See: https://golang.org/cl/161137/ | |
| return | |
| } | |
| // Disallow calls from the runtime. We could | |
| // potentially make this condition tighter (e.g., not | |
| // when locks are held), but there are enough tightly | |
| // coded sequences (e.g., defer handling) that it's | |
| // better to play it safe. | |
| if pfx := "runtime."; len(name) > len(pfx) && name[:len(pfx)] == pfx { | |
| ret = debugCallRuntime | |
| return | |
| } | |
| // Check that this isn't an unsafe-point. | |
| if pc != f.entry { | |
| pc-- | |
| } | |
| up := pcdatavalue(f, _PCDATA_UnsafePoint, pc, nil) | |
| if up != _PCDATA_UnsafePointSafe { | |
| // Not at a safe point. | |
| ret = debugCallUnsafePoint | |
| } | |
| }) | |
| return ret | |
| } | |
| // debugCallWrap starts a new goroutine to run a debug call and blocks | |
| // the calling goroutine. On the goroutine, it prepares to recover | |
| // panics from the debug call, and then calls the call dispatching | |
| // function at PC dispatch. | |
| // | |
| // This must be deeply nosplit because there are untyped values on the | |
| // stack from debugCallV1. | |
| // | |
| //go:nosplit | |
| func debugCallWrap(dispatch uintptr) { | |
| var lockedm bool | |
| var lockedExt uint32 | |
| callerpc := getcallerpc() | |
| gp := getg() | |
| // Create a new goroutine to execute the call on. Run this on | |
| // the system stack to avoid growing our stack. | |
| systemstack(func() { | |
| var args struct { | |
| dispatch uintptr | |
| callingG *g | |
| } | |
| args.dispatch = dispatch | |
| args.callingG = gp | |
| fn := debugCallWrap1 | |
| newg := newproc1(*(**funcval)(unsafe.Pointer(&fn)), unsafe.Pointer(&args), int32(unsafe.Sizeof(args)), gp, callerpc) | |
| // If the current G is locked, then transfer that | |
| // locked-ness to the new goroutine. | |
| if gp.lockedm != 0 { | |
| // Save lock state to restore later. | |
| mp := gp.m | |
| if mp != gp.lockedm.ptr() { | |
| throw("inconsistent lockedm") | |
| } | |
| lockedm = true | |
| lockedExt = mp.lockedExt | |
| // Transfer external lock count to internal so | |
| // it can't be unlocked from the debug call. | |
| mp.lockedInt++ | |
| mp.lockedExt = 0 | |
| mp.lockedg.set(newg) | |
| newg.lockedm.set(mp) | |
| gp.lockedm = 0 | |
| } | |
| // Mark the calling goroutine as being at an async | |
| // safe-point, since it has a few conservative frames | |
| // at the bottom of the stack. This also prevents | |
| // stack shrinks. | |
| gp.asyncSafePoint = true | |
| // Stash newg away so we can execute it below (mcall's | |
| // closure can't capture anything). | |
| gp.schedlink.set(newg) | |
| }) | |
| // Switch to the new goroutine. | |
| mcall(func(gp *g) { | |
| // Get newg. | |
| newg := gp.schedlink.ptr() | |
| gp.schedlink = 0 | |
| // Park the calling goroutine. | |
| gp.waitreason = waitReasonDebugCall | |
| if trace.enabled { | |
| traceGoPark(traceEvGoBlock, 1) | |
| } | |
| casgstatus(gp, _Grunning, _Gwaiting) | |
| dropg() | |
| // Directly execute the new goroutine. The debug | |
| // protocol will continue on the new goroutine, so | |
| // it's important we not just let the scheduler do | |
| // this or it may resume a different goroutine. | |
| execute(newg, true) | |
| }) | |
| // We'll resume here when the call returns. | |
| // Restore locked state. | |
| if lockedm { | |
| mp := gp.m | |
| mp.lockedExt = lockedExt | |
| mp.lockedInt-- | |
| mp.lockedg.set(gp) | |
| gp.lockedm.set(mp) | |
| } | |
| gp.asyncSafePoint = false | |
| } | |
| // debugCallWrap1 is the continuation of debugCallWrap on the callee | |
| // goroutine. | |
| func debugCallWrap1(dispatch uintptr, callingG *g) { | |
| // Dispatch call and trap panics. | |
| debugCallWrap2(dispatch) | |
| // Resume the caller goroutine. | |
| getg().schedlink.set(callingG) | |
| mcall(func(gp *g) { | |
| callingG := gp.schedlink.ptr() | |
| gp.schedlink = 0 | |
| // Unlock this goroutine from the M if necessary. The | |
| // calling G will relock. | |
| if gp.lockedm != 0 { | |
| gp.lockedm = 0 | |
| gp.m.lockedg = 0 | |
| } | |
| // Switch back to the calling goroutine. At some point | |
| // the scheduler will schedule us again and we'll | |
| // finish exiting. | |
| if trace.enabled { | |
| traceGoSched() | |
| } | |
| casgstatus(gp, _Grunning, _Grunnable) | |
| dropg() | |
| lock(&sched.lock) | |
| globrunqput(gp) | |
| unlock(&sched.lock) | |
| if trace.enabled { | |
| traceGoUnpark(callingG, 0) | |
| } | |
| casgstatus(callingG, _Gwaiting, _Grunnable) | |
| execute(callingG, true) | |
| }) | |
| } | |
| func debugCallWrap2(dispatch uintptr) { | |
| // Call the dispatch function and trap panics. | |
| var dispatchF func() | |
| dispatchFV := funcval{dispatch} | |
| *(*unsafe.Pointer)(unsafe.Pointer(&dispatchF)) = noescape(unsafe.Pointer(&dispatchFV)) | |
| var ok bool | |
| defer func() { | |
| if !ok { | |
| err := recover() | |
| debugCallPanicked(err) | |
| } | |
| }() | |
| dispatchF() | |
| ok = true | |
| } |