]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: support for debugger function calls on linux/ppc64le
authorArchana <aravinda@redhat.com>
Mon, 24 Jul 2023 14:47:02 +0000 (20:17 +0530)
committerLynn Boger <laboger@linux.vnet.ibm.com>
Fri, 8 Sep 2023 15:08:04 +0000 (15:08 +0000)
This CL adds support for debugger function calls on linux ppc64le
platform. The protocol is basically the same as in CL 395754, except for
the following differences:
1, The abi differences which affect parameter passing and frame layout.
2, The closure register is R11.
3, Minimum framesize on pp64le is 32 bytes
4, Added functions to return parent context structure for general purpose
   registers in order to work with the way these structures are defined in
   ppc64le

Change-Id: I58e01fedad66a818ab322e2b2d8f5104cfa64f39
Reviewed-on: https://go-review.googlesource.com/c/go/+/512575
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Lynn Boger <laboger@linux.vnet.ibm.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Archana Ravindar <aravinda@redhat.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/runtime/asm_ppc64x.s
src/runtime/debug_test.go
src/runtime/debugcall.go
src/runtime/export_debug_ppc64le_test.go [new file with mode: 0644]
src/runtime/export_debug_test.go
src/runtime/signal_linux_ppc64x.go
src/runtime/signal_ppc64x.go

index 8d8b576d3d717a8a7f3952ed4834ee3836758a39..922c50dc4f0d375f796c838138e617c7f9ce73d0 100644 (file)
@@ -98,7 +98,11 @@ nocgo:
 
        // start this M
        BL      runtime·mstart(SB)
-
+       // Prevent dead-code elimination of debugCallV2, which is
+       // intended to be called by debuggers.
+#ifdef GOARCH_ppc64le
+       MOVD    $runtime·debugCallV2<ABIInternal>(SB), R31
+#endif
        MOVD    R0, 0(R0)
        RET
 
@@ -1110,6 +1114,219 @@ TEXT runtime·gcWriteBarrier8<ABIInternal>(SB),NOSPLIT,$0
        MOVD    $64, R29
        JMP     gcWriteBarrier<>(SB)
 
+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 320 bytes free on the stack.
+// 2. Set SP as SP-32.
+// 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-32
+// 6. Save all machine registers (including flags and floating point registers)
+//    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 TW to raise a breakpoint signal. Note that the signal PC of
+// the signal triggered by the TW instruction is the PC where the signal
+// is trapped, not the next PC, so to resume execution, the debugger needs
+// to set the signal PC to PC+4. 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.
+#ifdef GOARCH_ppc64le
+TEXT runtime·debugCallV2<ABIInternal>(SB), NOSPLIT|NOFRAME, $0-0
+       // save scratch register R31 first
+       MOVD    R31, -184(R1)
+       MOVD    0(R1), R31
+       // save caller LR
+       MOVD    R31, -304(R1)
+       MOVD    -32(R1), R31
+       // save argument frame size
+       MOVD    R31, -192(R1)
+       MOVD    LR, R31
+       MOVD    R31, -320(R1)
+       ADD     $-320, R1
+       // save all registers that can contain pointers
+       // and the CR register
+       MOVW    CR, R31
+       MOVD    R31, 8(R1)
+       MOVD    R2, 24(R1)
+       MOVD    R3, 56(R1)
+       MOVD    R4, 64(R1)
+       MOVD    R5, 72(R1)
+       MOVD    R6, 80(R1)
+       MOVD    R7, 88(R1)
+       MOVD    R8, 96(R1)
+       MOVD    R9, 104(R1)
+       MOVD    R10, 112(R1)
+       MOVD    R11, 120(R1)
+       MOVD    R12, 144(R1)
+       MOVD    R13, 152(R1)
+       MOVD    R14, 160(R1)
+       MOVD    R15, 168(R1)
+       MOVD    R16, 176(R1)
+       MOVD    R17, 184(R1)
+       MOVD    R18, 192(R1)
+       MOVD    R19, 200(R1)
+       MOVD    R20, 208(R1)
+       MOVD    R21, 216(R1)
+       MOVD    R22, 224(R1)
+       MOVD    R23, 232(R1)
+       MOVD    R24, 240(R1)
+       MOVD    R25, 248(R1)
+       MOVD    R26, 256(R1)
+       MOVD    R27, 264(R1)
+       MOVD    R28, 272(R1)
+       MOVD    R29, 280(R1)
+       MOVD    g, 288(R1)
+       MOVD    LR, R31
+       MOVD    R31, 32(R1)
+       CALL    runtime·debugCallCheck(SB)
+       MOVD    40(R1), R22
+       XOR     R0, R0
+       CMP     R22, R0
+       BEQ     good
+       MOVD    48(R1), R22
+       MOVD    $8, R20
+       TW      $31, R0, R0
+
+       BR      restore
+
+good:
+#define DEBUG_CALL_DISPATCH(NAME,MAXSIZE)      \
+       MOVD    $MAXSIZE, R23;                  \
+       CMP     R26, R23;                       \
+       BGT     5(PC);                          \
+       MOVD    $NAME(SB), R26;                 \
+       MOVD    R26, 32(R1);                    \
+       CALL    runtime·debugCallWrap(SB);     \
+       BR      restore
+
+       // the argument frame size
+       MOVD    128(R1), R26
+
+       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), R22
+       MOVD    R22, 32(R1)
+       MOVD    $20, R22
+       // length of debugCallFrameTooLarge string
+       MOVD    R22, 40(R1)
+       MOVD    $8, R20
+       TW      $31, R0, R0
+       BR      restore
+restore:
+       MOVD    $16, R20
+       TW      $31, R0, R0
+       // restore all registers that can contain
+       // pointers including CR
+       MOVD    8(R1), R31
+       MOVW    R31, CR
+       MOVD    24(R1), R2
+       MOVD    56(R1), R3
+       MOVD    64(R1), R4
+       MOVD    72(R1), R5
+       MOVD    80(R1), R6
+       MOVD    88(R1), R7
+       MOVD    96(R1), R8
+       MOVD    104(R1), R9
+       MOVD    112(R1), R10
+       MOVD    120(R1), R11
+       MOVD    144(R1), R12
+       MOVD    152(R1), R13
+       MOVD    160(R1), R14
+       MOVD    168(R1), R15
+       MOVD    176(R1), R16
+       MOVD    184(R1), R17
+       MOVD    192(R1), R18
+       MOVD    200(R1), R19
+       MOVD    208(R1), R20
+       MOVD    216(R1), R21
+       MOVD    224(R1), R22
+       MOVD    232(R1), R23
+       MOVD    240(R1), R24
+       MOVD    248(R1), R25
+       MOVD    256(R1), R26
+       MOVD    264(R1), R27
+       MOVD    272(R1), R28
+       MOVD    280(R1), R29
+       MOVD    288(R1), g
+       MOVD    16(R1), R31
+       // restore old LR
+       MOVD    R31, LR
+       // restore caller PC
+       MOVD    0(R1), CTR
+       MOVD    136(R1), R31
+       // Add 32 bytes more to compensate for SP change in saveSigContext
+       ADD     $352, R1
+       JMP     (CTR)
+#endif
+#define DEBUG_CALL_FN(NAME,MAXSIZE)    \
+TEXT NAME(SB),WRAPPER,$MAXSIZE-0;      \
+       NO_LOCAL_POINTERS;              \
+       MOVD    $0, R20;                \
+       TW      $31, R0, R0             \
+       MOVD    $1, R20;                \
+       TW      $31, R0, R0             \
+       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)
+
+#ifdef GOARCH_ppc64le
+// func debugCallPanicked(val interface{})
+TEXT runtime·debugCallPanicked(SB),NOSPLIT,$32-16
+       // Copy the panic value to the top of stack at SP+32.
+       MOVD    val_type+0(FP), R31
+       MOVD    R31, 32(R1)
+       MOVD    val_data+8(FP), R31
+       MOVD    R31, 40(R1)
+       MOVD    $2, R20
+       TW      $31, R0, R0
+       RET
+#endif
 // 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 75fe07ec2a2df87e3d6857ff920911440c708948..1c00d2fb0d918b07fc44ed98996c9aa6f9ca3f41 100644 (file)
@@ -9,7 +9,7 @@
 // spends all of its time in the race runtime, which isn't a safe
 // point.
 
-//go:build (amd64 || arm64) && linux && !race
+//go:build (amd64 || arm64 || ppc64le) && linux && !race
 
 package runtime_test
 
index ee984074ce04d3daf55f58f7b1e7923404bc39f7..98ab413ff49cc84b008cd02241248a122d29a6f0 100644 (file)
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build amd64 || arm64
+// Though the debug call function feature is not enabled on
+// ppc64, inserted ppc64 to avoid missing Go declaration error
+// for debugCallPanicked while building runtime.test
+//go:build amd64 || arm64 || ppc64le || ppc64
 
 package runtime
 
diff --git a/src/runtime/export_debug_ppc64le_test.go b/src/runtime/export_debug_ppc64le_test.go
new file mode 100644 (file)
index 0000000..b20cf6c
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright 2023 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 ppc64le && linux
+
+package runtime
+
+import (
+       "internal/abi"
+       "internal/goarch"
+       "math"
+       "unsafe"
+)
+
+type sigContext struct {
+       savedRegs sigcontext
+}
+
+func sigctxtSetContextRegister(ctxt *sigctxt, x uint64) {
+       ctxt.regs().gpr[11] = x
+}
+
+func sigctxtAtTrapInstruction(ctxt *sigctxt) bool {
+       return *(*uint32)(unsafe.Pointer(ctxt.sigpc())) == 0x7fe00008 // Trap
+}
+
+func sigctxtStatus(ctxt *sigctxt) uint64 {
+       return ctxt.r20()
+}
+
+func (h *debugCallHandler) saveSigContext(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       sp -= 4 * goarch.PtrSize
+       ctxt.set_sp(sp)
+       *(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.link() // save the current lr
+       ctxt.set_link(ctxt.pc())                              // set new lr to the current pc
+       // Write the argument frame size.
+       *(*uintptr)(unsafe.Pointer(uintptr(sp - 32))) = h.argSize
+       // Save current registers.
+       h.sigCtxt.savedRegs = *ctxt.cregs()
+}
+
+// case 0
+func (h *debugCallHandler) debugCallRun(ctxt *sigctxt) {
+       sp := ctxt.sp()
+       memmove(unsafe.Pointer(uintptr(sp)+32), h.argp, h.argSize)
+       if h.regArgs != nil {
+               storeRegArgs(ctxt.cregs(), 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_link(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)+32), h.argSize)
+       if h.regArgs != nil {
+               loadRegArgs(h.regArgs, ctxt.cregs())
+       }
+       // Restore the old lr from *sp
+       olr := *(*uint64)(unsafe.Pointer(uintptr(sp)))
+       ctxt.set_link(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)+32), 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) + 40))
+       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.cregs() = 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) {
+       // Gprs R3..R10, R14..R17 are used to pass int arguments in registers on PPC64
+       for i := 0; i < 12; i++ {
+               if i > 7 {
+                       dst.gp_regs[i+6] = uint64(src.Ints[i])
+               } else {
+                       dst.gp_regs[i+3] = uint64(src.Ints[i])
+               }
+       }
+       // Fprs F1..F13 are used to pass float arguments in registers on PPC64
+       for i := 0; i < 12; i++ {
+               dst.fp_regs[i+1] = math.Float64frombits(src.Floats[i])
+       }
+
+}
+
+func loadRegArgs(dst *abi.RegArgs, src *sigcontext) {
+       // Gprs R3..R10, R14..R17 are used to pass int arguments in registers on PPC64
+       for i, _ := range [12]int{} {
+               if i > 7 {
+                       dst.Ints[i] = uintptr(src.gp_regs[i+6])
+               } else {
+                       dst.Ints[i] = uintptr(src.gp_regs[i+3])
+               }
+       }
+       // Fprs F1..F13 are used to pass float arguments in registers on PPC64
+       for i, _ := range [12]int{} {
+               dst.Floats[i] = math.Float64bits(src.fp_regs[i+1])
+       }
+
+}
index 76dc206d065e049ad23d85f10379f6bf022ea742..7ee73ef07ca0e6090ae8471bcdc93bd92cfb52f3 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 || arm64) && linux
+//go:build (amd64 || arm64 || ppc64le) && linux
 
 package runtime
 
index 31754289ece3674ec3d0dfd6c5ae3ec1d775a102..95a034490816ac5fc0f6bde06530208f9e92342c 100644 (file)
@@ -18,7 +18,8 @@ type sigctxt struct {
 
 //go:nosplit
 //go:nowritebarrierrec
-func (c *sigctxt) regs() *ptregs { return (*ucontext)(c.ctxt).uc_mcontext.regs }
+func (c *sigctxt) regs() *ptregs      { return (*ucontext)(c.ctxt).uc_mcontext.regs }
+func (c *sigctxt) cregs() *sigcontext { return &(*ucontext)(c.ctxt).uc_mcontext }
 
 func (c *sigctxt) r0() uint64  { return c.regs().gpr[0] }
 func (c *sigctxt) r1() uint64  { return c.regs().gpr[1] }
index 930c468010b4922ce79859139cf8c3ad6acd9cca..b5722f99c66a227083aa83e3d58c4765bd5b2b37 100644 (file)
@@ -55,7 +55,8 @@ func dumpregs(c *sigctxt) {
 
 //go:nosplit
 //go:nowritebarrierrec
-func (c *sigctxt) sigpc() uintptr { return uintptr(c.pc()) }
+func (c *sigctxt) sigpc() uintptr    { return uintptr(c.pc()) }
+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.link()) }