]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/runtime/debugcall.go
runtime: refactor runtime->tracer API to appear more like a lock
[gostls13.git] / src / runtime / debugcall.go
index f03d2358ebb2682a1a0c5c25e274ab698dd6be82..5dd83063ff9cb8bb88f6e34d42ab162c9f106812 100644 (file)
@@ -2,11 +2,17 @@
 // 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"
@@ -15,8 +21,8 @@ const (
        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
@@ -61,7 +67,7 @@ func debugCallCheck(pc uintptr) 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
                }
@@ -76,28 +82,174 @@ func debugCallCheck(pc uintptr) string {
                        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))