]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: support for debugger function calls on linux/arm64
authoreric fang <eric.fang@arm.com>
Tue, 22 Mar 2022 07:04:35 +0000 (07:04 +0000)
committerEric Fang <eric.fang@arm.com>
Sat, 23 Apr 2022 05:38:56 +0000 (05:38 +0000)
This CL adds support for debugger function calls on linux arm64
platform. The protocol is basically the same as in CL 109699, except for
the following differences:
1, The abi difference which affect parameter passing and frame layout.
2, Stores communication information in R20.
3, The closure register is R26.
4, Use BRK 0 instruction to generate a breakpoint. The saved PC in
sigcontext is the PC where the signal occurred, not the next PC.

In addition, this CL refactors the existing code (which is dedicated to
amd64) for easier multi-arch scaling.

Fixes #50614

Change-Id: I06b14e345cc89aab175f4a5f2287b765da85a86b
Reviewed-on: https://go-review.googlesource.com/c/go/+/395754
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Eric Fang <eric.fang@arm.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/runtime/asm_arm64.s
src/runtime/debug_test.go
src/runtime/debugcall.go
src/runtime/export_debug_amd64_test.go [new file with mode: 0644]
src/runtime/export_debug_arm64_test.go [new file with mode: 0644]
src/runtime/export_debug_regabiargs_off_test.go [deleted file]
src/runtime/export_debug_regabiargs_on_test.go [deleted file]
src/runtime/export_debug_test.go
src/runtime/signal_amd64.go
src/runtime/signal_arm64.go
src/runtime/traceback.go

index 9ef7346e00207e3f66553b22ab3d4b60ac0645ae..956910f6ce7642ab467da1ee930598acdbd26761 100644 (file)
@@ -96,6 +96,10 @@ nocgo:
        // start this M
        BL      runtime·mstart(SB)
 
+       // Prevent dead-code elimination of debugCallV2, which is
+       // intended to be called by debuggers.
+       MOVD    $runtime·debugCallV2<ABIInternal>(SB), R0
+
        MOVD    $0, R0
        MOVD    R0, (R0)        // boom
        UNDEF
@@ -1240,6 +1244,200 @@ flush:
        LDP     21*8(RSP), (R25, R26)
        JMP     ret
 
+DATA   debugCallFrameTooLarge<>+0x00(SB)/20, $"call frame too large"
+GLOBL  debugCallFrameTooLarge<>(SB), RODATA, $20       // Size duplicated below
+
+// 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.
+//
+// To inject a function call, a debugger should:
+// 1. Check that the goroutine is in state _Grunning and that
+//    there are at least 288 bytes free on the stack.
+// 2. Set SP as SP-16.
+// 3. Store the current LR in (SP) (using the SP after step 2).
+// 4. Store the current PC in the LR register.
+// 5. Write the desired argument frame size at SP-16
+// 6. Save all machine registers (including flags and fpsimd reigsters)
+//    so they can be restored later by the debugger.
+// 7. 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
+// operations. Instead, the debugger should unwind the stack to find
+// the return to non-runtime code, add a temporary breakpoint there,
+// and inject the call once that breakpoint is hit.
+//
+// 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 R20 and
+// invoking BRK 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. debugCallV2 cannot check
+// this invariant.
+//
+// This is ABIInternal because Go code injects its PC directly into new
+// goroutine stacks.
+TEXT runtime·debugCallV2<ABIInternal>(SB),NOSPLIT|NOFRAME,$0-0
+       STP     (R29, R30), -280(RSP)
+       SUB     $272, RSP, RSP
+       SUB     $8, RSP, R29
+       // Save all registers that may contain pointers so they can be
+       // conservatively scanned.
+       //
+       // We can't do anything that might clobber any of these
+       // registers before this.
+       STP     (R27, g), (30*8)(RSP)
+       STP     (R25, R26), (28*8)(RSP)
+       STP     (R23, R24), (26*8)(RSP)
+       STP     (R21, R22), (24*8)(RSP)
+       STP     (R19, R20), (22*8)(RSP)
+       STP     (R16, R17), (20*8)(RSP)
+       STP     (R14, R15), (18*8)(RSP)
+       STP     (R12, R13), (16*8)(RSP)
+       STP     (R10, R11), (14*8)(RSP)
+       STP     (R8, R9), (12*8)(RSP)
+       STP     (R6, R7), (10*8)(RSP)
+       STP     (R4, R5), (8*8)(RSP)
+       STP     (R2, R3), (6*8)(RSP)
+       STP     (R0, R1), (4*8)(RSP)
+
+       // Perform a safe-point check.
+       MOVD    R30, 8(RSP) // Caller's PC
+       CALL    runtime·debugCallCheck(SB)
+       MOVD    16(RSP), R0
+       CBZ     R0, good
+
+       // The safety check failed. Put the reason string at the top
+       // of the stack.
+       MOVD    R0, 8(RSP)
+       MOVD    24(RSP), R0
+       MOVD    R0, 16(RSP)
+
+       // Set R20 to 8 and invoke BRK. The debugger should get the
+       // reason a call can't be injected from SP+8 and resume execution.
+       MOVD    $8, R20
+       BRK
+       JMP     restore
+
+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 R20 to 0 and
+       // invoke BRK. The debugger should write the argument
+       // frame for the call at SP+8, set up argument registers,
+       // set the lr as the signal PC + 4, set the PC to the function
+       // to call, set R26 to point to the closure (if a closure call),
+       // and resume execution.
+       //
+       // If the function returns, this will set R20 to 1 and invoke
+       // BRK. The debugger can then inspect any return value saved
+       // on the stack at SP+8 and in registers and resume execution again.
+       //
+       // If the function panics, this will set R20 to 2 and invoke BRK.
+       // The interface{} value of the panic will be at SP+8. The debugger
+       // can inspect the panic value and resume execution again.
+#define DEBUG_CALL_DISPATCH(NAME,MAXSIZE)      \
+       CMP     $MAXSIZE, R0;                   \
+       BGT     5(PC);                          \
+       MOVD    $NAME(SB), R0;                  \
+       MOVD    R0, 8(RSP);                     \
+       CALL    runtime·debugCallWrap(SB);     \
+       JMP     restore
+
+       MOVD    256(RSP), R0 // the argument frame size
+       DEBUG_CALL_DISPATCH(debugCall32<>, 32)
+       DEBUG_CALL_DISPATCH(debugCall64<>, 64)
+       DEBUG_CALL_DISPATCH(debugCall128<>, 128)
+       DEBUG_CALL_DISPATCH(debugCall256<>, 256)
+       DEBUG_CALL_DISPATCH(debugCall512<>, 512)
+       DEBUG_CALL_DISPATCH(debugCall1024<>, 1024)
+       DEBUG_CALL_DISPATCH(debugCall2048<>, 2048)
+       DEBUG_CALL_DISPATCH(debugCall4096<>, 4096)
+       DEBUG_CALL_DISPATCH(debugCall8192<>, 8192)
+       DEBUG_CALL_DISPATCH(debugCall16384<>, 16384)
+       DEBUG_CALL_DISPATCH(debugCall32768<>, 32768)
+       DEBUG_CALL_DISPATCH(debugCall65536<>, 65536)
+       // The frame size is too large. Report the error.
+       MOVD    $debugCallFrameTooLarge<>(SB), R0
+       MOVD    R0, 8(RSP)
+       MOVD    $20, R0
+       MOVD    R0, 16(RSP) // length of debugCallFrameTooLarge string
+       MOVD    $8, R20
+       BRK
+       JMP     restore
+
+restore:
+       // Calls and failures resume here.
+       //
+       // Set R20 to 16 and invoke BRK. The debugger should restore
+       // all registers except for PC and RSP and resume execution.
+       MOVD    $16, R20
+       BRK
+       // We must not modify flags after this point.
+
+       // Restore pointer-containing registers, which may have been
+       // modified from the debugger's copy by stack copying.
+       LDP     (30*8)(RSP), (R27, g)
+       LDP     (28*8)(RSP), (R25, R26)
+       LDP     (26*8)(RSP), (R23, R24)
+       LDP     (24*8)(RSP), (R21, R22)
+       LDP     (22*8)(RSP), (R19, R20)
+       LDP     (20*8)(RSP), (R16, R17)
+       LDP     (18*8)(RSP), (R14, R15)
+       LDP     (16*8)(RSP), (R12, R13)
+       LDP     (14*8)(RSP), (R10, R11)
+       LDP     (12*8)(RSP), (R8, R9)
+       LDP     (10*8)(RSP), (R6, R7)
+       LDP     (8*8)(RSP), (R4, R5)
+       LDP     (6*8)(RSP), (R2, R3)
+       LDP     (4*8)(RSP), (R0, R1)
+
+       LDP     -8(RSP), (R29, R27)
+       ADD     $288, RSP, RSP // Add 16 more bytes, see saveSigContext
+       MOVD    -16(RSP), R30 // restore old lr
+       JMP     (R27)
+
+// runtime.debugCallCheck assumes that functions defined with the
+// DEBUG_CALL_FN macro are safe points to inject calls.
+#define DEBUG_CALL_FN(NAME,MAXSIZE)            \
+TEXT NAME(SB),WRAPPER,$MAXSIZE-0;              \
+       NO_LOCAL_POINTERS;              \
+       MOVD    $0, R20;                \
+       BRK;            \
+       MOVD    $1, R20;                \
+       BRK;            \
+       RET
+DEBUG_CALL_FN(debugCall32<>, 32)
+DEBUG_CALL_FN(debugCall64<>, 64)
+DEBUG_CALL_FN(debugCall128<>, 128)
+DEBUG_CALL_FN(debugCall256<>, 256)
+DEBUG_CALL_FN(debugCall512<>, 512)
+DEBUG_CALL_FN(debugCall1024<>, 1024)
+DEBUG_CALL_FN(debugCall2048<>, 2048)
+DEBUG_CALL_FN(debugCall4096<>, 4096)
+DEBUG_CALL_FN(debugCall8192<>, 8192)
+DEBUG_CALL_FN(debugCall16384<>, 16384)
+DEBUG_CALL_FN(debugCall32768<>, 32768)
+DEBUG_CALL_FN(debugCall65536<>, 65536)
+
+// func debugCallPanicked(val interface{})
+TEXT runtime·debugCallPanicked(SB),NOSPLIT,$16-16
+       // Copy the panic value to the top of stack at SP+8.
+       MOVD    val_type+0(FP), R0
+       MOVD    R0, 8(RSP)
+       MOVD    val_data+8(FP), R0
+       MOVD    R0, 16(RSP)
+       MOVD    $2, R20
+       BRK
+       RET
+
 // Note: these functions use a special calling convention to save generated code space.
 // Arguments are passed in registers, but the space for those arguments are allocated
 // in the caller's stack frame. These stubs write the args into that stack space and
index 7698eacb5957dcc2c84e205adb47e07b6b6d1490..75fe07ec2a2df87e3d6857ff920911440c708948 100644 (file)
@@ -9,7 +9,7 @@
 // spends all of its time in the race runtime, which isn't a safe
 // point.
 
-//go:build amd64 && linux && !race
+//go:build (amd64 || arm64) && linux && !race
 
 package runtime_test
 
index 205971c4286876ec8206ad82e38ad08b9ba8bc5d..2f164e7fd7ca2fc380c9c88fcac1801b8180ee27 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build amd64
+//go:build amd64 || arm64
 
 package runtime
 
diff --git a/src/runtime/export_debug_amd64_test.go b/src/runtime/export_debug_amd64_test.go
new file mode 100644 (file)
index 0000000..f9908cd
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build amd64 && linux
+
+package runtime
+
+import (
+       "internal/abi"
+       "internal/goarch"
+       "unsafe"
+)
+
+type sigContext struct {
+       savedRegs sigcontext
+       // sigcontext.fpstate is a pointer, so we need to save
+       // the its value with a fpstate1 structure.
+       savedFP fpstate1
+}
+
+func sigctxtSetContextRegister(ctxt *sigctxt, x uint64) {
+       ctxt.regs().rdx = x
+}
+
+func sigctxtAtTrapInstruction(ctxt *sigctxt) bool {
+       return *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) == 0xcc // INT 3
+}
+
+func sigctxtStatus(ctxt *sigctxt) uint64 {
+       return ctxt.r12()
+}
+
+func (h *debugCallHandler) saveSigContext(ctxt *sigctxt) {
+       // Push current PC on the stack.
+       rsp := ctxt.rsp() - goarch.PtrSize
+       *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip()
+       ctxt.set_rsp(rsp)
+       // Write the argument frame size.
+       *(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize
+       // Save current registers.
+       h.sigCtxt.savedRegs = *ctxt.regs()
+       h.sigCtxt.savedFP = *h.sigCtxt.savedRegs.fpstate
+       h.sigCtxt.savedRegs.fpstate = nil
+}
+
+// case 0
+func (h *debugCallHandler) debugCallRun(ctxt *sigctxt) {
+       rsp := ctxt.rsp()
+       memmove(unsafe.Pointer(uintptr(rsp)), h.argp, h.argSize)
+       if h.regArgs != nil {
+               storeRegArgs(ctxt.regs(), h.regArgs)
+       }
+       // Push return PC.
+       rsp -= goarch.PtrSize
+       ctxt.set_rsp(rsp)
+       // The signal PC is the next PC of the trap instruction.
+       *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip()
+       // Set PC to call and context register.
+       ctxt.set_rip(uint64(h.fv.fn))
+       sigctxtSetContextRegister(ctxt, uint64(uintptr(unsafe.Pointer(h.fv))))
+}
+
+// case 1
+func (h *debugCallHandler) debugCallReturn(ctxt *sigctxt) {
+       rsp := ctxt.rsp()
+       memmove(h.argp, unsafe.Pointer(uintptr(rsp)), h.argSize)
+       if h.regArgs != nil {
+               loadRegArgs(h.regArgs, ctxt.regs())
+       }
+}
+
+// case 2
+func (h *debugCallHandler) debugCallPanicOut(ctxt *sigctxt) {
+       rsp := ctxt.rsp()
+       memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(rsp)), 2*goarch.PtrSize)
+}
+
+// case 8
+func (h *debugCallHandler) debugCallUnsafe(ctxt *sigctxt) {
+       rsp := ctxt.rsp()
+       reason := *(*string)(unsafe.Pointer(uintptr(rsp)))
+       h.err = plainError(reason)
+}
+
+// case 16
+func (h *debugCallHandler) restoreSigContext(ctxt *sigctxt) {
+       // Restore all registers except RIP and RSP.
+       rip, rsp := ctxt.rip(), ctxt.rsp()
+       fp := ctxt.regs().fpstate
+       *ctxt.regs() = h.sigCtxt.savedRegs
+       ctxt.regs().fpstate = fp
+       *fp = h.sigCtxt.savedFP
+       ctxt.set_rip(rip)
+       ctxt.set_rsp(rsp)
+}
+
+// storeRegArgs sets up argument registers in the signal
+// context state from an abi.RegArgs.
+//
+// Both src and dst must be non-nil.
+func storeRegArgs(dst *sigcontext, src *abi.RegArgs) {
+       dst.rax = uint64(src.Ints[0])
+       dst.rbx = uint64(src.Ints[1])
+       dst.rcx = uint64(src.Ints[2])
+       dst.rdi = uint64(src.Ints[3])
+       dst.rsi = uint64(src.Ints[4])
+       dst.r8 = uint64(src.Ints[5])
+       dst.r9 = uint64(src.Ints[6])
+       dst.r10 = uint64(src.Ints[7])
+       dst.r11 = uint64(src.Ints[8])
+       for i := range src.Floats {
+               dst.fpstate._xmm[i].element[0] = uint32(src.Floats[i] >> 0)
+               dst.fpstate._xmm[i].element[1] = uint32(src.Floats[i] >> 32)
+       }
+}
+
+func loadRegArgs(dst *abi.RegArgs, src *sigcontext) {
+       dst.Ints[0] = uintptr(src.rax)
+       dst.Ints[1] = uintptr(src.rbx)
+       dst.Ints[2] = uintptr(src.rcx)
+       dst.Ints[3] = uintptr(src.rdi)
+       dst.Ints[4] = uintptr(src.rsi)
+       dst.Ints[5] = uintptr(src.r8)
+       dst.Ints[6] = uintptr(src.r9)
+       dst.Ints[7] = uintptr(src.r10)
+       dst.Ints[8] = uintptr(src.r11)
+       for i := range dst.Floats {
+               dst.Floats[i] = uint64(src.fpstate._xmm[i].element[0]) << 0
+               dst.Floats[i] |= uint64(src.fpstate._xmm[i].element[1]) << 32
+       }
+}
diff --git a/src/runtime/export_debug_arm64_test.go b/src/runtime/export_debug_arm64_test.go
new file mode 100644 (file)
index 0000000..ee90241
--- /dev/null
@@ -0,0 +1,135 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build arm64 && linux
+
+package runtime
+
+import (
+       "internal/abi"
+       "internal/goarch"
+       "unsafe"
+)
+
+type sigContext struct {
+       savedRegs sigcontext
+}
+
+func sigctxtSetContextRegister(ctxt *sigctxt, x uint64) {
+       ctxt.regs().regs[26] = x
+}
+
+func sigctxtAtTrapInstruction(ctxt *sigctxt) bool {
+       return *(*uint32)(unsafe.Pointer(ctxt.sigpc())) == 0xd4200000 // BRK 0
+}
+
+func sigctxtStatus(ctxt *sigctxt) uint64 {
+       return ctxt.r20()
+}
+
+func (h *debugCallHandler) saveSigContext(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       sp -= 2 * goarch.PtrSize
+       ctxt.set_sp(sp)
+       *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.lr() // save the current lr
+       ctxt.set_lr(ctxt.pc())                              // set new lr to the current pc
+       // Write the argument frame size.
+       *(*uintptr)(unsafe.Pointer(uintptr(sp - 16))) = h.argSize
+       // Save current registers.
+       h.sigCtxt.savedRegs = *ctxt.regs()
+}
+
+// case 0
+func (h *debugCallHandler) debugCallRun(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       memmove(unsafe.Pointer(uintptr(sp)+8), h.argp, h.argSize)
+       if h.regArgs != nil {
+               storeRegArgs(ctxt.regs(), h.regArgs)
+       }
+       // Push return PC, which should be the signal PC+4, because
+       // the signal PC is the PC of the trap instruction itself.
+       ctxt.set_lr(ctxt.pc() + 4)
+       // Set PC to call and context register.
+       ctxt.set_pc(uint64(h.fv.fn))
+       sigctxtSetContextRegister(ctxt, uint64(uintptr(unsafe.Pointer(h.fv))))
+}
+
+// case 1
+func (h *debugCallHandler) debugCallReturn(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       memmove(h.argp, unsafe.Pointer(uintptr(sp)+8), h.argSize)
+       if h.regArgs != nil {
+               loadRegArgs(h.regArgs, ctxt.regs())
+       }
+       // Restore the old lr from *sp
+       olr := *(*uint64)(unsafe.Pointer(uintptr(sp)))
+       ctxt.set_lr(olr)
+       pc := ctxt.pc()
+       ctxt.set_pc(pc + 4) // step to next instruction
+}
+
+// case 2
+func (h *debugCallHandler) debugCallPanicOut(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)+8), 2*goarch.PtrSize)
+       ctxt.set_pc(ctxt.pc() + 4)
+}
+
+// case 8
+func (h *debugCallHandler) debugCallUnsafe(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       reason := *(*string)(unsafe.Pointer(uintptr(sp) + 8))
+       h.err = plainError(reason)
+       ctxt.set_pc(ctxt.pc() + 4)
+}
+
+// case 16
+func (h *debugCallHandler) restoreSigContext(ctxt *sigctxt) {
+       // Restore all registers except for pc and sp
+       pc, sp := ctxt.pc(), ctxt.sp()
+       *ctxt.regs() = h.sigCtxt.savedRegs
+       ctxt.set_pc(pc + 4)
+       ctxt.set_sp(sp)
+}
+
+// storeRegArgs sets up argument registers in the signal
+// context state from an abi.RegArgs.
+//
+// Both src and dst must be non-nil.
+func storeRegArgs(dst *sigcontext, src *abi.RegArgs) {
+       for i, r := range src.Ints {
+               dst.regs[i] = uint64(r)
+       }
+       for i, r := range src.Floats {
+               *(fpRegAddr(dst, i)) = r
+       }
+}
+
+func loadRegArgs(dst *abi.RegArgs, src *sigcontext) {
+       for i := range dst.Ints {
+               dst.Ints[i] = uintptr(src.regs[i])
+       }
+       for i := range dst.Floats {
+               dst.Floats[i] = *(fpRegAddr(src, i))
+       }
+}
+
+// fpRegAddr returns the address of the ith fp-simd register in sigcontext.
+func fpRegAddr(dst *sigcontext, i int) *uint64 {
+       /* FP-SIMD registers are saved in sigcontext.__reserved, which is orgnized in
+       the following C structs:
+       struct fpsimd_context {
+               struct _aarch64_ctx head;
+               __u32 fpsr;
+               __u32 fpcr;
+               __uint128_t vregs[32];
+       };
+       struct _aarch64_ctx {
+               __u32 magic;
+               __u32 size;
+       };
+       So the offset of the ith FP_SIMD register is 16+i*128.
+       */
+       return (*uint64)(unsafe.Pointer(&dst.__reserved[16+i*128]))
+}
diff --git a/src/runtime/export_debug_regabiargs_off_test.go b/src/runtime/export_debug_regabiargs_off_test.go
deleted file mode 100644 (file)
index 81f7392..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build amd64 && linux && !goexperiment.regabiargs
-
-package runtime
-
-import "internal/abi"
-
-func storeRegArgs(dst *sigcontext, src *abi.RegArgs) {
-}
-
-func loadRegArgs(dst *abi.RegArgs, src *sigcontext) {
-}
diff --git a/src/runtime/export_debug_regabiargs_on_test.go b/src/runtime/export_debug_regabiargs_on_test.go
deleted file mode 100644 (file)
index 7d1ab68..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build amd64 && linux && goexperiment.regabiargs
-
-package runtime
-
-import "internal/abi"
-
-// storeRegArgs sets up argument registers in the signal
-// context state from an abi.RegArgs.
-//
-// Both src and dst must be non-nil.
-func storeRegArgs(dst *sigcontext, src *abi.RegArgs) {
-       dst.rax = uint64(src.Ints[0])
-       dst.rbx = uint64(src.Ints[1])
-       dst.rcx = uint64(src.Ints[2])
-       dst.rdi = uint64(src.Ints[3])
-       dst.rsi = uint64(src.Ints[4])
-       dst.r8 = uint64(src.Ints[5])
-       dst.r9 = uint64(src.Ints[6])
-       dst.r10 = uint64(src.Ints[7])
-       dst.r11 = uint64(src.Ints[8])
-       for i := range src.Floats {
-               dst.fpstate._xmm[i].element[0] = uint32(src.Floats[i] >> 0)
-               dst.fpstate._xmm[i].element[1] = uint32(src.Floats[i] >> 32)
-       }
-}
-
-func loadRegArgs(dst *abi.RegArgs, src *sigcontext) {
-       dst.Ints[0] = uintptr(src.rax)
-       dst.Ints[1] = uintptr(src.rbx)
-       dst.Ints[2] = uintptr(src.rcx)
-       dst.Ints[3] = uintptr(src.rdi)
-       dst.Ints[4] = uintptr(src.rsi)
-       dst.Ints[5] = uintptr(src.r8)
-       dst.Ints[6] = uintptr(src.r9)
-       dst.Ints[7] = uintptr(src.r10)
-       dst.Ints[8] = uintptr(src.r11)
-       for i := range dst.Floats {
-               dst.Floats[i] = uint64(src.fpstate._xmm[i].element[0]) << 0
-               dst.Floats[i] |= uint64(src.fpstate._xmm[i].element[1]) << 32
-       }
-}
index 19a9ec135fac0f8d2086f3b6b350c7049d103aa3..09e9779696b845af2c12c560d1998ffb46333730 100644 (file)
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build amd64 && linux
+//go:build (amd64 || arm64) && linux
 
 package runtime
 
 import (
        "internal/abi"
-       "internal/goarch"
        "unsafe"
 )
 
@@ -100,10 +99,9 @@ type debugCallHandler struct {
 
        handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool
 
-       err       plainError
-       done      note
-       savedRegs sigcontext
-       savedFP   fpstate1
+       err     plainError
+       done    note
+       sigCtxt sigContext
 }
 
 func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
@@ -117,18 +115,10 @@ func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
                        println("trap on wrong M", getg().m, h.mp)
                        return false
                }
-               // Push current PC on the stack.
-               rsp := ctxt.rsp() - goarch.PtrSize
-               *(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip()
-               ctxt.set_rsp(rsp)
-               // Write the argument frame size.
-               *(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize
-               // Save current registers.
-               h.savedRegs = *ctxt.regs()
-               h.savedFP = *h.savedRegs.fpstate
-               h.savedRegs.fpstate = nil
+               // Save the signal context
+               h.saveSigContext(ctxt)
                // Set PC to debugCallV2.
-               ctxt.set_rip(uint64(abi.FuncPCABIInternal(debugCallV2)))
+               ctxt.setsigpc(uint64(abi.FuncPCABIInternal(debugCallV2)))
                // Call injected. Switch to the debugCall protocol.
                testSigtrap = h.handleF
        case _Grunnable:
@@ -154,57 +144,33 @@ func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
                println("trap on wrong M", getg().m, h.mp)
                return false
        }
-       f := findfunc(uintptr(ctxt.rip()))
+       f := findfunc(ctxt.sigpc())
        if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) {
                println("trap in unknown function", funcname(f))
                return false
        }
-       if *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) != 0xcc {
-               println("trap at non-INT3 instruction pc =", hex(ctxt.rip()))
+       if !sigctxtAtTrapInstruction(ctxt) {
+               println("trap at non-INT3 instruction pc =", hex(ctxt.sigpc()))
                return false
        }
 
-       switch status := ctxt.r12(); status {
+       switch status := sigctxtStatus(ctxt); status {
        case 0:
                // 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 -= goarch.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().rdx = uint64(uintptr(unsafe.Pointer(h.fv)))
+               // Call the debug function.
+               h.debugCallRun(ctxt)
        case 1:
                // 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())
-               }
+               h.debugCallReturn(ctxt)
        case 2:
                // Function panicked. Copy panic out.
-               sp := ctxt.rsp()
-               memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)), 2*goarch.PtrSize)
+               h.debugCallPanicOut(ctxt)
        case 8:
                // Call isn't safe. Get the reason.
-               sp := ctxt.rsp()
-               reason := *(*string)(unsafe.Pointer(uintptr(sp)))
-               h.err = plainError(reason)
+               h.debugCallUnsafe(ctxt)
                // Don't wake h.done. We need to transition to status 16 first.
        case 16:
-               // Restore all registers except RIP and RSP.
-               rip, rsp := ctxt.rip(), ctxt.rsp()
-               fp := ctxt.regs().fpstate
-               *ctxt.regs() = h.savedRegs
-               ctxt.regs().fpstate = fp
-               *fp = h.savedFP
-               ctxt.set_rip(rip)
-               ctxt.set_rsp(rsp)
+               h.restoreSigContext(ctxt)
                // Done
                notewakeup(&h.done)
        default:
index 67a21950d715b37f33a0924fc5dfcb43eb1b3652..8ade2088362e6c365b3d7f2d6dfea1f0f7b9e331 100644 (file)
@@ -40,9 +40,10 @@ func dumpregs(c *sigctxt) {
 //go:nowritebarrierrec
 func (c *sigctxt) sigpc() uintptr { return uintptr(c.rip()) }
 
-func (c *sigctxt) sigsp() uintptr { return uintptr(c.rsp()) }
-func (c *sigctxt) siglr() uintptr { return 0 }
-func (c *sigctxt) fault() uintptr { return uintptr(c.sigaddr()) }
+func (c *sigctxt) setsigpc(x uint64) { c.set_rip(x) }
+func (c *sigctxt) sigsp() uintptr    { return uintptr(c.rsp()) }
+func (c *sigctxt) siglr() uintptr    { return 0 }
+func (c *sigctxt) fault() uintptr    { return uintptr(c.sigaddr()) }
 
 // preparePanic sets up the stack to look like a call to sigpanic.
 func (c *sigctxt) preparePanic(sig uint32, gp *g) {
index 771585a8f9b392e58c9dda4cd4968c2c8ebba60a..c8b87817b435ab39076cc3e2838e47e0b6c0b193 100644 (file)
@@ -53,8 +53,9 @@ func dumpregs(c *sigctxt) {
 //go:nowritebarrierrec
 func (c *sigctxt) sigpc() uintptr { return uintptr(c.pc()) }
 
-func (c *sigctxt) sigsp() uintptr { return uintptr(c.sp()) }
-func (c *sigctxt) siglr() uintptr { return uintptr(c.lr()) }
+func (c *sigctxt) setsigpc(x uint64) { c.set_pc(x) }
+func (c *sigctxt) sigsp() uintptr    { return uintptr(c.sp()) }
+func (c *sigctxt) siglr() uintptr    { return uintptr(c.lr()) }
 
 // preparePanic sets up the stack to look like a call to sigpanic.
 func (c *sigctxt) preparePanic(sig uint32, gp *g) {
index 23bce2bf34eca2b8f7b05854cd3f65d377718a33..9187d1ff13ea69db42b219090a2d4eaa08d54454 100644 (file)
@@ -470,7 +470,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
                }
 
                waspanic = f.funcID == funcID_sigpanic
-               injectedCall := waspanic || f.funcID == funcID_asyncPreempt
+               injectedCall := waspanic || f.funcID == funcID_asyncPreempt || f.funcID == funcID_debugCallV2
 
                // Do not unwind past the bottom of the stack.
                if !flr.valid() {