diff --git a/pkg/proc/dwarf_export_test.go b/pkg/proc/dwarf_export_test.go
index 7add16c718..38944a0a78 100644
--- a/pkg/proc/dwarf_export_test.go
+++ b/pkg/proc/dwarf_export_test.go
@@ -2,6 +2,7 @@ package proc
import (
"github.com/go-delve/delve/pkg/dwarf/op"
+ "github.com/go-delve/delve/pkg/proc/evalop"
"golang.org/x/arch/x86/x86asm"
)
@@ -30,3 +31,14 @@ func NewCompositeMemory(p *Target, pieces []op.Piece, base uint64) (*compositeMe
func IsJNZ(inst archInst) bool {
return inst.(*x86Inst).Op == x86asm.JNE
}
+
+// HasDebugPinner returns true if the target has runtime.debugPinner.
+func (bi *BinaryInfo) HasDebugPinner() bool {
+ return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
+}
+
+// DebugPinCount returns the number of addresses pinned during the last
+// function call injection.
+func DebugPinCount() int {
+ return debugPinCount
+}
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index 73b0b9b4f5..822fb8369d 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -182,9 +182,17 @@ func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) {
return FrameToScope(t, thread.ProcessMemory(), g, threadID, locations...), nil
}
+func (scope *EvalScope) evalopFlags() evalop.Flags {
+ flags := evalop.Flags(0)
+ if scope.BinInfo.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil {
+ flags |= evalop.HasDebugPinner
+ }
+ return flags
+}
+
// EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
- ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, false)
+ ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags())
if err != nil {
return nil, err
}
@@ -627,7 +635,7 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {
// SetVariable sets the value of the named variable
func (scope *EvalScope) SetVariable(name, value string) error {
- ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value)
+ ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value, scope.evalopFlags())
if err != nil {
return err
}
@@ -813,9 +821,13 @@ type evalStack struct {
scope *EvalScope
curthread Thread
lastRetiredFncall *functionCallState
+ debugPinner *Variable
}
func (s *evalStack) push(v *Variable) {
+ if v == nil {
+ panic(fmt.Errorf("internal debugger error, nil pushed onto variables stack"))
+ }
s.stack = append(s.stack, v)
}
@@ -929,7 +941,7 @@ func (stack *evalStack) run() {
stack.executeOp()
// If the instruction we just executed requests the call injection
// protocol by setting callInjectionContinue we switch to it.
- if stack.callInjectionContinue {
+ if stack.callInjectionContinue && stack.err == nil {
scope.callCtx.injectionThread = nil
return
}
@@ -944,25 +956,35 @@ func (stack *evalStack) run() {
// injections before returning.
if len(stack.fncalls) > 0 {
+ fncallLog("undoing calls (%v)", stack.err)
fncall := stack.fncallPeek()
if fncall == stack.lastRetiredFncall {
stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err)
return
}
if fncall.undoInjection != nil {
- // setTargetExecuted is set if evalop.CallInjectionSetTarget has been
- // executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
- // call in evalop.CallInjectionSetTarget before continuing.
- switch scope.BinInfo.Arch.Name {
- case "amd64":
- regs, _ := curthread.Registers()
- setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
- setPC(curthread, fncall.undoInjection.oldpc)
- case "arm64", "ppc64le":
- setLR(curthread, fncall.undoInjection.oldlr)
- setPC(curthread, fncall.undoInjection.oldpc)
- default:
- panic("not implemented")
+ if fncall.undoInjection.doComplete2 {
+ // doComplete2 is set if CallInjectionComplete{DoPinning: true} has been
+ // executed but CallInjectionComplete2 hasn't.
+ regs, err := curthread.Registers()
+ if err == nil {
+ callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
+ }
+ } else {
+ // undoInjection is set if evalop.CallInjectionSetTarget has been
+ // executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
+ // call in evalop.CallInjectionSetTarget before continuing.
+ switch scope.BinInfo.Arch.Name {
+ case "amd64":
+ regs, _ := curthread.Registers()
+ setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
+ setPC(curthread, fncall.undoInjection.oldpc)
+ case "arm64", "ppc64le":
+ setLR(curthread, fncall.undoInjection.oldlr)
+ setPC(curthread, fncall.undoInjection.oldpc)
+ default:
+ panic("not implemented")
+ }
}
}
stack.lastRetiredFncall = fncall
@@ -1122,6 +1144,11 @@ func (stack *evalStack) executeOp() {
case *evalop.Pop:
stack.pop()
+ case *evalop.Roll:
+ rolled := stack.stack[len(stack.stack)-op.N-1]
+ copy(stack.stack[len(stack.stack)-op.N-1:], stack.stack[len(stack.stack)-op.N:])
+ stack.stack[len(stack.stack)-1] = rolled
+
case *evalop.BuiltinCall:
vars := make([]*Variable, len(op.Args))
for i := len(op.Args) - 1; i >= 0; i-- {
@@ -1144,9 +1171,29 @@ func (stack *evalStack) executeOp() {
stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)
case *evalop.CallInjectionComplete:
- stack.fncallPeek().undoInjection = nil
+ fncall := stack.fncallPeek()
+ fncall.doPinning = op.DoPinning
+ if op.DoPinning {
+ fncall.undoInjection.doComplete2 = true
+ } else {
+ fncall.undoInjection = nil
+ }
stack.callInjectionContinue = true
+ case *evalop.CallInjectionComplete2:
+ fncall := stack.fncallPeek()
+ if len(fncall.addrsToPin) != 0 {
+ stack.err = fmt.Errorf("internal debugger error: CallInjectionComplete2 called when there still are addresses to pin")
+ }
+ fncall.undoInjection = nil
+ regs, err := curthread.Registers()
+ if err == nil {
+ callInjectionComplete2(scope, scope.BinInfo, stack.fncallPeek(), regs, curthread)
+ stack.callInjectionContinue = true
+ } else {
+ stack.err = err
+ }
+
case *evalop.CallInjectionStartSpecial:
stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)
@@ -1158,6 +1205,26 @@ func (stack *evalStack) executeOp() {
rhv := stack.pop()
stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe))
+ case *evalop.PushPinAddress:
+ debugPinCount++
+ fncall := stack.fncallPeek()
+ addrToPin := fncall.addrsToPin[len(fncall.addrsToPin)-1]
+ fncall.addrsToPin = fncall.addrsToPin[:len(fncall.addrsToPin)-1]
+ typ, err := scope.BinInfo.findType("unsafe.Pointer")
+ if ptyp, ok := typ.(*godwarf.PtrType); err == nil && ok {
+ v := newVariable("", 0, typ, scope.BinInfo, scope.Mem)
+ v.Children = []Variable{*(newVariable("", uint64(addrToPin), ptyp.Type, scope.BinInfo, scope.Mem))}
+ stack.push(v)
+ } else {
+ stack.err = fmt.Errorf("can not pin address: %v", err)
+ }
+
+ case *evalop.SetDebugPinner:
+ stack.debugPinner = stack.pop()
+
+ case *evalop.PushDebugPinner:
+ stack.push(stack.debugPinner)
+
default:
stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op)
}
@@ -1258,7 +1325,7 @@ func (stack *evalStack) pushIdent(scope *EvalScope, name string) (found bool) {
}
func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
- ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t)
+ ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t, scope.evalopFlags())
if err != nil {
return nil, err
}
@@ -1274,9 +1341,14 @@ func exprToString(t ast.Expr) string {
}
func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
- x := stack.peek()
- if op.Pop {
- stack.pop()
+ var x *Variable
+
+ switch op.When {
+ case evalop.JumpIfTrue, evalop.JumpIfFalse, evalop.JumpIfAllocStringChecksFail:
+ x = stack.peek()
+ if op.Pop {
+ stack.pop()
+ }
}
var v bool
@@ -1293,6 +1365,17 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
return
}
return
+ case evalop.JumpAlways:
+ stack.opidx = op.Target - 1
+ return
+ case evalop.JumpIfPinningDone:
+ fncall := stack.fncallPeek()
+ if len(fncall.addrsToPin) == 0 {
+ stack.opidx = op.Target - 1
+ }
+ return
+ default:
+ panic("internal error, bad jump condition")
}
if x.Kind != reflect.Bool {
diff --git a/pkg/proc/evalop/evalcompile.go b/pkg/proc/evalop/evalcompile.go
index 660c787942..976ad573a9 100644
--- a/pkg/proc/evalop/evalcompile.go
+++ b/pkg/proc/evalop/evalcompile.go
@@ -18,7 +18,8 @@ import (
)
var (
- ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
+ ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
+ DebugPinnerFunctionName = "runtime.debugPinnerV1"
)
type compileCtx struct {
@@ -26,6 +27,8 @@ type compileCtx struct {
ops []Op
allowCalls bool
curCall int
+ flags Flags
+ firstCall bool
}
type evalLookup interface {
@@ -33,14 +36,23 @@ type evalLookup interface {
HasBuiltin(string) bool
}
+type Flags uint8
+
+const (
+ CanSet Flags = 1 << iota
+ HasDebugPinner
+)
+
// CompileAST compiles the expression t into a list of instructions.
-func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
- ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
+func CompileAST(lookup evalLookup, t ast.Expr, flags Flags) ([]Op, error) {
+ ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err := ctx.compileAST(t)
if err != nil {
return nil, err
}
+ ctx.compileDebugUnpin()
+
err = ctx.depthCheck(1)
if err != nil {
return ctx.ops, err
@@ -50,18 +62,18 @@ func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
// Compile compiles the expression expr into a list of instructions.
// If canSet is true expressions like "x = y" are also accepted.
-func Compile(lookup evalLookup, expr string, canSet bool) ([]Op, error) {
+func Compile(lookup evalLookup, expr string, flags Flags) ([]Op, error) {
t, err := parser.ParseExpr(expr)
if err != nil {
- if canSet {
+ if flags&CanSet != 0 {
eqOff, isAs := isAssignment(err)
if isAs {
- return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:])
+ return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:], flags)
}
}
return nil, err
}
- return CompileAST(lookup, t)
+ return CompileAST(lookup, t, flags)
}
func isAssignment(err error) (int, bool) {
@@ -74,7 +86,7 @@ func isAssignment(err error) (int, bool) {
// CompileSet compiles the expression setting lhexpr to rhexpr into a list of
// instructions.
-func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
+func CompileSet(lookup evalLookup, lhexpr, rhexpr string, flags Flags) ([]Op, error) {
lhe, err := parser.ParseExpr(lhexpr)
if err != nil {
return nil, err
@@ -84,7 +96,7 @@ func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
return nil, err
}
- ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
+ ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err = ctx.compileAST(rhe)
if err != nil {
return nil, err
@@ -120,13 +132,17 @@ func (ctx *compileCtx) compileAllocLiteralString() {
&PushLen{},
&PushNil{},
&PushConst{constant.MakeBool(false)},
- })
+ }, true)
ctx.pushOp(&ConvertAllocToString{})
jmp.Target = len(ctx.ops)
}
-func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op) {
+func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op, doPinning bool) {
+ if doPinning {
+ ctx.compileGetDebugPinner()
+ }
+
id := ctx.curCall
ctx.curCall++
ctx.pushOp(&CallInjectionStartSpecial{
@@ -136,11 +152,40 @@ func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args
ctx.pushOp(&CallInjectionSetTarget{id: id})
for i := range args {
- ctx.pushOp(args[i])
+ if args[i] != nil {
+ ctx.pushOp(args[i])
+ }
ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i})
}
- ctx.pushOp(&CallInjectionComplete{id: id})
+ doPinning = doPinning && (ctx.flags&HasDebugPinner != 0)
+
+ ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: doPinning})
+
+ if doPinning {
+ ctx.compilePinningLoop(id)
+ }
+}
+
+func (ctx *compileCtx) compileGetDebugPinner() {
+ if ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
+ ctx.compileSpecialCall(DebugPinnerFunctionName, []ast.Expr{}, []Op{}, false)
+ ctx.pushOp(&SetDebugPinner{})
+ ctx.firstCall = false
+ }
+}
+
+func (ctx *compileCtx) compileDebugUnpin() {
+ if !ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
+ ctx.compileSpecialCall("runtime.(*Pinner).Unpin", []ast.Expr{
+ &ast.Ident{Name: "debugPinner"},
+ }, []Op{
+ &PushDebugPinner{},
+ }, false)
+ ctx.pushOp(&Pop{})
+ ctx.pushOp(&PushNil{})
+ ctx.pushOp(&SetDebugPinner{})
+ }
}
func (ctx *compileCtx) pushOp(op Op) {
@@ -172,6 +217,8 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
}
}
+ debugPinnerSeen := false
+
for i, op := range ctx.ops {
npop, npush := op.depthCheck()
if depth[i] < npop {
@@ -179,8 +226,15 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
}
d := depth[i] - npop + npush
checkAndSet(i+1, d)
- if jmp, _ := op.(*Jump); jmp != nil {
- checkAndSet(jmp.Target, d)
+ switch op := op.(type) {
+ case *Jump:
+ checkAndSet(op.Target, d)
+ case *CallInjectionStartSpecial:
+ debugPinnerSeen = true
+ case *CallInjectionComplete:
+ if op.DoPinning && !debugPinnerSeen {
+ err = fmt.Errorf("internal debugger error: pinning call injection seen before call to %s at instrution %d", DebugPinnerFunctionName, i)
+ }
}
if err != nil {
return err
@@ -521,6 +575,16 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
id := ctx.curCall
ctx.curCall++
+ if ctx.flags&HasDebugPinner != 0 {
+ return ctx.compileFunctionCallNew(node, id)
+ }
+
+ return ctx.compileFunctionCallOld(node, id)
+}
+
+// compileFunctionCallOld compiles a function call when runtime.debugPinner is
+// not available in the target.
+func (ctx *compileCtx) compileFunctionCallOld(node *ast.CallExpr, id int) error {
oldAllowCalls := ctx.allowCalls
oldOps := ctx.ops
ctx.allowCalls = false
@@ -570,6 +634,61 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
return nil
}
+// compileFunctionCallNew compiles a function call when runtime.debugPinner
+// is available in the target.
+func (ctx *compileCtx) compileFunctionCallNew(node *ast.CallExpr, id int) error {
+ ctx.compileGetDebugPinner()
+
+ err := ctx.compileAST(node.Fun)
+ if err != nil {
+ return err
+ }
+
+ for i, arg := range node.Args {
+ err := ctx.compileAST(arg)
+ if isStringLiteral(arg) {
+ ctx.compileAllocLiteralString()
+ }
+ if err != nil {
+ return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
+ }
+ }
+
+ ctx.pushOp(&Roll{len(node.Args)})
+ ctx.pushOp(&CallInjectionStart{HasFunc: true, id: id, Node: node})
+ ctx.pushOp(&Pop{})
+ ctx.pushOp(&CallInjectionSetTarget{id: id})
+
+ for i := len(node.Args) - 1; i >= 0; i-- {
+ arg := node.Args[i]
+ ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
+ }
+
+ ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: true})
+
+ ctx.compilePinningLoop(id)
+
+ return nil
+}
+
+func (ctx *compileCtx) compilePinningLoop(id int) {
+ loopStart := len(ctx.ops)
+ jmp := &Jump{When: JumpIfPinningDone}
+ ctx.pushOp(jmp)
+ ctx.pushOp(&PushPinAddress{})
+ ctx.compileSpecialCall("runtime.(*Pinner).Pin", []ast.Expr{
+ &ast.Ident{Name: "debugPinner"},
+ &ast.Ident{Name: "pinAddress"},
+ }, []Op{
+ &PushDebugPinner{},
+ nil,
+ }, false)
+ ctx.pushOp(&Pop{})
+ ctx.pushOp(&Jump{When: JumpAlways, Target: loopStart})
+ jmp.Target = len(ctx.ops)
+ ctx.pushOp(&CallInjectionComplete2{id: id})
+}
+
func Listing(depth []int, ops []Op) string {
if depth == nil {
depth = make([]int, len(ops)+1)
diff --git a/pkg/proc/evalop/ops.go b/pkg/proc/evalop/ops.go
index 43ae96c563..d64c099545 100644
--- a/pkg/proc/evalop/ops.go
+++ b/pkg/proc/evalop/ops.go
@@ -164,8 +164,13 @@ type Jump struct {
}
func (jmpif *Jump) depthCheck() (npop, npush int) {
- if jmpif.Pop {
- return 1, 0
+ switch jmpif.When {
+ case JumpIfTrue, JumpIfFalse, JumpIfAllocStringChecksFail:
+ if jmpif.Pop {
+ return 1, 0
+ } else {
+ return 1, 1
+ }
}
return 0, 0
}
@@ -177,6 +182,8 @@ const (
JumpIfFalse JumpCond = iota
JumpIfTrue
JumpIfAllocStringChecksFail
+ JumpAlways
+ JumpIfPinningDone
)
// Binary pops two variables from the stack, applies the specified binary
@@ -200,6 +207,13 @@ type Pop struct {
func (*Pop) depthCheck() (npop, npush int) { return 1, 0 }
+// Roll removes the n-th element of the stack and pushes it back in at the top
+type Roll struct {
+ N int
+}
+
+func (*Roll) depthCheck() (npop, npush int) { return 1, 1 }
+
// BuiltinCall pops len(Args) argument from the stack, calls the specified
// builtin on them and pushes the result back on the stack.
type BuiltinCall struct {
@@ -240,11 +254,30 @@ type CallInjectionCopyArg struct {
func (*CallInjectionCopyArg) depthCheck() (npop, npush int) { return 1, 0 }
// CallInjectionComplete resumes target execution so that the injected call can run.
+// If DoPinning is true it stops after the call is completed without undoing
+// the call injection frames so that address pinning for the return value
+// can be performed, see CallInjectionComplete2.
type CallInjectionComplete struct {
+ id int
+ DoPinning bool
+}
+
+func (op *CallInjectionComplete) depthCheck() (npop, npush int) {
+ if op.DoPinning {
+ return 0, 0
+ } else {
+ return 0, 1
+ }
+}
+
+// CallInjectionComplete2 if DoPinning was passed to CallInjectionComplete
+// this will finish the call injection protocol and push the evaluation
+// result on the stack.
+type CallInjectionComplete2 struct {
id int
}
-func (*CallInjectionComplete) depthCheck() (npop, npush int) { return 0, 1 }
+func (*CallInjectionComplete2) depthCheck() (npop, npush int) { return 0, 1 }
// CallInjectionStartSpecial starts call injection for a function with a
// name and arguments known at compile time.
@@ -272,3 +305,22 @@ type SetValue struct {
}
func (*SetValue) depthCheck() (npop, npush int) { return 2, 0 }
+
+// SetDebugPinner pops one variable from the stack and uses it as the saved debug pinner.
+type SetDebugPinner struct {
+}
+
+func (*SetDebugPinner) depthCheck() (npop, npush int) { return 1, 0 }
+
+// PushDebugPinner pushes the debug pinner on the stack.
+type PushDebugPinner struct {
+}
+
+func (*PushDebugPinner) depthCheck() (npop, npush int) { return 0, 1 }
+
+// PushPinAddress pushes an address to pin on the stack (as an
+// unsafe.Pointer) and removes it from the list of addresses to pin.
+type PushPinAddress struct {
+}
+
+func (*PushPinAddress) depthCheck() (npop, npush int) { return 0, 1 }
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index ef9a9d05f9..fa8ec49085 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -42,6 +42,9 @@ import (
// - evalop.CallInjectionSetTarget
// - evalCallInjectionCopyArg
// - evalCallInjectionComplete
+//
+// When the target has runtime.debugPinner then evalCallInjectionPinPointer
+// must be also called in a loop until it returns false.
const (
debugCallFunctionNamePrefix1 = "debugCall"
@@ -89,12 +92,20 @@ type functionCallState struct {
// it contains information on how to undo a function call injection without running it
undoInjection *undoInjection
+ // hasDebugPinner is true if the target has runtime.debugPinner
+ hasDebugPinner bool
+ // doPinning is true if this call injection should pin the results
+ doPinning bool
+ // addrsToPin addresses from return variables that should be pinned
+ addrsToPin []uint64
+
protocolReg uint64
debugCallName string
}
type undoInjection struct {
oldpc, oldlr uint64
+ doComplete2 bool
}
type callContext struct {
@@ -123,10 +134,14 @@ type callInjection struct {
endCallInjection func()
}
+//lint:ignore U1000 this variable is only used by tests
+var debugPinCount int
+
// EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
// Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
+ debugPinCount = 0
t := grp.Selected
bi := t.BinInfo()
if !t.SupportsFunctionCalls() {
@@ -172,7 +187,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
return err
}
- ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, true)
+ ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags()|evalop.CanSet)
if err != nil {
return err
}
@@ -186,7 +201,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
}
stack.eval(scope, ops)
- if stack.callInjectionContinue {
+ if stack.callInjectionContinue && stack.err == nil {
return grp.Continue()
}
@@ -289,11 +304,19 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
return
}
+ for _, v := range stack.stack {
+ if v.Flags&(VariableFakeAddress|VariableCPURegister|variableSaved) != 0 || v.Unreadable != nil || v.DwarfType == nil || v.RealType == nil || v.Addr == 0 {
+ continue
+ }
+ saveVariable(v)
+ }
+
fncall := functionCallState{
- expr: op.Node,
- savedRegs: regs,
- protocolReg: protocolReg,
- debugCallName: dbgcallfn.Name,
+ expr: op.Node,
+ savedRegs: regs,
+ protocolReg: protocolReg,
+ debugCallName: dbgcallfn.Name,
+ hasDebugPinner: scope.BinInfo.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil,
}
if op.HasFunc {
@@ -365,10 +388,18 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID()
stack.fncallPush(&fncall)
- stack.push(newConstant(constant.MakeBool(fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0), scope.Mem))
+ stack.push(newConstant(constant.MakeBool(!fncall.hasDebugPinner && (fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0)), scope.Mem))
stack.callInjectionContinue = true
}
+func saveVariable(v *Variable) {
+ v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
+ v.Flags |= variableSaved
+ if cachemem, ok := v.mem.(*memCache); ok {
+ v.Unreadable = cachemem.load()
+ }
+}
+
func funcCallFinish(scope *EvalScope, stack *evalStack) {
fncall := stack.fncallPop()
if fncall.err != nil {
@@ -697,7 +728,7 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
return fmt.Errorf("escape check for %s failed, variable unreadable: %v", name, v.Unreadable)
}
switch v.Kind {
- case reflect.Ptr:
+ case reflect.Ptr, reflect.UnsafePointer:
var w *Variable
if len(v.Children) == 1 {
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
@@ -713,6 +744,12 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
sv = sv.maybeDereference()
return f(sv.Addr, name)
+ case reflect.Interface:
+ sv := v.clone()
+ sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.InterfaceType).TypedefType))
+ sv = sv.maybeDereference()
+ sv.Kind = reflect.Struct
+ return allPointers(sv, name, f)
case reflect.Struct:
t := v.RealType.(*godwarf.StructType)
for _, field := range t.Field {
@@ -732,6 +769,10 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
if err := f(v.funcvalAddr(), name); err != nil {
return err
}
+ case reflect.Complex64, reflect.Complex128, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64:
+ // nothing to do
+ default:
+ panic(fmt.Errorf("not implemented: %s", v.Kind))
}
return nil
@@ -864,29 +905,33 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
return (v.Flags & VariableReturnArgument) != 0
})
- loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
+ if !fncall.doPinning {
+ loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
+ }
for _, v := range fncall.retvars {
v.Flags |= VariableFakeAddress
}
- // Store the stack span of the currently running goroutine (which in Go >=
- // 1.15 might be different from the original injection goroutine) so that
- // later on we can use it to perform the escapeCheck
- if threadg, _ := GetG(thread); threadg != nil {
- callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
- }
- if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
- oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
- if err != nil {
- fncall.err = fmt.Errorf("could not restore LR: %v", err)
- break
- }
- if err = setLR(thread, oldlr); err != nil {
- fncall.err = fmt.Errorf("could not restore LR: %v", err)
- break
+ if fncall.doPinning {
+ stack.callInjectionContinue = false
+ for _, v := range fncall.retvars {
+ saveVariable(v)
+ //fmt.Printf("addr %#v %s %s %#v\n", v.Addr, v.Name, v.DwarfType.String(), v.mem)
+ allPointers(v, "", func(addr uint64, _ string) error {
+ if addr != 0 && pointerEscapes(addr, callScope.g.stack, callScope.callCtx.stacks) {
+ fncall.addrsToPin = append(fncall.addrsToPin, addr)
+ }
+ return nil
+ })
}
+ sort.Slice(fncall.addrsToPin, func(i, j int) bool { return fncall.addrsToPin[i] < fncall.addrsToPin[j] })
+ fncall.addrsToPin = uniqUint64(fncall.addrsToPin)
+
+ return false // will continue with evalop.CallInjectionComplete2
}
+ callInjectionComplete2(callScope, bi, fncall, regs, thread)
+
case debugCallRegReadPanic: // 2
// read panic value from stack
stack.callInjectionContinue = true
@@ -913,9 +958,29 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
return false
}
+func callInjectionComplete2(callScope *EvalScope, bi *BinaryInfo, fncall *functionCallState, regs Registers, thread Thread) {
+ // Store the stack span of the currently running goroutine (which in Go >=
+ // 1.15 might be different from the original injection goroutine) so that
+ // later on we can use it to perform the escapeCheck
+ if threadg, _ := GetG(thread); threadg != nil {
+ callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
+ }
+ if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
+ oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
+ if err != nil {
+ fncall.err = fmt.Errorf("could not restore LR: %v", err)
+ return
+ }
+ if err = setLR(thread, oldlr); err != nil {
+ fncall.err = fmt.Errorf("could not restore LR: %v", err)
+ return
+ }
+ }
+}
+
func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) {
fncall := stack.fncallPeek()
- if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
+ if !fncall.hasDebugPinner && (fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0) {
funcCallEvalFuncExpr(scope, stack, fncall)
}
stack.pop() // target function, consumed by funcCallEvalFuncExpr either above or in evalop.CallInjectionStart
@@ -944,7 +1009,7 @@ func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTa
if fncall.receiver != nil {
err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread)
if err != nil {
- stack.err = err
+ stack.err = fmt.Errorf("could not set call receiver: %v", err)
return
}
fncall.formalArgs = fncall.formalArgs[1:]
@@ -995,6 +1060,9 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.CallInjectionStartSpecial, curthread Thread) bool {
fnv, err := scope.findGlobalInternal(op.FnName)
if fnv == nil {
+ if err == nil {
+ err = fmt.Errorf("function %s not found", op.FnName)
+ }
stack.err = err
return false
}
@@ -1013,6 +1081,9 @@ func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.C
func (scope *EvalScope) convertAllocToString(stack *evalStack) {
mallocv := stack.pop()
v := stack.pop()
+
+ mallocv.loadValue(loadFullValue)
+
if mallocv.Unreadable != nil {
stack.err = mallocv.Unreadable
return
@@ -1180,7 +1251,10 @@ func debugCallProtocolReg(archName string, version int) (uint64, bool) {
// runtimeWhitelist is a list of functions in the runtime that we can call
// (through call injection) even if they are optimized.
var runtimeWhitelist = map[string]bool{
- "runtime.mallocgc": true,
+ "runtime.mallocgc": true,
+ evalop.DebugPinnerFunctionName: true,
+ "runtime.(*Pinner).Unpin": true,
+ "runtime.(*Pinner).Pin": true,
}
// runtimeOptimizedWorkaround modifies the input DIE so that arguments and
@@ -1227,3 +1301,18 @@ func runtimeOptimizedWorkaround(bi *BinaryInfo, image *Image, in *godwarf.Tree)
}
}
}
+
+func uniqUint64(s []uint64) []uint64 {
+ if len(s) == 0 {
+ return s
+ }
+ src, dst := 1, 1
+ for src < len(s) {
+ if s[src] != s[dst-1] {
+ s[dst] = s[src]
+ dst++
+ }
+ src++
+ }
+ return s[:dst]
+}
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index 5f2ad383a7..530ca5c0f9 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -43,14 +43,25 @@ func (m *memCache) contains(addr uint64, size int) bool {
return addr >= m.cacheAddr && end <= m.cacheAddr+uint64(len(m.cache))
}
+func (m *memCache) load() error {
+ if m.loaded {
+ return nil
+ }
+ _, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
+ if err != nil {
+ return err
+ }
+ m.loaded = true
+ return nil
+}
+
func (m *memCache) ReadMemory(data []byte, addr uint64) (n int, err error) {
if m.contains(addr, len(data)) {
if !m.loaded {
- _, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
+ err := m.load()
if err != nil {
return 0, err
}
- m.loaded = true
}
copy(data, m.cache[addr-m.cacheAddr:])
return len(data), nil
diff --git a/pkg/proc/types.go b/pkg/proc/types.go
index 3facc9ff78..bc8fe2f9b7 100644
--- a/pkg/proc/types.go
+++ b/pkg/proc/types.go
@@ -200,7 +200,10 @@ func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type)
if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
kindv = _type.loadFieldNamed("Kind_")
}
- if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
+ if kindv == nil {
+ return 0, 0, false, fmt.Errorf("unreadable interace type (no kind field)")
+ }
+ if kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
}
typeKind, _ = constant.Uint64Val(kindv.Value)
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 85fb290c2f..6542d34890 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -89,6 +89,8 @@ const (
// variableTrustLen means that when this variable is loaded its length
// should be trusted and used instead of MaxArrayValues
variableTrustLen
+
+ variableSaved
)
// Variable represents a variable. It contains the address, name,
@@ -1235,7 +1237,7 @@ func (v *Variable) maybeDereference() *Variable {
switch t := v.RealType.(type) {
case *godwarf.PtrType:
- if v.Addr == 0 && len(v.Children) == 1 && v.loaded {
+ if (v.Addr == 0 || v.Flags&VariableFakeAddress != 0) && len(v.Children) == 1 && v.loaded {
// fake pointer variable constructed by casting an integer to a pointer type
return &v.Children[0]
}
@@ -1469,7 +1471,7 @@ func convertToEface(srcv, dstv *Variable) error {
}
typeAddr, typeKind, runtimeTypeFound, err := dwarfToRuntimeType(srcv.bi, srcv.mem, srcv.RealType)
if err != nil {
- return err
+ return fmt.Errorf("can not convert value of type %s to %s: %v", srcv.DwarfType.String(), dstv.DwarfType.String(), err)
}
if !runtimeTypeFound || typeKind&kindDirectIface == 0 {
return &typeConvErr{srcv.DwarfType, dstv.RealType}
diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go
index 94f34d79c4..e94c2f2418 100644
--- a/pkg/proc/variables_test.go
+++ b/pkg/proc/variables_test.go
@@ -909,14 +909,14 @@ func getEvalExpressionTestCases() []varTest {
{"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil},
// function call / typecast errors
- {"unknownthing(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
- {"(unknownthing)(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
+ {"unknownthing(1, 2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
+ {"(unknownthing)(1, 2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
{"afunc(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
{"(afunc)(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
- {"(*afunc)(2)", false, "", "", "", errors.New("expression \"afunc\" (func()) can not be dereferenced")},
- {"unknownthing(2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
- {"(*unknownthing)(2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
- {"(*strings.Split)(2)", false, "", "", "", errors.New("could not find symbol value for strings")},
+ {"(*afunc)(2)", false, "", "", "", errors.New("*")},
+ {"unknownthing(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
+ {"(*unknownthing)(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
+ {"(*strings.Split)(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
// pretty printing special types
{"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil},
@@ -997,7 +997,7 @@ func TestEvalExpression(t *testing.T) {
if err == nil {
t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
}
- if tc.err.Error() != err.Error() {
+ if tc.err.Error() != "*" && tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
@@ -1269,9 +1269,10 @@ func TestIssue1075(t *testing.T) {
}
type testCaseCallFunction struct {
- expr string // call expression to evaluate
- outs []string // list of return parameters in this format: ::
- err error // if not nil should return an error
+ expr string // call expression to evaluate
+ outs []string // list of return parameters in this format: ::
+ err error // if not nil should return an error
+ pinCount int // where debugPinner is supported this is the number of pins created during the function call injection
}
func TestCallFunction(t *testing.T) {
@@ -1283,126 +1284,126 @@ func TestCallFunction(t *testing.T) {
var testcases = []testCaseCallFunction{
// Basic function call injection tests
- {"call1(one, two)", []string{":int:3"}, nil},
- {"call1(one+two, 4)", []string{":int:7"}, nil},
- {"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil},
- {`stringsJoin(nil, "")`, []string{`:string:""`}, nil},
- {`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
- {`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil},
- {`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
- {`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
- {`noreturncall(2)`, nil, nil},
+ {"call1(one, two)", []string{":int:3"}, nil, 0},
+ {"call1(one+two, 4)", []string{":int:7"}, nil, 0},
+ {"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil, 0},
+ {`stringsJoin(nil, "")`, []string{`:string:""`}, nil, 0},
+ {`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
+ {`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil, 2},
+ {`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
+ {`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
+ {`noreturncall(2)`, nil, nil, 0},
// Expression tests
- {`square(2) + 1`, []string{":int:5"}, nil},
- {`intcallpanic(1) + 1`, []string{":int:2"}, nil},
- {`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil},
- {`onetwothree(5)[1] + 2`, []string{":int:9"}, nil},
+ {`square(2) + 1`, []string{":int:5"}, nil, 0},
+ {`intcallpanic(1) + 1`, []string{":int:2"}, nil, 0},
+ {`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
+ {`onetwothree(5)[1] + 2`, []string{":int:9"}, nil, 1},
// Call types tests (methods, function pointers, etc.)
// The following set of calls was constructed using https://docs.google.com/document/d/1bMwCey-gmqZVTpRax-ESeVuZGmjwbocYs1iHplK-cjo/pub as a reference
- {`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
+ {`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
- {`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil}, // direct call of a method with pointer receiver / on a value
- {`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil}, // direct call of a method with value receiver / on a pointer
- {`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
+ {`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil, 1}, // direct call of a method with pointer receiver / on a value
+ {`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 1}, // direct call of a method with value receiver / on a pointer
+ {`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 1}, // direct call of a method with pointer receiver / on a pointer
- {`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil}, // indirect call of method on interface / containing value with value method
- {`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil}, // indirect call of method on interface / containing pointer with value method
- {`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
+ {`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil, 1}, // indirect call of method on interface / containing value with value method
+ {`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 1}, // indirect call of method on interface / containing pointer with value method
+ {`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil, 1}, // indirect call of method on interface / containing pointer with pointer method
- {`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent")},
- {`a.nonexistent()`, nil, errors.New("a has no member nonexistent")},
- {`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent")},
- {`vable_a.nonexistent()`, nil, errors.New("vable_a has no member nonexistent")},
- {`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent")},
+ {`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent"), 0},
+ {`a.nonexistent()`, nil, errors.New("a has no member nonexistent"), 0},
+ {`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent"), 0},
+ {`vable_a.nonexistent()`, nil, errors.New("vable_a has no member nonexistent"), 0},
+ {`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent"), 0},
- {`fn2glob(10, 20)`, []string{":int:30"}, nil}, // indirect call of func value / set to top-level func
- {`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil}, // indirect call of func value / set to func literal
- {`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil},
- {`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil}, // indirect call of func value / set to value method
- {`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil}, // indirect call of func value / set to pointer method
+ {`fn2glob(10, 20)`, []string{":int:30"}, nil, 0}, // indirect call of func value / set to top-level func
+ {`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil, 1}, // indirect call of func value / set to func literal
+ {`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil, 1},
+ {`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil, 1}, // indirect call of func value / set to value method
+ {`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil, 1}, // indirect call of func value / set to pointer method
- {"fn2nil()", nil, errors.New("nil pointer dereference")},
+ {"fn2nil()", nil, errors.New("nil pointer dereference"), 0},
- {"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil},
+ {"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil, 1},
- {"x.CallMe()", nil, nil},
- {"x2.CallMe(5)", []string{":int:25"}, nil},
+ {"x.CallMe()", nil, nil, 0},
+ {"x2.CallMe(5)", []string{":int:25"}, nil, 0},
- {"\"delve\".CallMe()", nil, errors.New("\"delve\" (type string) is not a struct")},
+ {"\"delve\".CallMe()", nil, errors.New("\"delve\" (type string) is not a struct"), 0},
// Nested function calls tests
- {`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil},
- {`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil},
- {`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil},
- {`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int")},
+ {`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil, 1},
+ {`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
+ {`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil, 1},
+ {`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int"), 1},
// Variable setting tests
- {`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil},
+ {`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil, 1},
// Escape tests
- {"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2")},
+ {"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2"), 0},
// Issue 1577
- {"1+2", []string{`::3`}, nil},
- {`"de"+"mo"`, []string{`::"demo"`}, nil},
+ {"1+2", []string{`::3`}, nil, 0},
+ {`"de"+"mo"`, []string{`::"demo"`}, nil, 0},
// Issue 3176
- {`ref.String()[0]`, []string{`:byte:98`}, nil},
- {`ref.String()[20]`, nil, errors.New("index out of bounds")},
+ {`ref.String()[0]`, []string{`:byte:98`}, nil, 1},
+ {`ref.String()[20]`, nil, errors.New("index out of bounds"), 1},
}
var testcases112 = []testCaseCallFunction{
// string allocation requires trusted argument order, which we don't have in Go 1.11
- {`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
- {`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil},
+ {`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
+ {`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil, 1},
// support calling optimized functions
- {`strings.Join(nil, "")`, []string{`:string:""`}, nil},
- {`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
- {`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
- {`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
- {`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil},
- {`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil},
- {`d.Base.Method()`, []string{`:int:4`}, nil},
- {`d.Method()`, []string{`:int:4`}, nil},
+ {`strings.Join(nil, "")`, []string{`:string:""`}, nil, 0},
+ {`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
+ {`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
+ {`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
+ {`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil, 0},
+ {`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil, 0},
+ {`d.Base.Method()`, []string{`:int:4`}, nil, 0},
+ {`d.Method()`, []string{`:int:4`}, nil, 0},
}
var testcases113 = []testCaseCallFunction{
- {`curriedAdd(2)(3)`, []string{`:int:5`}, nil},
+ {`curriedAdd(2)(3)`, []string{`:int:5`}, nil, 1},
// Method calls on a value returned by a function
- {`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
+ {`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
- {`getAStruct(3).PRcvr(2)`, nil, errors.New("cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa")}, // direct call of a method with pointer receiver / on a value
- {`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil}, // direct call of a method with value receiver / on a pointer
- {`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
+ {`getAStruct(3).PRcvr(2)`, nil, errors.New("could not set call receiver: cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa"), 0}, // direct call of a method with pointer receiver / on a value
+ {`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 2}, // direct call of a method with value receiver / on a pointer
+ {`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 2}, // direct call of a method with pointer receiver / on a pointer
- {`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil}, // indirect call of method on interface / containing value with value method
- {`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil}, // indirect call of method on interface / containing pointer with value method
- {`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
+ {`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil, 3}, // indirect call of method on interface / containing value with value method
+ {`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 3}, // indirect call of method on interface / containing pointer with value method
+ {`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil, 3}, // indirect call of method on interface / containing pointer with pointer method
}
var testcasesBefore114After112 = []testCaseCallFunction{
- {`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
+ {`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
}
var testcases114 = []testCaseCallFunction{
- {`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
+ {`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
}
var testcases117 = []testCaseCallFunction{
- {`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},
- {`issue3364.String()`, []string{`:string:"1 2"`}, 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},
+ {`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil, 10},
+ {`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, 0},
+ {`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil, 1},
+ {`issue3364.String()`, []string{`:string:"1 2"`}, nil, 1},
+ {`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil, 10},
+ {`floatsum(1, 2)`, []string{":float64:3"}, nil, 0},
}
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
@@ -1444,7 +1445,7 @@ func TestCallFunction(t *testing.T) {
}
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
- testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
+ testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil, 0})
})
}
@@ -1460,6 +1461,12 @@ func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, grp *proc.Targe
}
func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
+ t.Run(tc.expr, func(t *testing.T) {
+ testCallFunctionIntl(t, grp, p, tc)
+ })
+}
+
+func testCallFunctionIntl(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
const unsafePrefix = "-unsafe "
var callExpr, varExpr string
@@ -1490,7 +1497,11 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
}
if err != nil {
- t.Fatalf("call %q: error %q", tc.expr, err.Error())
+ if strings.HasPrefix(err.Error(), "internal debugger error") {
+ t.Fatalf("call %q: error %s", tc.expr, err.Error())
+ } else {
+ t.Fatalf("call %q: error %q", tc.expr, err.Error())
+ }
}
retvalsVar := p.CurrentThread().Common().ReturnValues(pnormalLoadConfig)
@@ -1535,6 +1546,13 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
t.Fatalf("call %q, output parameter %d: expected value %q, got %q", tc.expr, i, tgtValue, cvs)
}
}
+
+ if p.BinInfo().HasDebugPinner() {
+ t.Logf("\t(pins = %d)", proc.DebugPinCount())
+ if proc.DebugPinCount() != tc.pinCount {
+ t.Fatalf("call %q, expected pin count %d, got %d", tc.expr, tc.pinCount, proc.DebugPinCount())
+ }
+ }
}
func TestIssue1531(t *testing.T) {