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