// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build amd64
+// Though the debug call function feature is not enabled on
+// ppc64, inserted ppc64 to avoid missing Go declaration error
+// for debugCallPanicked while building runtime.test
+//go:build amd64 || arm64 || ppc64le || ppc64
package runtime
-import "unsafe"
+import (
+ "internal/abi"
+ "unsafe"
+)
const (
debugCallSystemStack = "executing on Go runtime stack"
debugCallUnsafePoint = "call not at safe point"
)
-func debugCallV1()
-func debugCallPanicked(val interface{})
+func debugCallV2()
+func debugCallPanicked(val any)
// debugCallCheck checks whether it is safe to inject a debugger
// function call with return PC pc. If not, it returns a string
"debugCall16384",
"debugCall32768",
"debugCall65536":
- // These functions are whitelisted so that the debugger can initiate multiple function calls.
+ // These functions are allowed so that the debugger can initiate multiple function calls.
// See: https://golang.org/cl/161137/
return
}
return
}
- // Look up PC's register map.
- pcdata := int32(-1)
- if pc != f.entry {
+ // Check that this isn't an unsafe-point.
+ if pc != f.entry() {
pc--
- pcdata = pcdatavalue(f, _PCDATA_RegMapIndex, pc, nil)
- }
- if pcdata == -1 {
- pcdata = 0 // in prologue
}
- stkmap := (*stackmap)(funcdata(f, _FUNCDATA_RegPointerMaps))
- if pcdata == -2 || stkmap == nil {
+ up := pcdatavalue(f, abi.PCDATA_UnsafePoint, pc)
+ if up != abi.UnsafePointSafe {
// Not at a safe point.
ret = debugCallUnsafePoint
- return
}
})
return ret
}
-// debugCallWrap pushes a defer to recover from panics in debug calls
-// and then calls the dispatching function at PC dispatch.
+// 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 debugCallV2.
+//
+//go:nosplit
func debugCallWrap(dispatch uintptr) {
+ var lockedExt uint32
+ callerpc := getcallerpc()
+ gp := getg()
+
+ // Lock ourselves to the OS thread.
+ //
+ // Debuggers rely on us running on the same thread until we get to
+ // dispatch the function they asked as to.
+ //
+ // We're going to transfer this to the new G we just created.
+ lockOSThread()
+
+ // Create a new goroutine to execute the call on. Run this on
+ // the system stack to avoid growing our stack.
+ systemstack(func() {
+ // TODO(mknyszek): It would be nice to wrap these arguments in an allocated
+ // closure and start the goroutine with that closure, but the compiler disallows
+ // implicit closure allocation in the runtime.
+ fn := debugCallWrap1
+ newg := newproc1(*(**funcval)(unsafe.Pointer(&fn)), gp, callerpc)
+ args := &debugCallWrapArgs{
+ dispatch: dispatch,
+ callingG: gp,
+ }
+ newg.param = unsafe.Pointer(args)
+
+ // Transfer locked-ness to the new goroutine.
+ // Save lock state to restore later.
+ mp := gp.m
+ if mp != gp.lockedm.ptr() {
+ throw("inconsistent lockedm")
+ }
+ // Save the external lock count and clear it so
+ // that it can't be unlocked from the debug call.
+ // Note: we already locked internally to the thread,
+ // so if we were locked before we're still locked now.
+ lockedExt = mp.lockedExt
+ 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.
+ trace := traceAcquire()
+ casGToWaiting(gp, _Grunning, waitReasonDebugCall)
+ if trace.ok() {
+ trace.GoPark(traceBlockDebugCall, 1)
+ traceRelease(trace)
+ }
+ 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.
+ mp := gp.m
+ mp.lockedExt = lockedExt
+ mp.lockedg.set(gp)
+ gp.lockedm.set(mp)
+
+ // Undo the lockOSThread we did earlier.
+ unlockOSThread()
+
+ gp.asyncSafePoint = false
+}
+
+type debugCallWrapArgs struct {
+ dispatch uintptr
+ callingG *g
+}
+
+// debugCallWrap1 is the continuation of debugCallWrap on the callee
+// goroutine.
+func debugCallWrap1() {
+ gp := getg()
+ args := (*debugCallWrapArgs)(gp.param)
+ dispatch, callingG := args.dispatch, args.callingG
+ gp.param = nil
+
+ // 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.
+ trace := traceAcquire()
+ casgstatus(gp, _Grunning, _Grunnable)
+ if trace.ok() {
+ trace.GoSched()
+ traceRelease(trace)
+ }
+ dropg()
+ lock(&sched.lock)
+ globrunqput(gp)
+ unlock(&sched.lock)
+
+ trace = traceAcquire()
+ casgstatus(callingG, _Gwaiting, _Grunnable)
+ if trace.ok() {
+ trace.GoUnpark(callingG, 0)
+ traceRelease(trace)
+ }
+ 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))