github.com/lzhfromustc/gofuzz@v0.0.0-20211116160056-151b3108bbd1/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 "runtime/internal/sys" 12 "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 // gp may not be running right now, but we can still get the M 52 // it will run on since it's locked. 53 h.mp = gp.lockedm.ptr() 54 h.fv, h.argp, h.argSize = fv, argp, argSize 55 h.handleF = h.handle // Avoid allocating closure during signal 56 57 defer func() { testSigtrap = nil }() 58 for i := 0; ; i++ { 59 testSigtrap = h.inject 60 noteclear(&h.done) 61 h.err = "" 62 63 if err := tkill(tid); err != nil { 64 return nil, err 65 } 66 // Wait for completion. 67 notetsleepg(&h.done, -1) 68 if h.err != "" { 69 switch h.err { 70 case "call not at safe point": 71 if returnOnUnsafePoint { 72 // This is for TestDebugCallUnsafePoint. 73 return nil, h.err 74 } 75 fallthrough 76 case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime": 77 // These are transient states. Try to get out of them. 78 if i < 100 { 79 usleep(100) 80 Gosched() 81 continue 82 } 83 } 84 return nil, h.err 85 } 86 return h.panic, nil 87 } 88 } 89 90 type debugCallHandler struct { 91 gp *g 92 mp *m 93 fv *funcval 94 argp unsafe.Pointer 95 argSize uintptr 96 panic interface{} 97 98 handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool 99 100 err plainError 101 done note 102 savedRegs sigcontext 103 savedFP fpstate1 104 } 105 106 func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 107 switch h.gp.atomicstatus { 108 case _Grunning: 109 if getg().m != h.mp { 110 println("trap on wrong M", getg().m, h.mp) 111 return false 112 } 113 // Push current PC on the stack. 114 rsp := ctxt.rsp() - sys.PtrSize 115 *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip() 116 ctxt.set_rsp(rsp) 117 // Write the argument frame size. 118 *(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize 119 // Save current registers. 120 h.savedRegs = *ctxt.regs() 121 h.savedFP = *h.savedRegs.fpstate 122 h.savedRegs.fpstate = nil 123 // Set PC to debugCallV1. 124 ctxt.set_rip(uint64(funcPC(debugCallV1))) 125 // Call injected. Switch to the debugCall protocol. 126 testSigtrap = h.handleF 127 case _Grunnable: 128 // Ask InjectDebugCall to pause for a bit and then try 129 // again to interrupt this goroutine. 130 h.err = plainError("retry _Grunnable") 131 notewakeup(&h.done) 132 default: 133 h.err = plainError("goroutine in unexpected state at call inject") 134 notewakeup(&h.done) 135 } 136 // Resume execution. 137 return true 138 } 139 140 func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { 141 // Sanity check. 142 if getg().m != h.mp { 143 println("trap on wrong M", getg().m, h.mp) 144 return false 145 } 146 f := findfunc(uintptr(ctxt.rip())) 147 if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) { 148 println("trap in unknown function", funcname(f)) 149 return false 150 } 151 if *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) != 0xcc { 152 println("trap at non-INT3 instruction pc =", hex(ctxt.rip())) 153 return false 154 } 155 156 switch status := ctxt.rax(); status { 157 case 0: 158 // Frame is ready. Copy the arguments to the frame. 159 sp := ctxt.rsp() 160 memmove(unsafe.Pointer(uintptr(sp)), h.argp, h.argSize) 161 // Push return PC. 162 sp -= sys.PtrSize 163 ctxt.set_rsp(sp) 164 *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.rip() 165 // Set PC to call and context register. 166 ctxt.set_rip(uint64(h.fv.fn)) 167 ctxt.regs().rcx = uint64(uintptr(unsafe.Pointer(h.fv))) 168 case 1: 169 // Function returned. Copy frame back out. 170 sp := ctxt.rsp() 171 memmove(h.argp, unsafe.Pointer(uintptr(sp)), h.argSize) 172 case 2: 173 // Function panicked. Copy panic out. 174 sp := ctxt.rsp() 175 memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)), 2*sys.PtrSize) 176 case 8: 177 // Call isn't safe. Get the reason. 178 sp := ctxt.rsp() 179 reason := *(*string)(unsafe.Pointer(uintptr(sp))) 180 h.err = plainError(reason) 181 // Don't wake h.done. We need to transition to status 16 first. 182 case 16: 183 // Restore all registers except RIP and RSP. 184 rip, rsp := ctxt.rip(), ctxt.rsp() 185 fp := ctxt.regs().fpstate 186 *ctxt.regs() = h.savedRegs 187 ctxt.regs().fpstate = fp 188 *fp = h.savedFP 189 ctxt.set_rip(rip) 190 ctxt.set_rsp(rsp) 191 // Done 192 notewakeup(&h.done) 193 default: 194 h.err = plainError("unexpected debugCallV1 status") 195 notewakeup(&h.done) 196 } 197 // Resume execution. 198 return true 199 }