Source file
src/runtime/export_debug_test.go
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 || loong64 || ppc64 || ppc64le) && linux
6
7 package runtime
8
9 import (
10 "internal/abi"
11 "internal/stringslite"
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.
25 func 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() != abi.Func {
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() != abi.Pointer {
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
92 type 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 sigCtxt sigContext
106 }
107
108 func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
109 // TODO(49370): This code is riddled with write barriers, but called from
110 // a signal handler. Add the go:nowritebarrierrec annotation and restructure
111 // this to avoid write barriers.
112
113 switch h.gp.atomicstatus.Load() {
114 case _Grunning:
115 if getg().m != h.mp {
116 println("trap on wrong M", getg().m, h.mp)
117 return false
118 }
119 // Save the signal context
120 h.saveSigContext(ctxt)
121 // Set PC to debugCallV2.
122 ctxt.setsigpc(uint64(abi.FuncPCABIInternal(debugCallV2)))
123 // Call injected. Switch to the debugCall protocol.
124 testSigtrap = h.handleF
125 case _Grunnable:
126 // Ask InjectDebugCall to pause for a bit and then try
127 // again to interrupt this goroutine.
128 h.err = plainError("retry _Grunnable")
129 notewakeup(&h.done)
130 default:
131 h.err = plainError("goroutine in unexpected state at call inject")
132 notewakeup(&h.done)
133 }
134 // Resume execution.
135 return true
136 }
137
138 func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
139 // TODO(49370): This code is riddled with write barriers, but called from
140 // a signal handler. Add the go:nowritebarrierrec annotation and restructure
141 // this to avoid write barriers.
142
143 // Double-check m.
144 if getg().m != h.mp {
145 println("trap on wrong M", getg().m, h.mp)
146 return false
147 }
148 f := findfunc(ctxt.sigpc())
149 if !(stringslite.HasPrefix(funcname(f), "runtime.debugCall") || stringslite.HasPrefix(funcname(f), "debugCall")) {
150 println("trap in unknown function", funcname(f))
151 return false
152 }
153 if !sigctxtAtTrapInstruction(ctxt) {
154 println("trap at non-INT3 instruction pc =", hex(ctxt.sigpc()))
155 return false
156 }
157
158 switch status := sigctxtStatus(ctxt); status {
159 case 0:
160 // Frame is ready. Copy the arguments to the frame and to registers.
161 // Call the debug function.
162 h.debugCallRun(ctxt)
163 case 1:
164 // Function returned. Copy frame and result registers back out.
165 h.debugCallReturn(ctxt)
166 case 2:
167 // Function panicked. Copy panic out.
168 h.debugCallPanicOut(ctxt)
169 case 8:
170 // Call isn't safe. Get the reason.
171 h.debugCallUnsafe(ctxt)
172 // Don't wake h.done. We need to transition to status 16 first.
173 case 16:
174 h.restoreSigContext(ctxt)
175 // Done
176 notewakeup(&h.done)
177 default:
178 h.err = plainError("unexpected debugCallV2 status")
179 notewakeup(&h.done)
180 }
181 // Resume execution.
182 return true
183 }
184
View as plain text