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