Skip to content

Commit

Permalink
pky/proc: enable function call injection in Delve for linux/ppc64le (#…
Browse files Browse the repository at this point in the history
…3449)

* enable func call injection on delve for ppc64le

* Function call injection on Delve/ppc64le, modified DWARF encoding and decoding for floating point registers to make floatsum test work

* Function call injection on Delve/ppc64le cleanup

* skip PIE tests for function call injection on other packages

* Address review comments

* accounted for additional skipped PIE tests for function call injection

* Code cleanup and undoing revert of previous commit

* Enable function call injection only on 1.22 and above and some cleanup

* additional cleanup, go fmt run

* Debug function call tests fail on ppc64le/PIE mode adjusted the backup_test_health.md file accordingly
  • Loading branch information
archanaravindar committed Sep 21, 2023
1 parent 4d30cd4 commit ebc3e61
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 33 deletions.
2 changes: 2 additions & 0 deletions Documentation/backend_test_health.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Tests skipped by each supported backend:
* 1 broken - cgo stacktraces
* linux/ppc64le/native skipped = 1
* 1 broken in linux ppc64le
* linux/ppc64le/native/pie skipped = 3
* 3 broken - pie mode
* pie skipped = 2
* 2 upstream issue - https://github.com/golang/go/issues/29322
* ppc64le skipped = 11
Expand Down
11 changes: 6 additions & 5 deletions pkg/dwarf/regnum/ppc64le.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ const (
PPC64LE_F0 = PPC64LE_FIRST_FPR
PPC64LE_LAST_FPR = 63
// Vector (Altivec/VMX) registers: from V0 to V31
PPC64LE_FIRST_VMX = 64
PPC64LE_FIRST_VMX = 77
PPC64LE_V0 = PPC64LE_FIRST_VMX
PPC64LE_LAST_VMX = 95
// Vector Scalar (VSX) registers: from VS0 to VS63
PPC64LE_FIRST_VSX = 96
PPC64LE_LAST_VMX = 108
// Vector Scalar (VSX) registers: from VS32 to VS63
// On ppc64le these are mapped to F0 to F31
PPC64LE_FIRST_VSX = 32
PPC64LE_VS0 = PPC64LE_FIRST_VSX
PPC64LE_LAST_VSX = 160
PPC64LE_LAST_VSX = 63
// Condition Registers: from CR0 to CR7
PPC64LE_CR0 = 0
// Special registers
Expand Down
38 changes: 29 additions & 9 deletions pkg/proc/fncall.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,16 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
return nil, err
}
case "arm64":
case "arm64", "ppc64le":
// debugCallV2 on arm64 needs a special call sequence, callOP can not be used
sp := regs.SP()
sp -= 2 * uint64(bi.Arch.PtrSize())
var spOffset uint64
if bi.Arch.Name == "arm64" {
spOffset = 2 * uint64(bi.Arch.PtrSize())
} else {
spOffset = 4 * uint64(bi.Arch.PtrSize())
}
sp -= spOffset
if err := setSP(thread, sp); err != nil {
return nil, err
}
Expand All @@ -349,7 +355,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if err := setLR(thread, regs.PC()); err != nil {
return nil, err
}
if err := writePointer(bi, scope.Mem, sp-uint64(2*bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
if err := writePointer(bi, scope.Mem, sp-spOffset, uint64(fncall.argFrameSize)); err != nil {
return nil, err
}
regs, err = thread.Registers()
Expand All @@ -365,6 +371,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if err != nil {
return nil, err
}

}

fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID())
Expand Down Expand Up @@ -483,11 +490,12 @@ func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) erro
return err
}
return setPC(thread, callAddr)
case "arm64":
case "arm64", "ppc64le":
if err := setLR(thread, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)

default:
panic("not implemented")
}
Expand Down Expand Up @@ -864,6 +872,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
} else if bi.Arch.Name == "ppc64le" {
archoff = 40
}
// get error from top of the stack and return it to user
errvar, err := readStackVariable(p, thread, regs, archoff, "string", loadFullValue)
Expand Down Expand Up @@ -912,7 +922,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
cfa := regs.SP()
oldpc := regs.PC()
var oldlr uint64
if bi.Arch.Name == "arm64" {
if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
oldlr = regs.LR()
}
callOP(bi, thread, regs, fncall.fn.Entry)
Expand All @@ -925,7 +935,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
case "amd64":
setSP(thread, cfa)
setPC(thread, oldpc)
case "arm64":
case "arm64", "ppc64le":
setLR(thread, oldlr)
setPC(thread, oldpc)
default:
Expand Down Expand Up @@ -1006,7 +1016,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
if threadg, _ := GetG(thread); threadg != nil {
callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
}
if bi.Arch.Name == "arm64" {
if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
if err != nil {
fncall.err = fmt.Errorf("could not restore LR: %v", err)
Expand All @@ -1023,6 +1033,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
} else if bi.Arch.Name == "ppc64le" {
archoff = 32
}
fncall.panicvar, err = readStackVariable(p, thread, regs, archoff, "interface {}", callScope.callCtx.retLoadCfg)
if err != nil {
Expand Down Expand Up @@ -1068,7 +1080,6 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
scope.PC = fn.Entry
scope.Fn = fn
scope.File, scope.Line = scope.BinInfo.EntryLineForFunc(fn)

scope.Regs.CFA = cfa
scope.Regs.Reg(scope.Regs.SPRegNum).Uint64Val = sp
scope.Regs.Reg(scope.Regs.PCRegNum).Uint64Val = fn.Entry
Expand Down Expand Up @@ -1260,7 +1271,7 @@ func debugCallProtocolReg(archName string, version int) (uint64, bool) {
return 0, false
}
return protocolReg, true
case "arm64":
case "arm64", "ppc64le":
if version == 2 {
return regnum.ARM64_X0 + 20, true
}
Expand Down Expand Up @@ -1335,6 +1346,15 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
}
return r, err1
case "ppc64le":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.PPC64LE_R0+3, false),
m("typ", t(ptrToRuntimeType), regnum.PPC64LE_R0+4, false),
m("needzero", t("bool"), regnum.PPC64LE_R0+5, false),
m("~r1", t("unsafe.Pointer"), regnum.PPC64LE_R0+3, true),
}
return r, err1

default:
// do nothing
return nil, nil
Expand Down
44 changes: 42 additions & 2 deletions pkg/proc/linutil/regs_ppc64le_arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package linutil
import (
"fmt"

"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/regnum"
"github.com/go-delve/delve/pkg/proc"
)

Expand Down Expand Up @@ -162,13 +164,51 @@ func (r *PPC64LERegisters) Copy() (proc.Registers, error) {
return &rr, nil
}

func (r *PPC64LERegisters) SetReg(regNum uint64, reg *op.DwarfRegister) (fpchanged bool, err error) {
switch regNum {
case regnum.PPC64LE_PC:
r.Regs.Nip = reg.Uint64Val
return false, nil
case regnum.PPC64LE_LR:
r.Regs.Link = reg.Uint64Val
return false, nil
case regnum.PPC64LE_SP:
r.Regs.Gpr[1] = reg.Uint64Val
return false, nil
default:
switch {
case regNum >= regnum.PPC64LE_R0 && regNum <= regnum.PPC64LE_R0+31:
r.Regs.Gpr[regNum-regnum.PPC64LE_R0] = reg.Uint64Val
return false, nil

case regNum >= regnum.PPC64LE_F0 && regNum <= regnum.PPC64LE_F0+31:
if r.loadFpRegs != nil {
err := r.loadFpRegs(r)
r.loadFpRegs = nil
if err != nil {
return false, err
}
}
// On ppc64le, PPC64LE_VS0 .. PPC64LE_VS31 are mapped onto
// PPC64LE_F0 .. PPC64LE_F31
i := regNum - regnum.PPC64LE_VS0
reg.FillBytes()
copy(r.Fpregset[8*i:], reg.Bytes)
return true, nil

default:
return false, fmt.Errorf("changing register %d not implemented", regNum)
}
}
}

type PPC64LEPtraceFpRegs struct {
Fp []byte
}

func (fpregs *PPC64LEPtraceFpRegs) Decode() (regs []proc.Register) {
for i := 0; i < len(fpregs.Fp); i += 16 {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("V%d", i/16), fpregs.Fp[i:i+16])
for i := 0; i < len(fpregs.Fp); i += 8 {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("VS%d", i/8), fpregs.Fp[i:i+8])
}
return
}
32 changes: 18 additions & 14 deletions pkg/proc/native/registers_linux_ppc64le.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"unsafe"

"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/regnum"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
sys "golang.org/x/sys/unix"
Expand Down Expand Up @@ -63,25 +62,30 @@ func (t *nativeThread) setPC(pc uint64) error {
}

// SetReg changes the value of the specified register.
func (t *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
ir, err := registers(t)
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
ir, err := registers(thread)
if err != nil {
return err
}
r := ir.(*linutil.PPC64LERegisters)

switch regNum {
case regnum.PPC64LE_PC:
r.Regs.Nip = reg.Uint64Val
case regnum.PPC64LE_SP:
r.Regs.Gpr[1] = reg.Uint64Val
case regnum.PPC64LE_LR:
r.Regs.Link = reg.Uint64Val
default:
panic("SetReg")
fpchanged, err := r.SetReg(regNum, reg)
if err != nil {
return err
}
thread.dbp.execPtraceFunc(func() {
err = ptraceSetGRegs(thread.ID, r.Regs)
if err != syscall.Errno(0) && err != nil {
return
}
if fpchanged && r.Fpregset != nil {
iov := sys.Iovec{Base: &r.Fpregset[0], Len: uint64(len(r.Fpregset))}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(thread.ID), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0)
}
})
if err == syscall.Errno(0) {
err = nil
}

t.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(t.ID, r.Regs) })
return err
}

Expand Down
25 changes: 24 additions & 1 deletion pkg/proc/native/threads_linux_ppc64le.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package native

import (
"fmt"
"debug/elf"
"syscall"
"unsafe"

sys "golang.org/x/sys/unix"


"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
Expand All @@ -21,5 +27,22 @@ func (t *nativeThread) fpRegisters() ([]proc.Register, []byte, error) {
}

func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
panic("Unimplemented restoreRegisters method in threads_linux_ppc64le.go")
sr := savedRegs.(*linutil.PPC64LERegisters)

var restoreRegistersErr error
t.dbp.execPtraceFunc(func() {
restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
if restoreRegistersErr != syscall.Errno(0) && restoreRegistersErr != nil {
return
}
if sr.Fpregset != nil {
iov := sys.Iovec{Base: &sr.Fpregset[0], Len: _PPC64LE_FPREGS_SIZE}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0)
}
})
if restoreRegistersErr == syscall.Errno(0) {
restoreRegistersErr = nil
}
return restoreRegistersErr
}

3 changes: 3 additions & 0 deletions pkg/proc/ppc64le_arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ func PPC64LEArch(goos string) *Arch {
usesLR: true,
PCRegNum: regnum.PPC64LE_PC,
SPRegNum: regnum.PPC64LE_SP,
ContextRegNum: regnum.PPC64LE_R0 + 11,
LRRegNum: regnum.PPC64LE_LR,
asmRegisters: ppc64leAsmRegisters,
RegisterNameToDwarf: nameToDwarfFunc(regnum.PPC64LENameToDwarf),
debugCallMinStackSize: 320,
maxRegArgBytes: 13*8 + 13*8,
}
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4312,6 +4312,8 @@ func TestReadDeferArgs(t *testing.T) {

func TestIssue1374(t *testing.T) {
// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")

protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("issue1374", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture.Source, 7)
Expand Down Expand Up @@ -4537,6 +4539,8 @@ func testCallConcurrentCheckReturns(p *proc.Target, t *testing.T, gid1, gid2 int
}

func TestCallConcurrent(t *testing.T) {
skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")

protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 24)
Expand Down Expand Up @@ -4932,6 +4936,7 @@ func TestIssue1925(t *testing.T) {
// In particular the stepInstructionOut function called at the end of a
// 'call' procedure should clean the G cache like every other function
// altering the state of the target process.
skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
assertNoError(grp.Continue(), t, "Continue()")
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func (t *Target) Valid() (bool, error) {
// Currently only non-recorded processes running on AMD64 support
// function calls.
func (t *Target) SupportsFunctionCalls() bool {
return t.Process.BinInfo().Arch.Name == "amd64" || (t.Process.BinInfo().Arch.Name == "arm64" && t.Process.BinInfo().GOOS != "windows")
return t.Process.BinInfo().Arch.Name == "amd64" || (t.Process.BinInfo().Arch.Name == "arm64" && t.Process.BinInfo().GOOS != "windows") || t.Process.BinInfo().Arch.Name == "ppc64le"
}

// ClearCaches clears internal caches that should not survive a restart.
Expand Down
8 changes: 7 additions & 1 deletion pkg/proc/test/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,20 @@ func MustSupportFunctionCalls(t *testing.T, testBackend string) {
if runtime.GOOS == "darwin" && os.Getenv("TRAVIS") == "true" && runtime.GOARCH == "amd64" {
t.Skip("function call injection tests are failing on macOS on Travis-CI (see #1802)")
}
if runtime.GOARCH == "386" || runtime.GOARCH == "ppc64le" {
if runtime.GOARCH == "386" {
t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
}
if runtime.GOARCH == "arm64" {
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 19) || runtime.GOOS == "windows" {
t.Skip("this version of Go does not support function calls")
}
}

if runtime.GOARCH == "ppc64le" {
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 22) {
t.Skip("On PPC64LE Building with Go lesser than 1.22 does not support function calls")
}
}
}

// DefaultTestBackend changes the value of testBackend to be the default
Expand Down
3 changes: 3 additions & 0 deletions pkg/proc/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,8 @@ type testCaseCallFunction struct {
}

func TestCallFunction(t *testing.T) {
skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")

protest.MustSupportFunctionCalls(t, testBackend)
protest.AllowRecording(t)

Expand Down Expand Up @@ -1289,6 +1291,7 @@ func TestCallFunction(t *testing.T) {
testCallFunctionSetBreakpoint(t, p, grp, fixture)

assertNoError(grp.Continue(), t, "Continue()")

for _, tc := range testcases {
testCallFunction(t, grp, p, tc)
}
Expand Down
Loading

0 comments on commit ebc3e61

Please sign in to comment.