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