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 && linux 6 7package runtime 8 9import ( 10 "internal/abi" 11 "internal/goarch" 12 "unsafe" 13) 14 15// InjectDebugCall injects a debugger call to fn into g. regArgs must 16// contain any arguments to fn that are passed in registers, according 17// to the internal Go ABI. It may be nil if no arguments are passed in 18// registers to fn. args must be a pointer to a valid call frame (including 19// arguments and return space) for fn, or nil. tkill must be a function that 20// will send SIGTRAP to thread ID tid. gp must be locked to its OS thread and 21// running. 22// 23// On success, InjectDebugCall returns the panic value of fn or nil. 24// If fn did not panic, its results will be available in args. 25func InjectDebugCall(gp *g, fn any, regArgs *abi.RegArgs, stackArgs any, tkill func(tid int) error, returnOnUnsafePoint bool) (any, error) { 26 if gp.lockedm == 0 { 27 return nil, plainError("goroutine not locked to thread") 28 } 29 30 tid := int(gp.lockedm.ptr().procid) 31 if tid == 0 { 32 return nil, plainError("missing tid") 33 } 34 35 f := efaceOf(&fn) 36 if f._type == nil || f._type.kind&kindMask != kindFunc { 37 return nil, plainError("fn must be a function") 38 } 39 fv := (*funcval)(f.data) 40 41 a := efaceOf(&stackArgs) 42 if a._type != nil && a._type.kind&kindMask != kindPtr { 43 return nil, plainError("args must be a pointer or nil") 44 } 45 argp := a.data 46 var argSize uintptr 47 if argp != nil { 48 argSize = (*ptrtype)(unsafe.Pointer(a._type)).elem.size 49 } 50 51 h := new(debugCallHandler) 52 h.gp = gp 53 // gp may not be running right now, but we can still get the M 54 // it will run on since it's locked. 55 h.mp = gp.lockedm.ptr() 56 h.fv, h.regArgs, h.argp, h.argSize = fv, regArgs, argp, argSize 57 h.handleF = h.handle // Avoid allocating closure during signal 58 59 defer func() { testSigtrap = nil }() 60 for i := 0; ; i++ { 61 testSigtrap = h.inject 62 noteclear(&h.done) 63 h.err = "" 64 65 if err := tkill(tid); err != nil { 66 return nil, err 67 } 68 // Wait for completion. 69 notetsleepg(&h.done, -1) 70 if h.err != "" { 71 switch h.err { 72 case "call not at safe point": 73 if returnOnUnsafePoint { 74 // This is for TestDebugCallUnsafePoint. 75 return nil, h.err 76 } 77 fallthrough 78 case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime": 79 // These are transient states. Try to get out of them. 80 if i < 100 { 81 usleep(100) 82 Gosched() 83 continue 84 } 85 } 86 return nil, h.err 87 } 88 return h.panic, nil 89 } 90} 91 92type debugCallHandler struct { 93 gp *g 94 mp *m 95 fv *funcval 96 regArgs *abi.RegArgs 97 argp unsafe.Pointer 98 argSize uintptr 99 panic any 100 101 handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool 102 103 err plainError 104 done note 105 savedRegs sigcontext 106 savedFP fpstate1 107} 108 109func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 110 // TODO(49370): This code is riddled with write barriers, but called from 111 // a signal handler. Add the go:nowritebarrierrec annotation and restructure 112 // this to avoid write barriers. 113 114 switch h.gp.atomicstatus { 115 case _Grunning: 116 if getg().m != h.mp { 117 println("trap on wrong M", getg().m, h.mp) 118 return false 119 } 120 // Push current PC on the stack. 121 rsp := ctxt.rsp() - goarch.PtrSize 122 *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip() 123 ctxt.set_rsp(rsp) 124 // Write the argument frame size. 125 *(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize 126 // Save current registers. 127 h.savedRegs = *ctxt.regs() 128 h.savedFP = *h.savedRegs.fpstate 129 h.savedRegs.fpstate = nil 130 // Set PC to debugCallV2. 131 ctxt.set_rip(uint64(abi.FuncPCABIInternal(debugCallV2))) 132 // Call injected. Switch to the debugCall protocol. 133 testSigtrap = h.handleF 134 case _Grunnable: 135 // Ask InjectDebugCall to pause for a bit and then try 136 // again to interrupt this goroutine. 137 h.err = plainError("retry _Grunnable") 138 notewakeup(&h.done) 139 default: 140 h.err = plainError("goroutine in unexpected state at call inject") 141 notewakeup(&h.done) 142 } 143 // Resume execution. 144 return true 145} 146 147func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 148 // TODO(49370): This code is riddled with write barriers, but called from 149 // a signal handler. Add the go:nowritebarrierrec annotation and restructure 150 // this to avoid write barriers. 151 152 // Double-check m. 153 if getg().m != h.mp { 154 println("trap on wrong M", getg().m, h.mp) 155 return false 156 } 157 f := findfunc(uintptr(ctxt.rip())) 158 if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) { 159 println("trap in unknown function", funcname(f)) 160 return false 161 } 162 if *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) != 0xcc { 163 println("trap at non-INT3 instruction pc =", hex(ctxt.rip())) 164 return false 165 } 166 167 switch status := ctxt.r12(); status { 168 case 0: 169 // Frame is ready. Copy the arguments to the frame and to registers. 170 sp := ctxt.rsp() 171 memmove(unsafe.Pointer(uintptr(sp)), h.argp, h.argSize) 172 if h.regArgs != nil { 173 storeRegArgs(ctxt.regs(), h.regArgs) 174 } 175 // Push return PC. 176 sp -= goarch.PtrSize 177 ctxt.set_rsp(sp) 178 *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.rip() 179 // Set PC to call and context register. 180 ctxt.set_rip(uint64(h.fv.fn)) 181 ctxt.regs().rdx = uint64(uintptr(unsafe.Pointer(h.fv))) 182 case 1: 183 // Function returned. Copy frame and result registers back out. 184 sp := ctxt.rsp() 185 memmove(h.argp, unsafe.Pointer(uintptr(sp)), h.argSize) 186 if h.regArgs != nil { 187 loadRegArgs(h.regArgs, ctxt.regs()) 188 } 189 case 2: 190 // Function panicked. Copy panic out. 191 sp := ctxt.rsp() 192 memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)), 2*goarch.PtrSize) 193 case 8: 194 // Call isn't safe. Get the reason. 195 sp := ctxt.rsp() 196 reason := *(*string)(unsafe.Pointer(uintptr(sp))) 197 h.err = plainError(reason) 198 // Don't wake h.done. We need to transition to status 16 first. 199 case 16: 200 // Restore all registers except RIP and RSP. 201 rip, rsp := ctxt.rip(), ctxt.rsp() 202 fp := ctxt.regs().fpstate 203 *ctxt.regs() = h.savedRegs 204 ctxt.regs().fpstate = fp 205 *fp = h.savedFP 206 ctxt.set_rip(rip) 207 ctxt.set_rsp(rsp) 208 // Done 209 notewakeup(&h.done) 210 default: 211 h.err = plainError("unexpected debugCallV2 status") 212 notewakeup(&h.done) 213 } 214 // Resume execution. 215 return true 216} 217