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.
5 // +build ignore_for_gccgo
12 "runtime/internal/sys"
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
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.
24 func InjectDebugCall(gp
*g
, fn
, args
interface{}, tkill
func(tid
int) error
) (interface{}, error
) {
26 return nil, plainError("goroutine not locked to thread")
29 tid
:= int(gp
.lockedm
.ptr().procid
)
31 return nil, plainError("missing tid")
35 if f
._type
== nil || f
._type
.kind
&kindMask
!= kindFunc
{
36 return nil, plainError("fn must be a function")
38 fv
:= (*funcval
)(f
.data
)
41 if a
._type
!= nil && a
._type
.kind
&kindMask
!= kindPtr
{
42 return nil, plainError("args must be a pointer or nil")
47 argSize
= (*ptrtype
)(unsafe
.Pointer(a
._type
)).elem
.size
50 h
:= new(debugCallHandler
)
52 h
.fv
, h
.argp
, h
.argSize
= fv
, argp
, argSize
53 h
.handleF
= h
.handle
// Avoid allocating closure during signal
56 defer func() { testSigtrap
= nil }()
57 testSigtrap
= h
.inject
58 if err
:= tkill(tid
); err
!= nil {
61 // Wait for completion.
62 notetsleepg(&h
.done
, -1)
69 type debugCallHandler
struct {
76 handleF
func(info
*siginfo
, ctxt
*sigctxt
, gp2
*g
) bool
84 func (h
*debugCallHandler
) inject(info
*siginfo
, ctxt
*sigctxt
, gp2
*g
) bool {
85 switch h
.gp
.atomicstatus
{
87 if getg().m
!= h
.gp
.m
{
88 println("trap on wrong M", getg().m
, h
.gp
.m
)
91 // Push current PC on the stack.
92 rsp
:= ctxt
.rsp() - sys
.PtrSize
93 *(*uint64)(unsafe
.Pointer(uintptr(rsp
))) = ctxt
.rip()
95 // Write the argument frame size.
96 *(*uintptr)(unsafe
.Pointer(uintptr(rsp
- 16))) = h
.argSize
97 // Save current registers.
98 h
.savedRegs
= *ctxt
.regs()
99 h
.savedFP
= *h
.savedRegs
.fpstate
100 h
.savedRegs
.fpstate
= nil
101 // Set PC to debugCallV1.
102 ctxt
.set_rip(uint64(funcPC(debugCallV1
)))
104 h
.err
= plainError("goroutine in unexpected state at call inject")
107 // Switch to the debugCall protocol and resume execution.
108 testSigtrap
= h
.handleF
112 func (h
*debugCallHandler
) handle(info
*siginfo
, ctxt
*sigctxt
, gp2
*g
) bool {
114 if getg().m
!= h
.gp
.m
{
115 println("trap on wrong M", getg().m
, h
.gp
.m
)
118 f
:= findfunc(uintptr(ctxt
.rip()))
119 if !(hasprefix(funcname(f
), "runtime.debugCall") ||
hasprefix(funcname(f
), "debugCall")) {
120 println("trap in unknown function", funcname(f
))
123 if *(*byte)(unsafe
.Pointer(uintptr(ctxt
.rip() - 1))) != 0xcc {
124 println("trap at non-INT3 instruction pc =", hex(ctxt
.rip()))
128 switch status
:= ctxt
.rax(); status
{
130 // Frame is ready. Copy the arguments to the frame.
132 memmove(unsafe
.Pointer(uintptr(sp
)), h
.argp
, h
.argSize
)
136 *(*uint64)(unsafe
.Pointer(uintptr(sp
))) = ctxt
.rip()
137 // Set PC to call and context register.
138 ctxt
.set_rip(uint64(h
.fv
.fn
))
139 ctxt
.regs().rcx
= uint64(uintptr(unsafe
.Pointer(h
.fv
)))
141 // Function returned. Copy frame back out.
143 memmove(h
.argp
, unsafe
.Pointer(uintptr(sp
)), h
.argSize
)
145 // Function panicked. Copy panic out.
147 memmove(unsafe
.Pointer(&h
.panic), unsafe
.Pointer(uintptr(sp
)), 2*sys
.PtrSize
)
149 // Call isn't safe. Get the reason.
151 reason
:= *(*string)(unsafe
.Pointer(uintptr(sp
)))
152 h
.err
= plainError(reason
)
154 // Restore all registers except RIP and RSP.
155 rip
, rsp
:= ctxt
.rip(), ctxt
.rsp()
156 fp
:= ctxt
.regs().fpstate
157 *ctxt
.regs() = h
.savedRegs
158 ctxt
.regs().fpstate
= fp
165 h
.err
= plainError("unexpected debugCallV1 status")