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/asm_amd64.s | |
| 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/asm_amd64.s')
| -rw-r--r-- | src/runtime/asm_amd64.s | 45 |
1 files changed, 23 insertions, 22 deletions
diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index e883f20045..a88cd17e6b 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -231,9 +231,9 @@ ok: CALL runtime·abort(SB) // mstart should never return RET - // Prevent dead-code elimination of debugCallV1, which is + // Prevent dead-code elimination of debugCallV2, which is // intended to be called by debuggers. - MOVQ $runtime·debugCallV1<ABIInternal>(SB), AX + MOVQ $runtime·debugCallV2<ABIInternal>(SB), AX RET // mainPC is a function value for runtime.main, to be passed to newproc. @@ -1763,7 +1763,7 @@ TEXT runtime·gcWriteBarrierR9<ABIInternal>(SB),NOSPLIT,$0 DATA debugCallFrameTooLarge<>+0x00(SB)/20, $"call frame too large" GLOBL debugCallFrameTooLarge<>(SB), RODATA, $20 // Size duplicated below -// debugCallV1 is the entry point for debugger-injected function +// debugCallV2 is the entry point for debugger-injected function // calls on running goroutines. It informs the runtime that a // debug call has been injected and creates a call frame for the // debugger to fill in. @@ -1776,7 +1776,7 @@ GLOBL debugCallFrameTooLarge<>(SB), RODATA, $20 // Size duplicated below // after step 2). // 4. Save all machine registers (including flags and XMM reigsters) // so they can be restored later by the debugger. -// 5. Set the PC to debugCallV1 and resume execution. +// 5. Set the PC to debugCallV2 and resume execution. // // If the goroutine is in state _Grunnable, then it's not generally // safe to inject a call because it may return out via other runtime @@ -1786,19 +1786,19 @@ GLOBL debugCallFrameTooLarge<>(SB), RODATA, $20 // Size duplicated below // // If the goroutine is in any other state, it's not safe to inject a call. // -// This function communicates back to the debugger by setting RAX and +// This function communicates back to the debugger by setting R12 and // invoking INT3 to raise a breakpoint signal. See the comments in the // implementation for the protocol the debugger is expected to // follow. InjectDebugCall in the runtime tests demonstrates this protocol. // // The debugger must ensure that any pointers passed to the function // obey escape analysis requirements. Specifically, it must not pass -// a stack pointer to an escaping argument. debugCallV1 cannot check +// a stack pointer to an escaping argument. debugCallV2 cannot check // this invariant. // // This is ABIInternal because Go code injects its PC directly into new // goroutine stacks. -TEXT runtime·debugCallV1<ABIInternal>(SB),NOSPLIT,$152-0 +TEXT runtime·debugCallV2<ABIInternal>(SB),NOSPLIT,$152-0 // Save all registers that may contain pointers so they can be // conservatively scanned. // @@ -1838,10 +1838,10 @@ TEXT runtime·debugCallV1<ABIInternal>(SB),NOSPLIT,$152-0 MOVQ AX, 0(SP) MOVQ 16(SP), AX MOVQ AX, 8(SP) - // Set AX to 8 and invoke INT3. The debugger should get the + // Set R12 to 8 and invoke INT3. The debugger should get the // reason a call can't be injected from the top of the stack // and resume execution. - MOVQ $8, AX + MOVQ $8, R12 BYTE $0xcc JMP restore @@ -1849,17 +1849,18 @@ good: // Registers are saved and it's safe to make a call. // Open up a call frame, moving the stack if necessary. // - // Once the frame is allocated, this will set AX to 0 and + // Once the frame is allocated, this will set R12 to 0 and // invoke INT3. The debugger should write the argument - // frame for the call at SP, push the trapping PC on the - // stack, set the PC to the function to call, set RCX to point - // to the closure (if a closure call), and resume execution. + // frame for the call at SP, set up argument registers, push + // the trapping PC on the stack, set the PC to the function to + // call, set RDX to point to the closure (if a closure call), + // and resume execution. // - // If the function returns, this will set AX to 1 and invoke + // If the function returns, this will set R12 to 1 and invoke // INT3. The debugger can then inspect any return value saved - // on the stack at SP and resume execution again. + // on the stack at SP and in registers and resume execution again. // - // If the function panics, this will set AX to 2 and invoke INT3. + // If the function panics, this will set R12 to 2 and invoke INT3. // The interface{} value of the panic will be at SP. The debugger // can inspect the panic value and resume execution again. #define DEBUG_CALL_DISPATCH(NAME,MAXSIZE) \ @@ -1887,16 +1888,16 @@ good: MOVQ $debugCallFrameTooLarge<>(SB), AX MOVQ AX, 0(SP) MOVQ $20, 8(SP) // length of debugCallFrameTooLarge string - MOVQ $8, AX + MOVQ $8, R12 BYTE $0xcc JMP restore restore: // Calls and failures resume here. // - // Set AX to 16 and invoke INT3. The debugger should restore + // Set R12 to 16 and invoke INT3. The debugger should restore // all registers except RIP and RSP and resume execution. - MOVQ $16, AX + MOVQ $16, R12 BYTE $0xcc // We must not modify flags after this point. @@ -1925,9 +1926,9 @@ restore: #define DEBUG_CALL_FN(NAME,MAXSIZE) \ TEXT NAME(SB),WRAPPER,$MAXSIZE-0; \ NO_LOCAL_POINTERS; \ - MOVQ $0, AX; \ + MOVQ $0, R12; \ BYTE $0xcc; \ - MOVQ $1, AX; \ + MOVQ $1, R12; \ BYTE $0xcc; \ RET DEBUG_CALL_FN(debugCall32<>, 32) @@ -1950,7 +1951,7 @@ TEXT runtime·debugCallPanicked(SB),NOSPLIT,$16-16 MOVQ AX, 0(SP) MOVQ val_data+8(FP), AX MOVQ AX, 8(SP) - MOVQ $2, AX + MOVQ $2, R12 BYTE $0xcc RET |
