pky/proc: enable function call injection in Delve for linux/ppc64le (#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
This commit is contained in:
Archana Ravindar 2023-09-21 23:09:57 +05:30 committed by GitHub
parent 4d30cd461b
commit ebc3e61367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 150 additions and 33 deletions

@ -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

@ -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

@ -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
}
@ -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()
@ -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())
@ -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")
}
@ -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)
@ -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)
@ -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:
@ -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)
@ -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 {
@ -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
@ -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
}
@ -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

@ -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"
)
@ -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
}

@ -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"
@ -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
}

@ -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"
@ -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
}

@ -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,
}
}

@ -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)
@ -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)
@ -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()")

@ -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.

@ -310,7 +310,7 @@ 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" {
@ -318,6 +318,12 @@ func MustSupportFunctionCalls(t *testing.T, testBackend string) {
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

@ -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)
@ -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)
}

@ -996,6 +996,9 @@ func findStarFile(name string) string {
}
func TestIssue1598(t *testing.T) {
if buildMode == "pie" && runtime.GOARCH == "ppc64le" {
t.Skip("Debug function call Test broken in PIE mode")
}
test.MustSupportFunctionCalls(t, testBackend)
withTestTerminal("issue1598", t, func(term *FakeTerminal) {
term.MustExec("break issue1598.go:5")

@ -2109,6 +2109,10 @@ func TestForceStopWhileContinue(t *testing.T) {
}
func TestClientServerFunctionCall(t *testing.T) {
if buildMode == "pie" && runtime.GOARCH == "ppc64le" {
t.Skip("Debug function call Test broken in PIE mode")
}
protest.MustSupportFunctionCalls(t, testBackend)
withTestClient2("fncall", t, func(c service.Client) {
c.SetReturnValuesLoadConfig(&normalLoadConfig)
@ -2139,6 +2143,9 @@ func TestClientServerFunctionCall(t *testing.T) {
}
func TestClientServerFunctionCallPanic(t *testing.T) {
if buildMode == "pie" && runtime.GOARCH == "ppc64le" {
t.Skip("Debug function call Test broken in PIE mode")
}
protest.MustSupportFunctionCalls(t, testBackend)
withTestClient2("fncall", t, func(c service.Client) {
c.SetReturnValuesLoadConfig(&normalLoadConfig)