diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2021-04-10 00:05:07 +0000 |
|---|---|---|
| committer | Michael Knyszek <mknyszek@google.com> | 2021-04-14 19:54:26 +0000 |
| commit | a89ace106f820a4f3b129c81ba0bcf0c48b5b7cd (patch) | |
| tree | 57f8b82d0e3d9215334fc3cd939293c96e8a2014 /src/runtime/export_debug_test.go | |
| parent | de7a87ef066d726eddcc47a018a2bc8fbd3793af (diff) | |
| download | go-a89ace106f820a4f3b129c81ba0bcf0c48b5b7cd.tar.xz | |
runtime: update debug call protocol for register ABI
The debug call tests currently assume that the target Go function is
ABI0; this is clearly no longer true when we switch to the new ABI, so
make the tests set up argument register state in the debug call handler
and copy back results returned in registers.
A small snag in calling a Go function that follows the new ABI is that
the debug call protocol depends on the AX register being set to a
specific value as it bounces in and out of the handler, but this
register is part of the new register ABI, so results end up being
clobbered. Use R12 instead.
Next, the new desugaring behavior for "go" statements means that
newosproc1 must always call a function with no frame; if it takes any
arguments, it closes over them and they're passed in the context
register. Currently when debugCallWrap creates a new goroutine, it uses
newosproc1 directly and passes a non-zero-sized frame, so that needs to
be updated. To fix this, briefly use the g's param field which is
otherwise only used for channels to pass an explicitly allocated object
containing the "closed over" variables. While we could manually do the
desugaring ourselves (we cannot do so automatically because the Go
compiler prevents heap-allocated closures in the runtime), that bakes in
more ABI details in a place that really doesn't need to care about them.
Finally, there's an old bug here where the context register was set up
in CX, so technically closure calls never worked. Oops. It was otherwise
harmless for other types of calls before, but now CX is an argument
register, so now that interferes with regular calls, too.
For #40724.
Change-Id: I652c25ed56a25741bb04c24cfb603063c099edde
Reviewed-on: https://go-review.googlesource.com/c/go/+/309169
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Diffstat (limited to 'src/runtime/export_debug_test.go')
| -rw-r--r-- | src/runtime/export_debug_test.go | 38 |
1 files changed, 24 insertions, 14 deletions
diff --git a/src/runtime/export_debug_test.go b/src/runtime/export_debug_test.go index 18ccecd5cd..fe4c9045c1 100644 --- a/src/runtime/export_debug_test.go +++ b/src/runtime/export_debug_test.go @@ -8,19 +8,22 @@ package runtime import ( + "internal/abi" "runtime/internal/sys" "unsafe" ) -// InjectDebugCall injects a debugger call to fn into g. args must be -// a pointer to a valid call frame (including arguments and return -// space) for fn, or nil. tkill must be a function that will send -// SIGTRAP to thread ID tid. gp must be locked to its OS thread and +// InjectDebugCall injects a debugger call to fn into g. regArgs must +// contain any arguments to fn that are passed in registers, according +// to the internal Go ABI. It may be nil if no arguments are passed in +// registers to fn. args must be a pointer to a valid call frame (including +// arguments and return space) for fn, or nil. tkill must be a function that +// will send SIGTRAP to thread ID tid. gp must be locked to its OS thread and // running. // // On success, InjectDebugCall returns the panic value of fn or nil. // If fn did not panic, its results will be available in args. -func InjectDebugCall(gp *g, fn, args interface{}, tkill func(tid int) error, returnOnUnsafePoint bool) (interface{}, error) { +func InjectDebugCall(gp *g, fn interface{}, regArgs *abi.RegArgs, stackArgs interface{}, tkill func(tid int) error, returnOnUnsafePoint bool) (interface{}, error) { if gp.lockedm == 0 { return nil, plainError("goroutine not locked to thread") } @@ -36,7 +39,7 @@ func InjectDebugCall(gp *g, fn, args interface{}, tkill func(tid int) error, ret } fv := (*funcval)(f.data) - a := efaceOf(&args) + a := efaceOf(&stackArgs) if a._type != nil && a._type.kind&kindMask != kindPtr { return nil, plainError("args must be a pointer or nil") } @@ -51,7 +54,7 @@ func InjectDebugCall(gp *g, fn, args interface{}, tkill func(tid int) error, ret // gp may not be running right now, but we can still get the M // it will run on since it's locked. h.mp = gp.lockedm.ptr() - h.fv, h.argp, h.argSize = fv, argp, argSize + h.fv, h.regArgs, h.argp, h.argSize = fv, regArgs, argp, argSize h.handleF = h.handle // Avoid allocating closure during signal defer func() { testSigtrap = nil }() @@ -91,6 +94,7 @@ type debugCallHandler struct { gp *g mp *m fv *funcval + regArgs *abi.RegArgs argp unsafe.Pointer argSize uintptr panic interface{} @@ -120,8 +124,8 @@ func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool { h.savedRegs = *ctxt.regs() h.savedFP = *h.savedRegs.fpstate h.savedRegs.fpstate = nil - // Set PC to debugCallV1. - ctxt.set_rip(uint64(funcPC(debugCallV1))) + // Set PC to debugCallV2. + ctxt.set_rip(uint64(funcPC(debugCallV2))) // Call injected. Switch to the debugCall protocol. testSigtrap = h.handleF case _Grunnable: @@ -153,22 +157,28 @@ func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { return false } - switch status := ctxt.rax(); status { + switch status := ctxt.r12(); status { case 0: - // Frame is ready. Copy the arguments to the frame. + // Frame is ready. Copy the arguments to the frame and to registers. sp := ctxt.rsp() memmove(unsafe.Pointer(uintptr(sp)), h.argp, h.argSize) + if h.regArgs != nil { + storeRegArgs(ctxt.regs(), h.regArgs) + } // Push return PC. sp -= sys.PtrSize ctxt.set_rsp(sp) *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.rip() // Set PC to call and context register. ctxt.set_rip(uint64(h.fv.fn)) - ctxt.regs().rcx = uint64(uintptr(unsafe.Pointer(h.fv))) + ctxt.regs().rdx = uint64(uintptr(unsafe.Pointer(h.fv))) case 1: - // Function returned. Copy frame back out. + // Function returned. Copy frame and result registers back out. sp := ctxt.rsp() memmove(h.argp, unsafe.Pointer(uintptr(sp)), h.argSize) + if h.regArgs != nil { + loadRegArgs(h.regArgs, ctxt.regs()) + } case 2: // Function panicked. Copy panic out. sp := ctxt.rsp() @@ -191,7 +201,7 @@ func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool { // Done notewakeup(&h.done) default: - h.err = plainError("unexpected debugCallV1 status") + h.err = plainError("unexpected debugCallV2 status") notewakeup(&h.done) } // Resume execution. |
