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// +build ignore_for_gccgo 6// +build amd64 7// +build linux 8 9package runtime 10 11import ( 12 "runtime/internal/sys" 13 "unsafe" 14) 15 16// InjectDebugCall injects a debugger call to fn into g. args must be 17// a pointer to a valid call frame (including arguments and return 18// space) for fn, or nil. tkill must be a function that will send 19// 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. 24func InjectDebugCall(gp *g, fn, args interface{}, tkill func(tid int) error, returnOnUnsafePoint bool) (interface{}, 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(&args) 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 h.fv, h.argp, h.argSize = fv, argp, argSize 53 h.handleF = h.handle // Avoid allocating closure during signal 54 55 defer func() { testSigtrap = nil }() 56 for i := 0; ; i++ { 57 testSigtrap = h.inject 58 noteclear(&h.done) 59 h.err = "" 60 61 if err := tkill(tid); err != nil { 62 return nil, err 63 } 64 // Wait for completion. 65 notetsleepg(&h.done, -1) 66 if h.err != "" { 67 switch h.err { 68 case "call not at safe point": 69 if returnOnUnsafePoint { 70 // This is for TestDebugCallUnsafePoint. 71 return nil, h.err 72 } 73 fallthrough 74 case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime": 75 // These are transient states. Try to get out of them. 76 if i < 100 { 77 usleep(100) 78 Gosched() 79 continue 80 } 81 } 82 return nil, h.err 83 } 84 return h.panic, nil 85 } 86} 87 88type debugCallHandler struct { 89 gp *g 90 fv *funcval 91 argp unsafe.Pointer 92 argSize uintptr 93 panic interface{} 94 95 handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool 96 97 err plainError 98 done note 99 savedRegs sigcontext 100 savedFP fpstate1 101} 102 103func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 104 switch h.gp.atomicstatus { 105 case _Grunning: 106 if getg().m != h.gp.m { 107 println("trap on wrong M", getg().m, h.gp.m) 108 return false 109 } 110 // Push current PC on the stack. 111 rsp := ctxt.rsp() - sys.PtrSize 112 *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip() 113 ctxt.set_rsp(rsp) 114 // Write the argument frame size. 115 *(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize 116 // Save current registers. 117 h.savedRegs = *ctxt.regs() 118 h.savedFP = *h.savedRegs.fpstate 119 h.savedRegs.fpstate = nil 120 // Set PC to debugCallV1. 121 ctxt.set_rip(uint64(funcPC(debugCallV1))) 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 137func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 138 // Sanity check. 139 if getg().m != h.gp.m { 140 println("trap on wrong M", getg().m, h.gp.m) 141 return false 142 } 143 f := findfunc(uintptr(ctxt.rip())) 144 if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) { 145 println("trap in unknown function", funcname(f)) 146 return false 147 } 148 if *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) != 0xcc { 149 println("trap at non-INT3 instruction pc =", hex(ctxt.rip())) 150 return false 151 } 152 153 switch status := ctxt.rax(); status { 154 case 0: 155 // Frame is ready. Copy the arguments to the frame. 156 sp := ctxt.rsp() 157 memmove(unsafe.Pointer(uintptr(sp)), h.argp, h.argSize) 158 // Push return PC. 159 sp -= sys.PtrSize 160 ctxt.set_rsp(sp) 161 *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.rip() 162 // Set PC to call and context register. 163 ctxt.set_rip(uint64(h.fv.fn)) 164 ctxt.regs().rcx = uint64(uintptr(unsafe.Pointer(h.fv))) 165 case 1: 166 // Function returned. Copy frame back out. 167 sp := ctxt.rsp() 168 memmove(h.argp, unsafe.Pointer(uintptr(sp)), h.argSize) 169 case 2: 170 // Function panicked. Copy panic out. 171 sp := ctxt.rsp() 172 memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)), 2*sys.PtrSize) 173 case 8: 174 // Call isn't safe. Get the reason. 175 sp := ctxt.rsp() 176 reason := *(*string)(unsafe.Pointer(uintptr(sp))) 177 h.err = plainError(reason) 178 // Don't wake h.done. We need to transition to status 16 first. 179 case 16: 180 // Restore all registers except RIP and RSP. 181 rip, rsp := ctxt.rip(), ctxt.rsp() 182 fp := ctxt.regs().fpstate 183 *ctxt.regs() = h.savedRegs 184 ctxt.regs().fpstate = fp 185 *fp = h.savedFP 186 ctxt.set_rip(rip) 187 ctxt.set_rsp(rsp) 188 // Done 189 notewakeup(&h.done) 190 default: 191 h.err = plainError("unexpected debugCallV1 status") 192 notewakeup(&h.done) 193 } 194 // Resume execution. 195 return true 196} 197