mirror of
https://github.com/go-delve/delve.git
synced 2025-11-02 21:40:22 +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
|
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 {
|
type Issue2698 struct {
|
||||||
a uint32
|
a uint32
|
||||||
b uint8
|
b uint8
|
||||||
@ -198,6 +210,7 @@ func main() {
|
|||||||
var pa2 *astruct
|
var pa2 *astruct
|
||||||
var str string = "old string value"
|
var str string = "old string value"
|
||||||
longstrs := []string{"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"}
|
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_a VRcvrable = a
|
||||||
var vable_pa VRcvrable = pa
|
var vable_pa VRcvrable = pa
|
||||||
var pable_pa PRcvrable = pa
|
var pable_pa PRcvrable = pa
|
||||||
@ -225,5 +238,5 @@ func main() {
|
|||||||
d.Method()
|
d.Method()
|
||||||
d.Base.Method()
|
d.Base.Method()
|
||||||
x.CallMe()
|
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,
|
ContextRegNum: regnum.AMD64_Rdx,
|
||||||
asmRegisters: amd64AsmRegisters,
|
asmRegisters: amd64AsmRegisters,
|
||||||
RegisterNameToDwarf: nameToDwarfFunc(regnum.AMD64NameToDwarf),
|
RegisterNameToDwarf: nameToDwarfFunc(regnum.AMD64NameToDwarf),
|
||||||
|
debugCallMinStackSize: 256,
|
||||||
|
maxRegArgBytes: 9*8 + 15*8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ type Arch struct {
|
|||||||
SPRegNum uint64
|
SPRegNum uint64
|
||||||
BPRegNum uint64
|
BPRegNum uint64
|
||||||
ContextRegNum uint64 // register used to pass a closure context when calling a function pointer
|
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.
|
// 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.
|
// 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 returns whether StepBreakpoint can be set at pc.
|
||||||
inhibitStepInto func(bi *BinaryInfo, pc uint64) bool
|
inhibitStepInto func(bi *BinaryInfo, pc uint64) bool
|
||||||
RegisterNameToDwarf func(s string) (int, 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 maps assembly register numbers to dwarf registers.
|
||||||
asmRegisters map[int]asmRegister
|
asmRegisters map[int]asmRegister
|
||||||
|
|||||||
@ -35,8 +35,12 @@ func ARM64Arch(goos string) *Arch {
|
|||||||
usesLR: true,
|
usesLR: true,
|
||||||
PCRegNum: regnum.ARM64_PC,
|
PCRegNum: regnum.ARM64_PC,
|
||||||
SPRegNum: regnum.ARM64_SP,
|
SPRegNum: regnum.ARM64_SP,
|
||||||
|
ContextRegNum: regnum.ARM64_X0 + 26,
|
||||||
|
LRRegNum: regnum.ARM64_LR,
|
||||||
asmRegisters: arm64AsmRegisters,
|
asmRegisters: arm64AsmRegisters,
|
||||||
RegisterNameToDwarf: nameToDwarfFunc(regnum.ARM64NameToDwarf),
|
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) SP() uint64 { return regs.sp }
|
||||||
func (regs *delveRegisters) TLS() uint64 { return regs.tls }
|
func (regs *delveRegisters) TLS() uint64 { return regs.tls }
|
||||||
func (regs *delveRegisters) GAddr() (uint64, bool) { return regs.gaddr, regs.hasGAddr }
|
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) {
|
func (regs *delveRegisters) Copy() (proc.Registers, error) {
|
||||||
return regs, nil
|
return regs, nil
|
||||||
|
|||||||
@ -141,6 +141,10 @@ func (r *AMD64Registers) BP() uint64 {
|
|||||||
return uint64(r.Regs.Rbp)
|
return uint64(r.Regs.Rbp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *AMD64Registers) LR() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// TLS returns the address of the thread local storage memory segment.
|
// TLS returns the address of the thread local storage memory segment.
|
||||||
func (r *AMD64Registers) TLS() uint64 {
|
func (r *AMD64Registers) TLS() uint64 {
|
||||||
return r.Fsbase
|
return r.Fsbase
|
||||||
|
|||||||
@ -44,10 +44,6 @@ const (
|
|||||||
debugCallFunctionNamePrefix2 = "runtime.debugCall"
|
debugCallFunctionNamePrefix2 = "runtime.debugCall"
|
||||||
maxDebugCallVersion = 2
|
maxDebugCallVersion = 2
|
||||||
maxArgFrameSize = 65535
|
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 (
|
var (
|
||||||
@ -303,10 +299,10 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if regs.SP()-256 <= stacklo {
|
if regs.SP()-bi.Arch.debugCallMinStackSize <= stacklo {
|
||||||
return nil, errNotEnoughStack
|
return nil, errNotEnoughStack
|
||||||
}
|
}
|
||||||
protocolReg, ok := debugCallProtocolReg(dbgcallversion)
|
protocolReg, ok := debugCallProtocolReg(bi.Arch.Name, dbgcallversion)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errFuncCallUnsupported
|
return nil, errFuncCallUnsupported
|
||||||
}
|
}
|
||||||
@ -324,12 +320,44 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
|
switch bi.Arch.Name {
|
||||||
return nil, err
|
case "amd64":
|
||||||
}
|
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
|
||||||
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
|
return nil, err
|
||||||
if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); 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())
|
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
|
// * changes the value of PC to callAddr
|
||||||
// Note: regs are NOT updated!
|
// Note: regs are NOT updated!
|
||||||
func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) error {
|
func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) error {
|
||||||
sp := regs.SP()
|
switch bi.Arch.Name {
|
||||||
// push PC on the stack
|
case "amd64":
|
||||||
sp -= uint64(bi.Arch.PtrSize())
|
sp := regs.SP()
|
||||||
if err := setSP(thread, sp); err != nil {
|
// push PC on the stack
|
||||||
return err
|
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.
|
// 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
|
// See: https://github.com/go-delve/delve/pull/2451#discussion_r665761531
|
||||||
// TODO: Make this generic for other platforms.
|
// TODO: Make this generic for other platforms.
|
||||||
argFrameSize = alignAddr(argFrameSize, 8)
|
argFrameSize = alignAddr(argFrameSize, 8)
|
||||||
argFrameSize += maxRegArgBytes
|
argFrameSize += int64(bi.Arch.maxRegArgBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(formalArgs, func(i, j int) bool {
|
sort.Slice(formalArgs, func(i, j int) bool {
|
||||||
@ -807,9 +845,13 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch regval {
|
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
|
// 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 {
|
if err != nil {
|
||||||
fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
|
fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
|
||||||
break
|
break
|
||||||
@ -817,8 +859,9 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
errvar.Name = "err"
|
errvar.Name = "err"
|
||||||
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
|
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
|
||||||
|
|
||||||
case debugCallRegCompleteCall:
|
case debugCallRegCompleteCall: // 0
|
||||||
p.fncallForG[callScope.g.ID].startThreadID = 0
|
p.fncallForG[callScope.g.ID].startThreadID = 0
|
||||||
|
|
||||||
// evaluate arguments of the target function, copy them into its argument frame and call the function
|
// 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 fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
|
||||||
// if we couldn't figure out which function we are calling before
|
// 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()
|
cfa := regs.SP()
|
||||||
oldpc := regs.PC()
|
oldpc := regs.PC()
|
||||||
|
var oldlr uint64
|
||||||
|
if bi.Arch.Name == "arm64" {
|
||||||
|
oldlr = regs.LR()
|
||||||
|
}
|
||||||
callOP(bi, thread, regs, fncall.fn.Entry)
|
callOP(bi, thread, regs, fncall.fn.Entry)
|
||||||
formalScope, err := GoroutineScope(callScope.target, thread)
|
formalScope, err := GoroutineScope(callScope.target, thread)
|
||||||
if formalScope != nil && formalScope.Regs.CFA != int64(cfa) {
|
if formalScope != nil && formalScope.Regs.CFA != int64(cfa) {
|
||||||
@ -861,14 +908,22 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// rolling back the call, note: this works because we called regs.Copy() above
|
// rolling back the call, note: this works because we called regs.Copy() above
|
||||||
setSP(thread, cfa)
|
switch bi.Arch.Name {
|
||||||
setPC(thread, oldpc)
|
case "amd64":
|
||||||
|
setSP(thread, cfa)
|
||||||
|
setPC(thread, oldpc)
|
||||||
|
case "arm64":
|
||||||
|
setLR(thread, oldlr)
|
||||||
|
setPC(thread, oldpc)
|
||||||
|
default:
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
fncall.err = err
|
fncall.err = err
|
||||||
fncall.lateCallFailure = true
|
fncall.lateCallFailure = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case debugCallRegRestoreRegisters:
|
case debugCallRegRestoreRegisters: // 16
|
||||||
// runtime requests that we restore the registers (all except pc and sp),
|
// runtime requests that we restore the registers (all except pc and sp),
|
||||||
// this is also the last step of the function call protocol.
|
// this is also the last step of the function call protocol.
|
||||||
pc, sp := regs.PC(), regs.SP()
|
pc, sp := regs.PC(), regs.SP()
|
||||||
@ -886,7 +941,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case debugCallRegReadReturn:
|
case debugCallRegReadReturn: // 1
|
||||||
// read return arguments from stack
|
// read return arguments from stack
|
||||||
if fncall.panicvar != nil || fncall.lateCallFailure {
|
if fncall.panicvar != nil || fncall.lateCallFailure {
|
||||||
break
|
break
|
||||||
@ -925,10 +980,25 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
if threadg, _ := GetG(thread); threadg != nil {
|
if threadg, _ := GetG(thread); threadg != nil {
|
||||||
callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
|
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
|
// 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 {
|
if err != nil {
|
||||||
fncall.err = fmt.Errorf("could not get panic: %v", err)
|
fncall.err = fmt.Errorf("could not get panic: %v", err)
|
||||||
break
|
break
|
||||||
@ -944,7 +1014,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
|
|||||||
return false
|
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()
|
bi := thread.BinInfo()
|
||||||
scope, err := ThreadScope(t, thread)
|
scope, err := ThreadScope(t, thread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -954,7 +1024,7 @@ func readTopstackVariable(t *Target, thread Thread, regs Registers, typename str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
v.loadValue(loadCfg)
|
||||||
if v.Unreadable != nil {
|
if v.Unreadable != nil {
|
||||||
return nil, v.Unreadable
|
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.
|
// call injection just started, did not make any progress before being interrupted by a concurrent breakpoint.
|
||||||
return false
|
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 {
|
if err != nil || len(text) <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1069,6 +1143,11 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
|
|||||||
return false, err
|
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)
|
fncallLog("step for injection on goroutine %d (current) thread=%d (location %s)", g.ID, thread.ThreadID(), loc.Fn.Name)
|
||||||
callinj.continueCompleted <- g
|
callinj.continueCompleted <- g
|
||||||
contReq, ok := <-callinj.continueRequest
|
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)
|
// 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.
|
// of the register used in the debug call protocol, given the debug call version.
|
||||||
// Also returns a bool indicating whether the version is supported.
|
// Also returns a bool indicating whether the version is supported.
|
||||||
func debugCallProtocolReg(version int) (uint64, bool) {
|
func debugCallProtocolReg(archName string, version int) (uint64, bool) {
|
||||||
// TODO(aarzilli): make this generic when call injection is supported on other architectures.
|
switch archName {
|
||||||
var protocolReg uint64
|
case "amd64":
|
||||||
switch version {
|
var protocolReg uint64
|
||||||
case 1:
|
switch version {
|
||||||
protocolReg = regnum.AMD64_Rax
|
case 1:
|
||||||
case 2:
|
protocolReg = regnum.AMD64_Rax
|
||||||
protocolReg = regnum.AMD64_R12
|
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:
|
default:
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
return protocolReg, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeEntry map[dwarf.Attr]interface{}
|
type fakeEntry map[dwarf.Attr]interface{}
|
||||||
@ -1186,12 +1274,25 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := []*godwarf.Tree{
|
switch bi.Arch.Name {
|
||||||
m("size", t("uintptr"), regnum.AMD64_Rax, false),
|
case "amd64":
|
||||||
m("typ", t("*runtime._type"), regnum.AMD64_Rbx, false),
|
r := []*godwarf.Tree{
|
||||||
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
|
m("size", t("uintptr"), regnum.AMD64_Rax, false),
|
||||||
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
|
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
|
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 {
|
func (regs *gdbRegisters) byName(name string) uint64 {
|
||||||
reg, ok := regs.regs[name]
|
reg, ok := regs.regs[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -1951,6 +1955,9 @@ func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
|
|||||||
gdbreg, ok = t.regs.regs["z"+regName[1:]]
|
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 {
|
if !ok {
|
||||||
return fmt.Errorf("could not set register %s: not found", regName)
|
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":
|
case "container-regs":
|
||||||
contained = true
|
contained = true
|
||||||
case "set":
|
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
|
// debugserver doesn't like it if we try to write these
|
||||||
ignoreOnWrite = true
|
ignoreOnWrite = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,11 @@ func (r *AMD64Registers) GAddr() (uint64, bool) {
|
|||||||
return 0, false
|
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.
|
// Copy returns a copy of these registers that is guaranteed not to change.
|
||||||
func (r *AMD64Registers) Copy() (proc.Registers, error) {
|
func (r *AMD64Registers) Copy() (proc.Registers, error) {
|
||||||
if r.loadFpRegs != nil {
|
if r.loadFpRegs != nil {
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package linutil
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,6 +117,11 @@ func (r *ARM64Registers) GAddr() (uint64, bool) {
|
|||||||
return r.Regs.Regs[28], !r.iscgo
|
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.
|
// Copy returns a copy of these registers that is guaranteed not to change.
|
||||||
func (r *ARM64Registers) Copy() (proc.Registers, error) {
|
func (r *ARM64Registers) Copy() (proc.Registers, error) {
|
||||||
if r.loadFpRegs != nil {
|
if r.loadFpRegs != nil {
|
||||||
@ -138,6 +145,40 @@ func (r *ARM64Registers) Copy() (proc.Registers, error) {
|
|||||||
return &rr, nil
|
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 {
|
type ARM64PtraceFpRegs struct {
|
||||||
Vregs []byte
|
Vregs []byte
|
||||||
Fpsr uint32
|
Fpsr uint32
|
||||||
|
|||||||
@ -101,10 +101,15 @@ func (r *I386Registers) CX() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TLS returns the address of the thread local storage memory segment.
|
// TLS returns the address of the thread local storage memory segment.
|
||||||
func (r I386Registers) TLS() uint64 {
|
func (r *I386Registers) TLS() uint64 {
|
||||||
return r.Tls
|
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
|
// GAddr returns the address of the G variable if it is known, 0 and false
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func (r *I386Registers) GAddr() (uint64, bool) {
|
func (r *I386Registers) GAddr() (uint64, bool) {
|
||||||
|
|||||||
@ -101,6 +101,10 @@ func (r *Regs) BP() uint64 {
|
|||||||
return r.rbp
|
return r.rbp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Regs) LR() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// TLS returns the value of the register
|
// TLS returns the value of the register
|
||||||
// that contains the location of the thread
|
// that contains the location of the thread
|
||||||
// local storage segment.
|
// local storage segment.
|
||||||
|
|||||||
@ -2,14 +2,12 @@ package native
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"fmt"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
"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"
|
||||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
)
|
)
|
||||||
@ -84,19 +82,24 @@ func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r := ir.(*linutil.ARM64Registers)
|
r := ir.(*linutil.ARM64Registers)
|
||||||
|
fpchanged, err := r.SetReg(regNum, reg)
|
||||||
switch regNum {
|
if err != nil {
|
||||||
case regnum.ARM64_PC:
|
return err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
|||||||
var restoreRegistersErr error
|
var restoreRegistersErr error
|
||||||
t.dbp.execPtraceFunc(func() {
|
t.dbp.execPtraceFunc(func() {
|
||||||
restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
|
restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
|
||||||
if restoreRegistersErr != syscall.Errno(0) {
|
if restoreRegistersErr != syscall.Errno(0) && restoreRegistersErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if sr.Fpregset != nil {
|
if sr.Fpregset != nil {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type Registers interface {
|
|||||||
PC() uint64
|
PC() uint64
|
||||||
SP() uint64
|
SP() uint64
|
||||||
BP() uint64
|
BP() uint64
|
||||||
|
LR() uint64
|
||||||
TLS() uint64
|
TLS() uint64
|
||||||
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
||||||
GAddr() (uint64, bool)
|
GAddr() (uint64, bool)
|
||||||
|
|||||||
@ -268,7 +268,7 @@ func (t *Target) Valid() (bool, error) {
|
|||||||
// Currently only non-recorded processes running on AMD64 support
|
// Currently only non-recorded processes running on AMD64 support
|
||||||
// function calls.
|
// function calls.
|
||||||
func (t *Target) SupportsFunctionCalls() bool {
|
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.
|
// 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")
|
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)")
|
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))
|
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
|
// 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 {
|
func setClosureReg(thread Thread, newClosureReg uint64) error {
|
||||||
return thread.SetReg(thread.BinInfo().Arch.ContextRegNum, op.DwarfRegisterFromUint64(newClosureReg))
|
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
|
return r.rbp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LR returns the link register.
|
||||||
|
func (r *AMD64Registers) LR() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// TLS returns the value of the register
|
// TLS returns the value of the register
|
||||||
// that contains the location of the thread
|
// that contains the location of the thread
|
||||||
// local storage segment.
|
// 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},
|
{`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},
|
{`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},
|
{`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) {
|
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
|||||||
Reference in New Issue
Block a user