diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go index 326ebade..34063d64 100644 --- a/_fixtures/fncall.go +++ b/_fixtures/fncall.go @@ -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) } diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index c8bccdff..050c776f 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -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, } } diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index f6db9953..0f1392f7 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -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 diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index e846523d..e6e47f0d 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -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 } } diff --git a/pkg/proc/core/delve_core.go b/pkg/proc/core/delve_core.go index 8dbc88db..9a8d146c 100644 --- a/pkg/proc/core/delve_core.go +++ b/pkg/proc/core/delve_core.go @@ -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 diff --git a/pkg/proc/fbsdutil/regs.go b/pkg/proc/fbsdutil/regs.go index 4b9cd80b..e8cbddf3 100644 --- a/pkg/proc/fbsdutil/regs.go +++ b/pkg/proc/fbsdutil/regs.go @@ -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 diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 8a88d25b..d2626f02 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -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 } diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 8638077b..aafca0c5 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -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) } diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index 798730f3..c5227474 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -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 } diff --git a/pkg/proc/linutil/regs_amd64_arch.go b/pkg/proc/linutil/regs_amd64_arch.go index dbea3396..38a8df8f 100644 --- a/pkg/proc/linutil/regs_amd64_arch.go +++ b/pkg/proc/linutil/regs_amd64_arch.go @@ -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 { diff --git a/pkg/proc/linutil/regs_arm64_arch.go b/pkg/proc/linutil/regs_arm64_arch.go index 8bdae132..5e9243a9 100644 --- a/pkg/proc/linutil/regs_arm64_arch.go +++ b/pkg/proc/linutil/regs_arm64_arch.go @@ -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 diff --git a/pkg/proc/linutil/regs_i386_arch.go b/pkg/proc/linutil/regs_i386_arch.go index d456ff00..dac34b3d 100644 --- a/pkg/proc/linutil/regs_i386_arch.go +++ b/pkg/proc/linutil/regs_i386_arch.go @@ -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) { diff --git a/pkg/proc/native/registers_darwin_amd64.go b/pkg/proc/native/registers_darwin_amd64.go index e965ea77..c8246d7f 100644 --- a/pkg/proc/native/registers_darwin_amd64.go +++ b/pkg/proc/native/registers_darwin_amd64.go @@ -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. diff --git a/pkg/proc/native/registers_linux_arm64.go b/pkg/proc/native/registers_linux_arm64.go index cebbd175..0b797cc0 100644 --- a/pkg/proc/native/registers_linux_arm64.go +++ b/pkg/proc/native/registers_linux_arm64.go @@ -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 } diff --git a/pkg/proc/native/threads_linux_arm64.go b/pkg/proc/native/threads_linux_arm64.go index ea0e7642..43caea68 100644 --- a/pkg/proc/native/threads_linux_arm64.go +++ b/pkg/proc/native/threads_linux_arm64.go @@ -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 { diff --git a/pkg/proc/registers.go b/pkg/proc/registers.go index 83a58650..185d0615 100644 --- a/pkg/proc/registers.go +++ b/pkg/proc/registers.go @@ -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) diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 3c317d48..ef2020aa 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -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. diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index 954fee7a..e56b9075 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -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 diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 36357258..f191598e 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -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)) +} diff --git a/pkg/proc/winutil/regs.go b/pkg/proc/winutil/regs.go index a2c482e9..3587ded8 100644 --- a/pkg/proc/winutil/regs.go +++ b/pkg/proc/winutil/regs.go @@ -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. diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 863a962d..6a83c397 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -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) {