// 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 || arm64 || ppc64le) && linux package runtime import ( "internal/abi" "internal/stringslite" "unsafe" ) // InjectDebugCall injects a debugger call to fn into g. regArgs must // contain any arguments to fn that are passed in registers, according // to the internal Go ABI. It may be nil if no arguments are passed in // registers to fn. args must be a pointer to a valid call frame (including // arguments and return space) for fn, or nil. tkill must be a function that // will send SIGTRAP to thread ID tid. gp must be locked to its OS thread and // running. // // On success, InjectDebugCall returns the panic value of fn or nil. // If fn did not panic, its results will be available in args. func InjectDebugCall(gp *g, fn any, regArgs *abi.RegArgs, stackArgs any, tkill func(tid int) error, returnOnUnsafePoint bool) (any, error) { if gp.lockedm == 0 { return nil, plainError("goroutine not locked to thread") } tid := int(gp.lockedm.ptr().procid) if tid == 0 { return nil, plainError("missing tid") } f := efaceOf(&fn) if f._type == nil || f._type.Kind_&abi.KindMask != abi.Func { return nil, plainError("fn must be a function") } fv := (*funcval)(f.data) a := efaceOf(&stackArgs) if a._type != nil && a._type.Kind_&abi.KindMask != abi.Pointer { return nil, plainError("args must be a pointer or nil") } argp := a.data var argSize uintptr if argp != nil { argSize = (*ptrtype)(unsafe.Pointer(a._type)).Elem.Size_ } h := new(debugCallHandler) h.gp = gp // gp may not be running right now, but we can still get the M // it will run on since it's locked. h.mp = gp.lockedm.ptr() h.fv, h.regArgs, h.argp, h.argSize = fv, regArgs, argp, argSize h.handleF = h.handle // Avoid allocating closure during signal defer func() { testSigtrap = nil }() for i := 0; ; i++ { testSigtrap = h.inject noteclear(&h.done) h.err = "" if err := tkill(tid); err != nil { return nil, err } // Wait for completion. notetsleepg(&h.done, -1) if h.err != "" { switch h.err { case "call not at safe point": if returnOnUnsafePoint { // This is for TestDebugCallUnsafePoint. return nil, h.err } fallthrough case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime": // These are transient states. Try to get out of them. if i < 100 { usleep(100) Gosched() continue } } return nil, h.err } return h.panic, nil } } type debugCallHandler struct { gp *g mp *m fv *funcval regArgs *abi.RegArgs argp unsafe.Pointer argSize uintptr panic any handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool err plainError done note sigCtxt sigContext } func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { // TODO(49370): This code is riddled with write barriers, but called from // a signal handler. Add the go:nowritebarrierrec annotation and restructure // this to avoid write barriers. switch h.gp.atomicstatus.Load() { case _Grunning: if getg().m != h.mp { println("trap on wrong M", getg().m, h.mp) return false } // Save the signal context h.saveSigContext(ctxt) // Set PC to debugCallV2. ctxt.setsigpc(uint64(abi.FuncPCABIInternal(debugCallV2))) // Call injected. Switch to the debugCall protocol. testSigtrap = h.handleF case _Grunnable: // Ask InjectDebugCall to pause for a bit and then try // again to interrupt this goroutine. h.err = plainError("retry _Grunnable") notewakeup(&h.done) default: h.err = plainError("goroutine in unexpected state at call inject") notewakeup(&h.done) } // Resume execution. return true } func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { // TODO(49370): This code is riddled with write barriers, but called from // a signal handler. Add the go:nowritebarrierrec annotation and restructure // this to avoid write barriers. // Double-check m. if getg().m != h.mp { println("trap on wrong M", getg().m, h.mp) return false } f := findfunc(ctxt.sigpc()) if !(stringslite.HasPrefix(funcname(f), "runtime.debugCall") || stringslite.HasPrefix(funcname(f), "debugCall")) { println("trap in unknown function", funcname(f)) return false } if !sigctxtAtTrapInstruction(ctxt) { println("trap at non-INT3 instruction pc =", hex(ctxt.sigpc())) return false } switch status := sigctxtStatus(ctxt); status { case 0: // Frame is ready. Copy the arguments to the frame and to registers. // Call the debug function. h.debugCallRun(ctxt) case 1: // Function returned. Copy frame and result registers back out. h.debugCallReturn(ctxt) case 2: // Function panicked. Copy panic out. h.debugCallPanicOut(ctxt) case 8: // Call isn't safe. Get the reason. h.debugCallUnsafe(ctxt) // Don't wake h.done. We need to transition to status 16 first. case 16: h.restoreSigContext(ctxt) // Done notewakeup(&h.done) default: h.err = plainError("unexpected debugCallV2 status") notewakeup(&h.done) } // Resume execution. return true }