proc: adds pointer pinning to call injection (#3787)

This commit adds a new mode to call injection. If the runtime.debugPinner
function is available in the target executable it obtains a pinner by
calling it and then uses it to pin the pointers in the results of call
injection.

This allows the code for call injection to be refactored to execute the
calls in the normal order, since it doesn't need to be concerned with having
space on the target's memory to store intermediate values.

Updates #3310
This commit is contained in:
Alessandro Arzilli 2024-10-04 19:44:57 +02:00 committed by GitHub
parent 52405ba86b
commit 025d47c6e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 573 additions and 158 deletions

@ -34,6 +34,7 @@ import (
"github.com/go-delve/delve/pkg/internal/gosym"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc/debuginfod"
"github.com/go-delve/delve/pkg/proc/evalop"
"github.com/hashicorp/golang-lru/simplelru"
)
@ -110,7 +111,8 @@ type BinaryInfo struct {
// Go 1.17 register ABI is enabled.
regabi bool
logger logflags.Logger
debugPinnerFn *Function
logger logflags.Logger
}
var (
@ -2574,13 +2576,23 @@ func (bi *BinaryInfo) LookupFunc() map[string][]*Function {
}
func (bi *BinaryInfo) lookupOneFunc(name string) *Function {
if name == evalop.DebugPinnerFunctionName && bi.debugPinnerFn != nil {
return bi.debugPinnerFn
}
fns := bi.LookupFunc()[name]
if fns == nil {
return nil
}
if name == evalop.DebugPinnerFunctionName {
bi.debugPinnerFn = fns[0]
}
return fns[0]
}
func (bi *BinaryInfo) hasDebugPinner() bool {
return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
}
// loadDebugInfoMapsCompileUnit loads entry from a single compile unit.
func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) {
hasAttrGoPkgName := goversion.ProducerAfterOrEqual(cu.producer, 1, 13)

@ -30,3 +30,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.hasDebugPinner()
}
// DebugPinCount returns the number of addresses pinned during the last
// function call injection.
func DebugPinCount() int {
return debugPinCount
}

@ -186,9 +186,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.hasDebugPinner() {
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
}
@ -642,7 +650,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
}
@ -828,9 +836,13 @@ type evalStack struct {
scope *EvalScope
curthread Thread
lastRetiredFncall *functionCallState
debugPinner *Variable
}
func (s *evalStack) push(v *Variable) {
if v == nil {
panic(errors.New("internal debugger error, nil pushed onto variables stack"))
}
s.stack = append(s.stack, v)
}
@ -944,7 +956,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
}
@ -959,25 +971,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
@ -1137,6 +1159,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-- {
@ -1159,9 +1186,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, fncall, regs, curthread)
stack.callInjectionContinue = true
} else {
stack.err = err
}
case *evalop.CallInjectionStartSpecial:
stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)
@ -1173,6 +1220,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)
}
@ -1273,7 +1340,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
}
@ -1289,9 +1356,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
@ -1308,6 +1380,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 {

@ -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,24 @@ type evalLookup interface {
HasBuiltin(string) bool
}
// Flags describes flags used to control Compile and CompileAST
type Flags uint8
const (
CanSet Flags = 1 << iota // Assignment is allowed
HasDebugPinner // runtime.debugPinner is available
)
// 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 +63,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 +87,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 +97,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 +133,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 +153,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 +218,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 +227,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 +576,16 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
id := ctx.curCall
ctx.curCall++
if ctx.flags&HasDebugPinner != 0 {
return ctx.compileFunctionCallWithPinning(node, id)
}
return ctx.compileFunctionCallNoPinning(node, id)
}
// compileFunctionCallNoPinning compiles a function call when runtime.debugPinner is
// not available in the target.
func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int) error {
oldAllowCalls := ctx.allowCalls
oldOps := ctx.ops
ctx.allowCalls = false
@ -570,6 +635,61 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
return nil
}
// compileFunctionCallWithPinning compiles a function call when runtime.debugPinner
// is available in the target.
func (ctx *compileCtx) compileFunctionCallWithPinning(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)

@ -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 }

@ -8,6 +8,7 @@ import (
"go/ast"
"go/constant"
"reflect"
"slices"
"sort"
"strconv"
"strings"
@ -42,6 +43,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 +93,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 +135,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 +188,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 +202,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 +305,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.hasDebugPinner(),
}
if op.HasFunc {
@ -365,10 +389,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 +729,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 +745,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 +770,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 +906,32 @@ 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)
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
})
}
slices.Sort(fncall.addrsToPin)
fncall.addrsToPin = slices.Compact(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
@ -1058,7 +1129,7 @@ func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
// callInjectionProtocol is the function called from Continue to progress
// the injection protocol for all threads.
// Returns true if a call injection terminated
func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
func callInjectionProtocol(t *Target, trapthread Thread, threads []Thread) (done bool, err error) {
if len(t.fncallForG) == 0 {
// we aren't injecting any calls, no need to check the threads.
return false, nil
@ -1072,6 +1143,9 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
if err != nil {
continue
}
if (thread.ThreadID() != trapthread.ThreadID()) && !thread.SoftExc() {
continue
}
if !isCallInjectionStop(t, thread, loc) {
continue
}
@ -1180,7 +1254,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

@ -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

@ -159,7 +159,7 @@ func (grp *TargetGroup) Continue() error {
log.Debugf("\t%d PC=%#x", th.ThreadID(), regs.PC())
}
}
callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads)
callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, trapthread, threads)
callInjectionDone = callInjectionDone || callInjectionDoneThis
if callInjectionDoneThis {
dbp.StopReason = StopCallReturned

@ -206,7 +206,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)

@ -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}

@ -912,14 +912,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, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
{"(unknownthing)(1, 2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
{"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, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
{"(*unknownthing)(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
{"(*strings.Split)(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for strings")},
// pretty printing special types
{"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil},
@ -977,6 +977,18 @@ func getEvalExpressionTestCases() []varTest {
return testcases
}
func altErrors(errs ...string) *altError {
return &altError{errs}
}
type altError struct {
errs []string
}
func (err *altError) Error() string {
return "[multiple alternatives]"
}
func TestEvalExpression(t *testing.T) {
testcases := getEvalExpressionTestCases()
protest.AllowRecording(t)
@ -1000,8 +1012,22 @@ 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() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
switch e := tc.err.(type) {
case *altError:
ok := false
for _, tgtErr := range e.errs {
if tgtErr == err.Error() {
ok = true
break
}
}
if !ok {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
default:
if tc.err.Error() != "*" && tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
}
})
@ -1272,9 +1298,10 @@ func TestIssue1075(t *testing.T) {
}
type testCaseCallFunction struct {
expr string // call expression to evaluate
outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
err error // if not nil should return an error
expr string // call expression to evaluate
outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
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) {
@ -1286,126 +1313,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) {
@ -1447,7 +1474,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})
})
}
@ -1463,6 +1490,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
@ -1493,7 +1526,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)
@ -1538,6 +1575,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) {