github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/export_debug_test.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build (amd64 || arm64) && linux 6 7 package runtime 8 9 import ( 10 "internal/abi" 11 "unsafe" 12 ) 13 14 // InjectDebugCall injects a debugger call to fn into g. regArgs must 15 // contain any arguments to fn that are passed in registers, according 16 // to the internal Go ABI. It may be nil if no arguments are passed in 17 // registers to fn. args must be a pointer to a valid call frame (including 18 // arguments and return space) for fn, or nil. tkill must be a function that 19 // will send SIGTRAP to thread ID tid. gp must be locked to its OS thread and 20 // running. 21 // 22 // On success, InjectDebugCall returns the panic value of fn or nil. 23 // If fn did not panic, its results will be available in args. 24 func InjectDebugCall(gp *g, fn any, regArgs *abi.RegArgs, stackArgs any, tkill func(tid int) error, returnOnUnsafePoint bool) (any, error) { 25 if gp.lockedm == 0 { 26 return nil, plainError("goroutine not locked to thread") 27 } 28 29 tid := int(gp.lockedm.ptr().procid) 30 if tid == 0 { 31 return nil, plainError("missing tid") 32 } 33 34 f := efaceOf(&fn) 35 if f._type == nil || f._type.Kind_&kindMask != kindFunc { 36 return nil, plainError("fn must be a function") 37 } 38 fv := (*funcval)(f.data) 39 40 a := efaceOf(&stackArgs) 41 if a._type != nil && a._type.Kind_&kindMask != kindPtr { 42 return nil, plainError("args must be a pointer or nil") 43 } 44 argp := a.data 45 var argSize uintptr 46 if argp != nil { 47 argSize = (*ptrtype)(unsafe.Pointer(a._type)).Elem.Size_ 48 } 49 50 h := new(debugCallHandler) 51 h.gp = gp 52 // gp may not be running right now, but we can still get the M 53 // it will run on since it's locked. 54 h.mp = gp.lockedm.ptr() 55 h.fv, h.regArgs, h.argp, h.argSize = fv, regArgs, argp, argSize 56 h.handleF = h.handle // Avoid allocating closure during signal 57 58 defer func() { testSigtrap = nil }() 59 for i := 0; ; i++ { 60 testSigtrap = h.inject 61 noteclear(&h.done) 62 h.err = "" 63 64 if err := tkill(tid); err != nil { 65 return nil, err 66 } 67 // Wait for completion. 68 notetsleepg(&h.done, -1) 69 if h.err != "" { 70 switch h.err { 71 case "call not at safe point": 72 if returnOnUnsafePoint { 73 // This is for TestDebugCallUnsafePoint. 74 return nil, h.err 75 } 76 fallthrough 77 case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime": 78 // These are transient states. Try to get out of them. 79 if i < 100 { 80 usleep(100) 81 Gosched() 82 continue 83 } 84 } 85 return nil, h.err 86 } 87 return h.panic, nil 88 } 89 } 90 91 type debugCallHandler struct { 92 gp *g 93 mp *m 94 fv *funcval 95 regArgs *abi.RegArgs 96 argp unsafe.Pointer 97 argSize uintptr 98 panic any 99 100 handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool 101 102 err plainError 103 done note 104 sigCtxt sigContext 105 } 106 107 func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 108 // TODO(49370): This code is riddled with write barriers, but called from 109 // a signal handler. Add the go:nowritebarrierrec annotation and restructure 110 // this to avoid write barriers. 111 112 switch h.gp.atomicstatus.Load() { 113 case _Grunning: 114 if getg().m != h.mp { 115 println("trap on wrong M", getg().m, h.mp) 116 return false 117 } 118 // Save the signal context 119 h.saveSigContext(ctxt) 120 // Set PC to debugCallV2. 121 ctxt.setsigpc(uint64(abi.FuncPCABIInternal(debugCallV2))) 122 // Call injected. Switch to the debugCall protocol. 123 testSigtrap = h.handleF 124 case _Grunnable: 125 // Ask InjectDebugCall to pause for a bit and then try 126 // again to interrupt this goroutine. 127 h.err = plainError("retry _Grunnable") 128 notewakeup(&h.done) 129 default: 130 h.err = plainError("goroutine in unexpected state at call inject") 131 notewakeup(&h.done) 132 } 133 // Resume execution. 134 return true 135 } 136 137 func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 138 // TODO(49370): This code is riddled with write barriers, but called from 139 // a signal handler. Add the go:nowritebarrierrec annotation and restructure 140 // this to avoid write barriers. 141 142 // Double-check m. 143 if getg().m != h.mp { 144 println("trap on wrong M", getg().m, h.mp) 145 return false 146 } 147 f := findfunc(ctxt.sigpc()) 148 if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) { 149 println("trap in unknown function", funcname(f)) 150 return false 151 } 152 if !sigctxtAtTrapInstruction(ctxt) { 153 println("trap at non-INT3 instruction pc =", hex(ctxt.sigpc())) 154 return false 155 } 156 157 switch status := sigctxtStatus(ctxt); status { 158 case 0: 159 // Frame is ready. Copy the arguments to the frame and to registers. 160 // Call the debug function. 161 h.debugCallRun(ctxt) 162 case 1: 163 // Function returned. Copy frame and result registers back out. 164 h.debugCallReturn(ctxt) 165 case 2: 166 // Function panicked. Copy panic out. 167 h.debugCallPanicOut(ctxt) 168 case 8: 169 // Call isn't safe. Get the reason. 170 h.debugCallUnsafe(ctxt) 171 // Don't wake h.done. We need to transition to status 16 first. 172 case 16: 173 h.restoreSigContext(ctxt) 174 // Done 175 notewakeup(&h.done) 176 default: 177 h.err = plainError("unexpected debugCallV2 status") 178 notewakeup(&h.done) 179 } 180 // Resume execution. 181 return true 182 }