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) {