proc: support function call injection on arm64 (#2996)

* _scripts/test_linux.sh,_scripts/test_windows.ps1: always return exit code 0 when testing on tip

Same as what we do for test_mac.sh

* proc: support function call injection on arm64

Support function call injection on arm64 with go1.19
This commit is contained in:
Alessandro Arzilli
2022-05-03 19:46:24 +02:00
committed by GitHub
parent d513b6da45
commit c9d800edb9
21 changed files with 283 additions and 70 deletions

View File

@ -176,6 +176,18 @@ func regabistacktest2(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10 int) (int, int, in
return n1 + n2, n2 + n3, n3 + n4, n4 + n5, n5 + n6, n6 + n7, n7 + n8, n8 + n9, n9 + n10, n10 + n1
}
func regabistacktest3(sargs [10]string, n uint8) (r [10]string, m uint8) {
for i := range sargs {
r[i] = sargs[i] + sargs[(i+1)%len(sargs)]
}
m = n * 3
return
}
func floatsum(a, b float64) float64 {
return a + b
}
type Issue2698 struct {
a uint32
b uint8
@ -198,6 +210,7 @@ func main() {
var pa2 *astruct
var str string = "old string value"
longstrs := []string{"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"}
rast3 := [10]string{"one", "two", "three", "four", "five", "six", "seven", "height", "nine", "ten"}
var vable_a VRcvrable = a
var vable_pa VRcvrable = pa
var pable_pa PRcvrable = pa
@ -225,5 +238,5 @@ func main() {
d.Method()
d.Base.Method()
x.CallMe()
fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String())
fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String(), regabistacktest3, rast3, floatsum)
}

View File

@ -40,6 +40,8 @@ func AMD64Arch(goos string) *Arch {
ContextRegNum: regnum.AMD64_Rdx,
asmRegisters: amd64AsmRegisters,
RegisterNameToDwarf: nameToDwarfFunc(regnum.AMD64NameToDwarf),
debugCallMinStackSize: 256,
maxRegArgBytes: 9*8 + 15*8,
}
}

View File

@ -24,6 +24,7 @@ type Arch struct {
SPRegNum uint64
BPRegNum uint64
ContextRegNum uint64 // register used to pass a closure context when calling a function pointer
LRRegNum uint64
// asmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
// It assumes that the Loc and AtPC fields of asmInst have already been filled.
@ -48,6 +49,11 @@ type Arch struct {
// inhibitStepInto returns whether StepBreakpoint can be set at pc.
inhibitStepInto func(bi *BinaryInfo, pc uint64) bool
RegisterNameToDwarf func(s string) (int, bool)
// debugCallMinStackSize is the minimum stack size for call injection on this architecture.
debugCallMinStackSize uint64
// maxRegArgBytes is extra padding for ABI1 call injections, equivalent to
// the maximum space occupied by register arguments.
maxRegArgBytes int
// asmRegisters maps assembly register numbers to dwarf registers.
asmRegisters map[int]asmRegister

View File

@ -35,8 +35,12 @@ func ARM64Arch(goos string) *Arch {
usesLR: true,
PCRegNum: regnum.ARM64_PC,
SPRegNum: regnum.ARM64_SP,
ContextRegNum: regnum.ARM64_X0 + 26,
LRRegNum: regnum.ARM64_LR,
asmRegisters: arm64AsmRegisters,
RegisterNameToDwarf: nameToDwarfFunc(regnum.ARM64NameToDwarf),
debugCallMinStackSize: 288,
maxRegArgBytes: 16*8 + 16*8, // 16 int argument registers plus 16 float argument registers
}
}

View File

@ -150,6 +150,7 @@ func (regs *delveRegisters) BP() uint64 { return regs.bp }
func (regs *delveRegisters) SP() uint64 { return regs.sp }
func (regs *delveRegisters) TLS() uint64 { return regs.tls }
func (regs *delveRegisters) GAddr() (uint64, bool) { return regs.gaddr, regs.hasGAddr }
func (regs *delveRegisters) LR() uint64 { return 0 }
func (regs *delveRegisters) Copy() (proc.Registers, error) {
return regs, nil

View File

@ -141,6 +141,10 @@ func (r *AMD64Registers) BP() uint64 {
return uint64(r.Regs.Rbp)
}
func (r *AMD64Registers) LR() uint64 {
return 0
}
// TLS returns the address of the thread local storage memory segment.
func (r *AMD64Registers) TLS() uint64 {
return r.Fsbase

View File

@ -44,10 +44,6 @@ const (
debugCallFunctionNamePrefix2 = "runtime.debugCall"
maxDebugCallVersion = 2
maxArgFrameSize = 65535
// maxRegArgBytes is extra padding for ABI1 call injections, equivalent to
// the maximum space occupied by register arguments.
maxRegArgBytes = 9*8 + 15*8 // TODO: Make this generic for other platforms.
)
var (
@ -303,10 +299,10 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if err != nil {
return nil, err
}
if regs.SP()-256 <= stacklo {
if regs.SP()-bi.Arch.debugCallMinStackSize <= stacklo {
return nil, errNotEnoughStack
}
protocolReg, ok := debugCallProtocolReg(dbgcallversion)
protocolReg, ok := debugCallProtocolReg(bi.Arch.Name, dbgcallversion)
if !ok {
return nil, errFuncCallUnsupported
}
@ -324,12 +320,44 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
return nil, err
}
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
return nil, err
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
return nil, err
switch bi.Arch.Name {
case "amd64":
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
return nil, err
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
return nil, err
}
case "arm64":
// debugCallV2 on arm64 needs a special call sequence, callOP can not be used
sp := regs.SP()
sp -= 2 * uint64(bi.Arch.PtrSize())
if err := setSP(thread, sp); err != nil {
return nil, err
}
if err := writePointer(bi, scope.Mem, sp, regs.LR()); err != nil {
return nil, err
}
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 {
return nil, err
}
regs, err = thread.Registers()
if err != nil {
return nil, err
}
regs, err = regs.Copy()
if err != nil {
return nil, err
}
fncall.savedRegs = regs
err = setPC(thread, dbgcallfn.Entry)
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())
@ -436,16 +464,26 @@ func writePointer(bi *BinaryInfo, mem MemoryReadWriter, addr, val uint64) error
// * changes the value of PC to callAddr
// Note: regs are NOT updated!
func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) error {
sp := regs.SP()
// push PC on the stack
sp -= uint64(bi.Arch.PtrSize())
if err := setSP(thread, sp); err != nil {
return err
switch bi.Arch.Name {
case "amd64":
sp := regs.SP()
// push PC on the stack
sp -= uint64(bi.Arch.PtrSize())
if err := setSP(thread, sp); err != nil {
return err
}
if err := writePointer(bi, thread.ProcessMemory(), sp, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)
case "arm64":
if err := setLR(thread, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)
default:
panic("not implemented")
}
if err := writePointer(bi, thread.ProcessMemory(), sp, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)
}
// funcCallEvalFuncExpr evaluates expr.Fun and returns the function that we're trying to call.
@ -658,7 +696,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
// See: https://github.com/go-delve/delve/pull/2451#discussion_r665761531
// TODO: Make this generic for other platforms.
argFrameSize = alignAddr(argFrameSize, 8)
argFrameSize += maxRegArgBytes
argFrameSize += int64(bi.Arch.maxRegArgBytes)
}
sort.Slice(formalArgs, func(i, j int) bool {
@ -807,9 +845,13 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
}
switch regval {
case debugCallRegPrecheckFailed:
case debugCallRegPrecheckFailed: // 8
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
}
// get error from top of the stack and return it to user
errvar, err := readTopstackVariable(p, thread, regs, "string", loadFullValue)
errvar, err := readStackVariable(p, thread, regs, archoff, "string", loadFullValue)
if err != nil {
fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
break
@ -817,8 +859,9 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
errvar.Name = "err"
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
case debugCallRegCompleteCall:
case debugCallRegCompleteCall: // 0
p.fncallForG[callScope.g.ID].startThreadID = 0
// evaluate arguments of the target function, copy them into its argument frame and call the function
if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
// if we couldn't figure out which function we are calling before
@ -849,6 +892,10 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
}
cfa := regs.SP()
oldpc := regs.PC()
var oldlr uint64
if bi.Arch.Name == "arm64" {
oldlr = regs.LR()
}
callOP(bi, thread, regs, fncall.fn.Entry)
formalScope, err := GoroutineScope(callScope.target, thread)
if formalScope != nil && formalScope.Regs.CFA != int64(cfa) {
@ -861,14 +908,22 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
if err != nil {
// rolling back the call, note: this works because we called regs.Copy() above
setSP(thread, cfa)
setPC(thread, oldpc)
switch bi.Arch.Name {
case "amd64":
setSP(thread, cfa)
setPC(thread, oldpc)
case "arm64":
setLR(thread, oldlr)
setPC(thread, oldpc)
default:
panic("not implemented")
}
fncall.err = err
fncall.lateCallFailure = true
break
}
case debugCallRegRestoreRegisters:
case debugCallRegRestoreRegisters: // 16
// runtime requests that we restore the registers (all except pc and sp),
// this is also the last step of the function call protocol.
pc, sp := regs.PC(), regs.SP()
@ -886,7 +941,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
}
return true
case debugCallRegReadReturn:
case debugCallRegReadReturn: // 1
// read return arguments from stack
if fncall.panicvar != nil || fncall.lateCallFailure {
break
@ -925,10 +980,25 @@ 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" {
oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
if err != nil {
fncall.err = fmt.Errorf("could not restore LR: %v", err)
break
}
if err = setLR(thread, oldlr); err != nil {
fncall.err = fmt.Errorf("could not restore LR: %v", err)
break
}
}
case debugCallRegReadPanic:
case debugCallRegReadPanic: // 2
// read panic value from stack
fncall.panicvar, err = readTopstackVariable(p, thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
}
fncall.panicvar, err = readStackVariable(p, thread, regs, archoff, "interface {}", callScope.callCtx.retLoadCfg)
if err != nil {
fncall.err = fmt.Errorf("could not get panic: %v", err)
break
@ -944,7 +1014,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
return false
}
func readTopstackVariable(t *Target, thread Thread, regs Registers, typename string, loadCfg LoadConfig) (*Variable, error) {
func readStackVariable(t *Target, thread Thread, regs Registers, off uint64, typename string, loadCfg LoadConfig) (*Variable, error) {
bi := thread.BinInfo()
scope, err := ThreadScope(t, thread)
if err != nil {
@ -954,7 +1024,7 @@ func readTopstackVariable(t *Target, thread Thread, regs Registers, typename str
if err != nil {
return nil, err
}
v := newVariable("", regs.SP(), typ, scope.BinInfo, scope.Mem)
v := newVariable("", regs.SP()+off, typ, scope.BinInfo, scope.Mem)
v.loadValue(loadCfg)
if v.Unreadable != nil {
return nil, v.Unreadable
@ -1040,7 +1110,11 @@ func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
// call injection just started, did not make any progress before being interrupted by a concurrent breakpoint.
return false
}
text, err := disassembleCurrentInstruction(t, thread, -1)
off := int64(0)
if thread.BinInfo().Arch.breakInstrMovesPC {
off = -int64(len(thread.BinInfo().Arch.breakpointInstruction))
}
text, err := disassembleCurrentInstruction(t, thread, off)
if err != nil || len(text) <= 0 {
return false
}
@ -1069,6 +1143,11 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
return false, err
}
arch := thread.BinInfo().Arch
if !arch.breakInstrMovesPC {
setPC(thread, loc.PC+uint64(len(arch.breakpointInstruction)))
}
fncallLog("step for injection on goroutine %d (current) thread=%d (location %s)", g.ID, thread.ThreadID(), loc.Fn.Name)
callinj.continueCompleted <- g
contReq, ok := <-callinj.continueRequest
@ -1134,18 +1213,27 @@ func debugCallFunction(bi *BinaryInfo) (*Function, int) {
// debugCallProtocolReg returns the register ID (as defined in pkg/dwarf/regnum)
// of the register used in the debug call protocol, given the debug call version.
// Also returns a bool indicating whether the version is supported.
func debugCallProtocolReg(version int) (uint64, bool) {
// TODO(aarzilli): make this generic when call injection is supported on other architectures.
var protocolReg uint64
switch version {
case 1:
protocolReg = regnum.AMD64_Rax
case 2:
protocolReg = regnum.AMD64_R12
func debugCallProtocolReg(archName string, version int) (uint64, bool) {
switch archName {
case "amd64":
var protocolReg uint64
switch version {
case 1:
protocolReg = regnum.AMD64_Rax
case 2:
protocolReg = regnum.AMD64_R12
default:
return 0, false
}
return protocolReg, true
case "arm64":
if version == 2 {
return regnum.ARM64_X0 + 20, true
}
return 0, false
default:
return 0, false
}
return protocolReg, true
}
type fakeEntry map[dwarf.Attr]interface{}
@ -1186,12 +1274,25 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
}
}
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.AMD64_Rax, false),
m("typ", t("*runtime._type"), regnum.AMD64_Rbx, false),
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
switch bi.Arch.Name {
case "amd64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.AMD64_Rax, false),
m("typ", t("*runtime._type"), regnum.AMD64_Rbx, false),
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
}
return r, err1
case "arm64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.ARM64_X0, false),
m("typ", t("*runtime._type"), regnum.ARM64_X0+1, false),
m("needzero", t("bool"), regnum.ARM64_X0+2, false),
m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
}
return r, err1
default:
// do nothing
return nil, nil
}
return r, err1
}

View File

@ -1915,6 +1915,10 @@ func (regs *gdbRegisters) GAddr() (uint64, bool) {
return regs.gaddr, regs.hasgaddr
}
func (regs *gdbRegisters) LR() uint64 {
return binary.LittleEndian.Uint64(regs.regs["lr"].value)
}
func (regs *gdbRegisters) byName(name string) uint64 {
reg, ok := regs.regs[name]
if !ok {
@ -1951,6 +1955,9 @@ func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
gdbreg, ok = t.regs.regs["z"+regName[1:]]
}
}
if !ok && t.p.bi.Arch.Name == "arm64" && regName == "x30" {
gdbreg, ok = t.regs.regs["lr"]
}
if !ok {
return fmt.Errorf("could not set register %s: not found", regName)
}

View File

@ -324,7 +324,7 @@ func (conn *gdbConn) readRegisterInfo(regFound map[string]bool) (err error) {
case "container-regs":
contained = true
case "set":
if value == "Exception State Registers" {
if value == "Exception State Registers" || value == "AMX Registers" {
// debugserver doesn't like it if we try to write these
ignoreOnWrite = true
}

View File

@ -129,6 +129,11 @@ func (r *AMD64Registers) GAddr() (uint64, bool) {
return 0, false
}
// LR returns the link register.
func (r *AMD64Registers) LR() uint64 {
panic("not valid")
}
// Copy returns a copy of these registers that is guaranteed not to change.
func (r *AMD64Registers) Copy() (proc.Registers, error) {
if r.loadFpRegs != nil {

View File

@ -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"
)
@ -115,6 +117,11 @@ func (r *ARM64Registers) GAddr() (uint64, bool) {
return r.Regs.Regs[28], !r.iscgo
}
// LR returns the link register.
func (r *ARM64Registers) LR() uint64 {
return r.Regs.Regs[30]
}
// Copy returns a copy of these registers that is guaranteed not to change.
func (r *ARM64Registers) Copy() (proc.Registers, error) {
if r.loadFpRegs != nil {
@ -138,6 +145,40 @@ func (r *ARM64Registers) Copy() (proc.Registers, error) {
return &rr, nil
}
func (r *ARM64Registers) SetReg(regNum uint64, reg *op.DwarfRegister) (fpchanged bool, err error) {
switch regNum {
case regnum.ARM64_PC:
r.Regs.Pc = reg.Uint64Val
return false, nil
case regnum.ARM64_SP:
r.Regs.Sp = reg.Uint64Val
return false, nil
default:
switch {
case regNum >= regnum.ARM64_X0 && regNum <= regnum.ARM64_X0+30:
r.Regs.Regs[regNum-regnum.ARM64_X0] = reg.Uint64Val
return false, nil
case regNum >= regnum.ARM64_V0 && regNum <= regnum.ARM64_V0+30:
if r.loadFpRegs != nil {
err := r.loadFpRegs(r)
r.loadFpRegs = nil
if err != nil {
return false, err
}
}
i := regNum - regnum.ARM64_V0
reg.FillBytes()
copy(r.Fpregset[16*i:], reg.Bytes)
return true, nil
default:
return false, fmt.Errorf("changing register %d not implemented", regNum)
}
}
}
type ARM64PtraceFpRegs struct {
Vregs []byte
Fpsr uint32

View File

@ -101,10 +101,15 @@ func (r *I386Registers) CX() uint64 {
}
// TLS returns the address of the thread local storage memory segment.
func (r I386Registers) TLS() uint64 {
func (r *I386Registers) TLS() uint64 {
return r.Tls
}
// LR returns the link register.
func (r *I386Registers) LR() uint64 {
panic("not valid")
}
// GAddr returns the address of the G variable if it is known, 0 and false
// otherwise.
func (r *I386Registers) GAddr() (uint64, bool) {

View File

@ -101,6 +101,10 @@ func (r *Regs) BP() uint64 {
return r.rbp
}
func (r *Regs) LR() uint64 {
return 0
}
// TLS returns the value of the register
// that contains the location of the thread
// local storage segment.

View File

@ -2,14 +2,12 @@ package native
import (
"debug/elf"
"fmt"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"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"
)
@ -84,19 +82,24 @@ func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
return err
}
r := ir.(*linutil.ARM64Registers)
switch regNum {
case regnum.ARM64_PC:
r.Regs.Pc = reg.Uint64Val
case regnum.ARM64_SP:
r.Regs.Sp = reg.Uint64Val
default:
//TODO(aarzilli): when the register calling convention is adopted by Go on
// arm64 this should be implemented.
return fmt.Errorf("changing register %d not implemented", regNum)
fpchanged, err := r.SetReg(regNum, reg)
if err != nil {
return err
}
thread.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(thread.ID, r.Regs) })
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
}
return err
}

View File

@ -30,7 +30,7 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
var restoreRegistersErr error
t.dbp.execPtraceFunc(func() {
restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
if restoreRegistersErr != syscall.Errno(0) {
if restoreRegistersErr != syscall.Errno(0) && restoreRegistersErr != nil {
return
}
if sr.Fpregset != nil {

View File

@ -17,6 +17,7 @@ type Registers interface {
PC() uint64
SP() uint64
BP() uint64
LR() uint64
TLS() uint64
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
GAddr() (uint64, bool)

View File

@ -268,7 +268,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"
return t.Process.BinInfo().Arch.Name == "amd64" || t.Process.BinInfo().Arch.Name == "arm64"
}
// ClearCaches clears internal caches that should not survive a restart.

View File

@ -325,12 +325,17 @@ func MustSupportFunctionCalls(t *testing.T, testBackend string) {
t.Skip("this backend does not support function calls")
}
if runtime.GOOS == "darwin" && os.Getenv("TRAVIS") == "true" {
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 == "arm64" || runtime.GOARCH == "386" {
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) {
t.Skip("this version of Go does not support function calls")
}
}
}
// DefaultTestBackend changes the value of testBackend to be the default

View File

@ -100,3 +100,7 @@ func setSP(thread Thread, newSP uint64) error {
func setClosureReg(thread Thread, newClosureReg uint64) error {
return thread.SetReg(thread.BinInfo().Arch.ContextRegNum, op.DwarfRegisterFromUint64(newClosureReg))
}
func setLR(thread Thread, newLR uint64) error {
return thread.SetReg(thread.BinInfo().Arch.LRRegNum, op.DwarfRegisterFromUint64(newLR))
}

View File

@ -149,6 +149,11 @@ func (r *AMD64Registers) BP() uint64 {
return r.rbp
}
// LR returns the link register.
func (r *AMD64Registers) LR() uint64 {
return 0
}
// TLS returns the value of the register
// that contains the location of the thread
// local storage segment.

View File

@ -1303,6 +1303,8 @@ func TestCallFunction(t *testing.T) {
{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil},
{`regabistacktest2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, []string{":int:3", ":int:5", ":int:7", ":int:9", ":int:11", ":int:13", ":int:15", ":int:17", ":int:19", ":int:11"}, nil},
{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil},
{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil},
{`floatsum(1, 2)`, []string{":float64:3"}, nil},
}
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {