mirror of
https://github.com/go-delve/delve.git
synced 2025-11-02 04:36:29 +08:00
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:
committed by
GitHub
parent
d513b6da45
commit
c9d800edb9
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user