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:
parent
4d30cd461b
commit
ebc3e61367
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user