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/internal/gosym"
"github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc/debuginfod" "github.com/go-delve/delve/pkg/proc/debuginfod"
"github.com/go-delve/delve/pkg/proc/evalop"
"github.com/hashicorp/golang-lru/simplelru" "github.com/hashicorp/golang-lru/simplelru"
) )
@ -110,7 +111,8 @@ type BinaryInfo struct {
// Go 1.17 register ABI is enabled. // Go 1.17 register ABI is enabled.
regabi bool regabi bool
logger logflags.Logger debugPinnerFn *Function
logger logflags.Logger
} }
var ( var (
@ -2574,13 +2576,23 @@ func (bi *BinaryInfo) LookupFunc() map[string][]*Function {
} }
func (bi *BinaryInfo) lookupOneFunc(name string) *Function { func (bi *BinaryInfo) lookupOneFunc(name string) *Function {
if name == evalop.DebugPinnerFunctionName && bi.debugPinnerFn != nil {
return bi.debugPinnerFn
}
fns := bi.LookupFunc()[name] fns := bi.LookupFunc()[name]
if fns == nil { if fns == nil {
return nil return nil
} }
if name == evalop.DebugPinnerFunctionName {
bi.debugPinnerFn = fns[0]
}
return fns[0] return fns[0]
} }
func (bi *BinaryInfo) hasDebugPinner() bool {
return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
}
// loadDebugInfoMapsCompileUnit loads entry from a single compile unit. // loadDebugInfoMapsCompileUnit loads entry from a single compile unit.
func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) { func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) {
hasAttrGoPkgName := goversion.ProducerAfterOrEqual(cu.producer, 1, 13) 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 { func IsJNZ(inst archInst) bool {
return inst.(*x86Inst).Op == x86asm.JNE 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 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. // EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) { 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 { if err != nil {
return nil, err 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 // SetVariable sets the value of the named variable
func (scope *EvalScope) SetVariable(name, value string) error { 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 { if err != nil {
return err return err
} }
@ -828,9 +836,13 @@ type evalStack struct {
scope *EvalScope scope *EvalScope
curthread Thread curthread Thread
lastRetiredFncall *functionCallState lastRetiredFncall *functionCallState
debugPinner *Variable
} }
func (s *evalStack) push(v *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) s.stack = append(s.stack, v)
} }
@ -944,7 +956,7 @@ func (stack *evalStack) run() {
stack.executeOp() stack.executeOp()
// If the instruction we just executed requests the call injection // If the instruction we just executed requests the call injection
// protocol by setting callInjectionContinue we switch to it. // protocol by setting callInjectionContinue we switch to it.
if stack.callInjectionContinue { if stack.callInjectionContinue && stack.err == nil {
scope.callCtx.injectionThread = nil scope.callCtx.injectionThread = nil
return return
} }
@ -959,25 +971,35 @@ func (stack *evalStack) run() {
// injections before returning. // injections before returning.
if len(stack.fncalls) > 0 { if len(stack.fncalls) > 0 {
fncallLog("undoing calls (%v)", stack.err)
fncall := stack.fncallPeek() fncall := stack.fncallPeek()
if fncall == stack.lastRetiredFncall { if fncall == stack.lastRetiredFncall {
stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err) stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err)
return return
} }
if fncall.undoInjection != nil { if fncall.undoInjection != nil {
// setTargetExecuted is set if evalop.CallInjectionSetTarget has been if fncall.undoInjection.doComplete2 {
// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP // doComplete2 is set if CallInjectionComplete{DoPinning: true} has been
// call in evalop.CallInjectionSetTarget before continuing. // executed but CallInjectionComplete2 hasn't.
switch scope.BinInfo.Arch.Name { regs, err := curthread.Registers()
case "amd64": if err == nil {
regs, _ := curthread.Registers() callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize())) }
setPC(curthread, fncall.undoInjection.oldpc) } else {
case "arm64", "ppc64le": // undoInjection is set if evalop.CallInjectionSetTarget has been
setLR(curthread, fncall.undoInjection.oldlr) // executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
setPC(curthread, fncall.undoInjection.oldpc) // call in evalop.CallInjectionSetTarget before continuing.
default: switch scope.BinInfo.Arch.Name {
panic("not implemented") 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 stack.lastRetiredFncall = fncall
@ -1137,6 +1159,11 @@ func (stack *evalStack) executeOp() {
case *evalop.Pop: case *evalop.Pop:
stack.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: case *evalop.BuiltinCall:
vars := make([]*Variable, len(op.Args)) vars := make([]*Variable, len(op.Args))
for i := len(op.Args) - 1; i >= 0; i-- { 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) stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)
case *evalop.CallInjectionComplete: 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 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: case *evalop.CallInjectionStartSpecial:
stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread) stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)
@ -1173,6 +1220,26 @@ func (stack *evalStack) executeOp() {
rhv := stack.pop() rhv := stack.pop()
stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe)) 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: default:
stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op) 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -1289,9 +1356,14 @@ func exprToString(t ast.Expr) string {
} }
func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) { func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
x := stack.peek() var x *Variable
if op.Pop {
stack.pop() switch op.When {
case evalop.JumpIfTrue, evalop.JumpIfFalse, evalop.JumpIfAllocStringChecksFail:
x = stack.peek()
if op.Pop {
stack.pop()
}
} }
var v bool var v bool
@ -1308,6 +1380,17 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
return return
} }
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 { if x.Kind != reflect.Bool {

@ -18,7 +18,8 @@ import (
) )
var ( 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 { type compileCtx struct {
@ -26,6 +27,8 @@ type compileCtx struct {
ops []Op ops []Op
allowCalls bool allowCalls bool
curCall int curCall int
flags Flags
firstCall bool
} }
type evalLookup interface { type evalLookup interface {
@ -33,14 +36,24 @@ type evalLookup interface {
HasBuiltin(string) bool 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. // CompileAST compiles the expression t into a list of instructions.
func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) { func CompileAST(lookup evalLookup, t ast.Expr, flags Flags) ([]Op, error) {
ctx := &compileCtx{evalLookup: lookup, allowCalls: true} ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err := ctx.compileAST(t) err := ctx.compileAST(t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ctx.compileDebugUnpin()
err = ctx.depthCheck(1) err = ctx.depthCheck(1)
if err != nil { if err != nil {
return ctx.ops, err 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. // Compile compiles the expression expr into a list of instructions.
// If canSet is true expressions like "x = y" are also accepted. // 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) t, err := parser.ParseExpr(expr)
if err != nil { if err != nil {
if canSet { if flags&CanSet != 0 {
eqOff, isAs := isAssignment(err) eqOff, isAs := isAssignment(err)
if isAs { if isAs {
return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:]) return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:], flags)
} }
} }
return nil, err return nil, err
} }
return CompileAST(lookup, t) return CompileAST(lookup, t, flags)
} }
func isAssignment(err error) (int, bool) { 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 // CompileSet compiles the expression setting lhexpr to rhexpr into a list of
// instructions. // 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) lhe, err := parser.ParseExpr(lhexpr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -84,7 +97,7 @@ func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
return nil, err return nil, err
} }
ctx := &compileCtx{evalLookup: lookup, allowCalls: true} ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err = ctx.compileAST(rhe) err = ctx.compileAST(rhe)
if err != nil { if err != nil {
return nil, err return nil, err
@ -120,13 +133,17 @@ func (ctx *compileCtx) compileAllocLiteralString() {
&PushLen{}, &PushLen{},
&PushNil{}, &PushNil{},
&PushConst{constant.MakeBool(false)}, &PushConst{constant.MakeBool(false)},
}) }, true)
ctx.pushOp(&ConvertAllocToString{}) ctx.pushOp(&ConvertAllocToString{})
jmp.Target = len(ctx.ops) 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 id := ctx.curCall
ctx.curCall++ ctx.curCall++
ctx.pushOp(&CallInjectionStartSpecial{ ctx.pushOp(&CallInjectionStartSpecial{
@ -136,11 +153,40 @@ func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args
ctx.pushOp(&CallInjectionSetTarget{id: id}) ctx.pushOp(&CallInjectionSetTarget{id: id})
for i := range args { 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(&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) { 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 { for i, op := range ctx.ops {
npop, npush := op.depthCheck() npop, npush := op.depthCheck()
if depth[i] < npop { if depth[i] < npop {
@ -179,8 +227,15 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
} }
d := depth[i] - npop + npush d := depth[i] - npop + npush
checkAndSet(i+1, d) checkAndSet(i+1, d)
if jmp, _ := op.(*Jump); jmp != nil { switch op := op.(type) {
checkAndSet(jmp.Target, d) 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 { if err != nil {
return err return err
@ -521,6 +576,16 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
id := ctx.curCall id := ctx.curCall
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 oldAllowCalls := ctx.allowCalls
oldOps := ctx.ops oldOps := ctx.ops
ctx.allowCalls = false ctx.allowCalls = false
@ -570,6 +635,61 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
return nil 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 { func Listing(depth []int, ops []Op) string {
if depth == nil { if depth == nil {
depth = make([]int, len(ops)+1) depth = make([]int, len(ops)+1)

@ -164,8 +164,13 @@ type Jump struct {
} }
func (jmpif *Jump) depthCheck() (npop, npush int) { func (jmpif *Jump) depthCheck() (npop, npush int) {
if jmpif.Pop { switch jmpif.When {
return 1, 0 case JumpIfTrue, JumpIfFalse, JumpIfAllocStringChecksFail:
if jmpif.Pop {
return 1, 0
} else {
return 1, 1
}
} }
return 0, 0 return 0, 0
} }
@ -177,6 +182,8 @@ const (
JumpIfFalse JumpCond = iota JumpIfFalse JumpCond = iota
JumpIfTrue JumpIfTrue
JumpIfAllocStringChecksFail JumpIfAllocStringChecksFail
JumpAlways
JumpIfPinningDone
) )
// Binary pops two variables from the stack, applies the specified binary // 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 } 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 // BuiltinCall pops len(Args) argument from the stack, calls the specified
// builtin on them and pushes the result back on the stack. // builtin on them and pushes the result back on the stack.
type BuiltinCall struct { type BuiltinCall struct {
@ -240,11 +254,30 @@ type CallInjectionCopyArg struct {
func (*CallInjectionCopyArg) depthCheck() (npop, npush int) { return 1, 0 } func (*CallInjectionCopyArg) depthCheck() (npop, npush int) { return 1, 0 }
// CallInjectionComplete resumes target execution so that the injected call can run. // 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 { 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 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 // CallInjectionStartSpecial starts call injection for a function with a
// name and arguments known at compile time. // name and arguments known at compile time.
@ -272,3 +305,22 @@ type SetValue struct {
} }
func (*SetValue) depthCheck() (npop, npush int) { return 2, 0 } 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/ast"
"go/constant" "go/constant"
"reflect" "reflect"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -42,6 +43,9 @@ import (
// - evalop.CallInjectionSetTarget // - evalop.CallInjectionSetTarget
// - evalCallInjectionCopyArg // - evalCallInjectionCopyArg
// - evalCallInjectionComplete // - evalCallInjectionComplete
//
// When the target has runtime.debugPinner then evalCallInjectionPinPointer
// must be also called in a loop until it returns false.
const ( const (
debugCallFunctionNamePrefix1 = "debugCall" debugCallFunctionNamePrefix1 = "debugCall"
@ -89,12 +93,20 @@ type functionCallState struct {
// it contains information on how to undo a function call injection without running it // it contains information on how to undo a function call injection without running it
undoInjection *undoInjection 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 protocolReg uint64
debugCallName string debugCallName string
} }
type undoInjection struct { type undoInjection struct {
oldpc, oldlr uint64 oldpc, oldlr uint64
doComplete2 bool
} }
type callContext struct { type callContext struct {
@ -123,10 +135,14 @@ type callInjection struct {
endCallInjection func() 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'. // EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
// Because this can only be done in the current goroutine, unlike // Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope. // EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error { func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
debugPinCount = 0
t := grp.Selected t := grp.Selected
bi := t.BinInfo() bi := t.BinInfo()
if !t.SupportsFunctionCalls() { if !t.SupportsFunctionCalls() {
@ -172,7 +188,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
return err return err
} }
ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, true) ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags()|evalop.CanSet)
if err != nil { if err != nil {
return err return err
} }
@ -186,7 +202,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
} }
stack.eval(scope, ops) stack.eval(scope, ops)
if stack.callInjectionContinue { if stack.callInjectionContinue && stack.err == nil {
return grp.Continue() return grp.Continue()
} }
@ -289,11 +305,19 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
return 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{ fncall := functionCallState{
expr: op.Node, expr: op.Node,
savedRegs: regs, savedRegs: regs,
protocolReg: protocolReg, protocolReg: protocolReg,
debugCallName: dbgcallfn.Name, debugCallName: dbgcallfn.Name,
hasDebugPinner: scope.BinInfo.hasDebugPinner(),
} }
if op.HasFunc { if op.HasFunc {
@ -365,10 +389,18 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID() p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID()
stack.fncallPush(&fncall) 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 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) { func funcCallFinish(scope *EvalScope, stack *evalStack) {
fncall := stack.fncallPop() fncall := stack.fncallPop()
if fncall.err != nil { 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) return fmt.Errorf("escape check for %s failed, variable unreadable: %v", name, v.Unreadable)
} }
switch v.Kind { switch v.Kind {
case reflect.Ptr: case reflect.Ptr, reflect.UnsafePointer:
var w *Variable var w *Variable
if len(v.Children) == 1 { if len(v.Children) == 1 {
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator // 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.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
sv = sv.maybeDereference() sv = sv.maybeDereference()
return f(sv.Addr, name) 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: case reflect.Struct:
t := v.RealType.(*godwarf.StructType) t := v.RealType.(*godwarf.StructType)
for _, field := range t.Field { 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 { if err := f(v.funcvalAddr(), name); err != nil {
return err 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 return nil
@ -864,29 +906,32 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
return (v.Flags & VariableReturnArgument) != 0 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 { for _, v := range fncall.retvars {
v.Flags |= VariableFakeAddress v.Flags |= VariableFakeAddress
} }
// Store the stack span of the currently running goroutine (which in Go >= if fncall.doPinning {
// 1.15 might be different from the original injection goroutine) so that stack.callInjectionContinue = false
// later on we can use it to perform the escapeCheck for _, v := range fncall.retvars {
if threadg, _ := GetG(thread); threadg != nil { saveVariable(v)
callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack) allPointers(v, "", func(addr uint64, _ string) error {
} if addr != 0 && pointerEscapes(addr, callScope.g.stack, callScope.callCtx.stacks) {
if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" { fncall.addrsToPin = append(fncall.addrsToPin, addr)
oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize())) }
if err != nil { return 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
} }
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 case debugCallRegReadPanic: // 2
// read panic value from stack // read panic value from stack
stack.callInjectionContinue = true stack.callInjectionContinue = true
@ -913,9 +958,29 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
return false 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) { func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) {
fncall := stack.fncallPeek() 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) funcCallEvalFuncExpr(scope, stack, fncall)
} }
stack.pop() // target function, consumed by funcCallEvalFuncExpr either above or in evalop.CallInjectionStart 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 { if fncall.receiver != nil {
err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread) err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread)
if err != nil { if err != nil {
stack.err = err stack.err = fmt.Errorf("could not set call receiver: %v", err)
return return
} }
fncall.formalArgs = fncall.formalArgs[1:] 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 { func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.CallInjectionStartSpecial, curthread Thread) bool {
fnv, err := scope.findGlobalInternal(op.FnName) fnv, err := scope.findGlobalInternal(op.FnName)
if fnv == nil { if fnv == nil {
if err == nil {
err = fmt.Errorf("function %s not found", op.FnName)
}
stack.err = err stack.err = err
return false return false
} }
@ -1013,6 +1081,9 @@ func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.C
func (scope *EvalScope) convertAllocToString(stack *evalStack) { func (scope *EvalScope) convertAllocToString(stack *evalStack) {
mallocv := stack.pop() mallocv := stack.pop()
v := stack.pop() v := stack.pop()
mallocv.loadValue(loadFullValue)
if mallocv.Unreadable != nil { if mallocv.Unreadable != nil {
stack.err = mallocv.Unreadable stack.err = mallocv.Unreadable
return return
@ -1058,7 +1129,7 @@ func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
// callInjectionProtocol is the function called from Continue to progress // callInjectionProtocol is the function called from Continue to progress
// the injection protocol for all threads. // the injection protocol for all threads.
// Returns true if a call injection terminated // 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 { if len(t.fncallForG) == 0 {
// we aren't injecting any calls, no need to check the threads. // we aren't injecting any calls, no need to check the threads.
return false, nil return false, nil
@ -1072,6 +1143,9 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
if err != nil { if err != nil {
continue continue
} }
if (thread.ThreadID() != trapthread.ThreadID()) && !thread.SoftExc() {
continue
}
if !isCallInjectionStop(t, thread, loc) { if !isCallInjectionStop(t, thread, loc) {
continue 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 // runtimeWhitelist is a list of functions in the runtime that we can call
// (through call injection) even if they are optimized. // (through call injection) even if they are optimized.
var runtimeWhitelist = map[string]bool{ 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 // 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)) 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) { func (m *memCache) ReadMemory(data []byte, addr uint64) (n int, err error) {
if m.contains(addr, len(data)) { if m.contains(addr, len(data)) {
if !m.loaded { if !m.loaded {
_, err := m.mem.ReadMemory(m.cache, m.cacheAddr) err := m.load()
if err != nil { if err != nil {
return 0, err return 0, err
} }
m.loaded = true
} }
copy(data, m.cache[addr-m.cacheAddr:]) copy(data, m.cache[addr-m.cacheAddr:])
return len(data), nil return len(data), nil

@ -159,7 +159,7 @@ func (grp *TargetGroup) Continue() error {
log.Debugf("\t%d PC=%#x", th.ThreadID(), regs.PC()) log.Debugf("\t%d PC=%#x", th.ThreadID(), regs.PC())
} }
} }
callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads) callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, trapthread, threads)
callInjectionDone = callInjectionDone || callInjectionDoneThis callInjectionDone = callInjectionDone || callInjectionDoneThis
if callInjectionDoneThis { if callInjectionDoneThis {
dbp.StopReason = StopCallReturned 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 { if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
kindv = _type.loadFieldNamed("Kind_") 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) return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
} }
typeKind, _ = constant.Uint64Val(kindv.Value) typeKind, _ = constant.Uint64Val(kindv.Value)

@ -89,6 +89,8 @@ const (
// variableTrustLen means that when this variable is loaded its length // variableTrustLen means that when this variable is loaded its length
// should be trusted and used instead of MaxArrayValues // should be trusted and used instead of MaxArrayValues
variableTrustLen variableTrustLen
variableSaved
) )
// Variable represents a variable. It contains the address, name, // Variable represents a variable. It contains the address, name,
@ -1235,7 +1237,7 @@ func (v *Variable) maybeDereference() *Variable {
switch t := v.RealType.(type) { switch t := v.RealType.(type) {
case *godwarf.PtrType: 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 // fake pointer variable constructed by casting an integer to a pointer type
return &v.Children[0] 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) typeAddr, typeKind, runtimeTypeFound, err := dwarfToRuntimeType(srcv.bi, srcv.mem, srcv.RealType)
if err != nil { 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 { if !runtimeTypeFound || typeKind&kindDirectIface == 0 {
return &typeConvErr{srcv.DwarfType, dstv.RealType} return &typeConvErr{srcv.DwarfType, dstv.RealType}

@ -912,14 +912,14 @@ func getEvalExpressionTestCases() []varTest {
{"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil}, {"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil},
// function call / typecast errors // function call / typecast errors
{"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, "", "", "", 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")},
{"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("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")}, {"(*afunc)(2)", false, "", "", "", errors.New("*")},
{"unknownthing(2)", false, "", "", "", errors.New("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")},
{"(*unknownthing)(2)", false, "", "", "", errors.New("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, "", "", "", errors.New("could not find symbol value for strings")}, {"(*strings.Split)(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for strings")},
// pretty printing special types // pretty printing special types
{"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil}, {"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 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) { func TestEvalExpression(t *testing.T) {
testcases := getEvalExpressionTestCases() testcases := getEvalExpressionTestCases()
protest.AllowRecording(t) protest.AllowRecording(t)
@ -1000,8 +1012,22 @@ func TestEvalExpression(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name) t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
} }
if tc.err.Error() != err.Error() { switch e := tc.err.(type) {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) 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 { type testCaseCallFunction struct {
expr string // call expression to evaluate expr string // call expression to evaluate
outs []string // list of return parameters in this format: <param name>:<param type>:<param value> outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
err error // if not nil should return an error 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) { func TestCallFunction(t *testing.T) {
@ -1286,126 +1313,126 @@ func TestCallFunction(t *testing.T) {
var testcases = []testCaseCallFunction{ var testcases = []testCaseCallFunction{
// Basic function call injection tests // Basic function call injection tests
{"call1(one, two)", []string{":int:3"}, nil}, {"call1(one, two)", []string{":int:3"}, nil, 0},
{"call1(one+two, 4)", []string{":int:7"}, nil}, {"call1(one+two, 4)", []string{":int:7"}, nil, 0},
{"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil}, {"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil, 0},
{`stringsJoin(nil, "")`, []string{`:string:""`}, nil}, {`stringsJoin(nil, "")`, []string{`:string:""`}, nil, 0},
{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil}, {`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
{`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil}, {`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil, 2},
{`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)}, {`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")}, {`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
{`noreturncall(2)`, nil, nil}, {`noreturncall(2)`, nil, nil, 0},
// Expression tests // Expression tests
{`square(2) + 1`, []string{":int:5"}, nil}, {`square(2) + 1`, []string{":int:5"}, nil, 0},
{`intcallpanic(1) + 1`, []string{":int:2"}, nil}, {`intcallpanic(1) + 1`, []string{":int:2"}, nil, 0},
{`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil}, {`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
{`onetwothree(5)[1] + 2`, []string{":int:9"}, nil}, {`onetwothree(5)[1] + 2`, []string{":int:9"}, nil, 1},
// Call types tests (methods, function pointers, etc.) // 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 // 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 {`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}, // direct call of a method with value receiver / on a pointer {`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}, // direct call of a method with pointer 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 {`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}, // indirect call of method on interface / containing pointer 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}, // indirect call of method on interface / containing pointer with pointer 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")}, {`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent"), 0},
{`a.nonexistent()`, nil, errors.New("a has no member nonexistent")}, {`a.nonexistent()`, nil, errors.New("a has no member nonexistent"), 0},
{`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent")}, {`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")}, {`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")}, {`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 {`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}, // indirect call of func value / set to func literal {`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}, {`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil, 1},
{`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil}, // indirect call of func value / set to value method {`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}, // indirect call of func value / set to pointer 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}, {"x.CallMe()", nil, nil, 0},
{"x2.CallMe(5)", []string{":int:25"}, nil}, {"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 // Nested function calls tests
{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil}, {`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}, {`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}, {`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")}, {`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int"), 1},
// Variable setting tests // 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 // 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 // Issue 1577
{"1+2", []string{`::3`}, nil}, {"1+2", []string{`::3`}, nil, 0},
{`"de"+"mo"`, []string{`::"demo"`}, nil}, {`"de"+"mo"`, []string{`::"demo"`}, nil, 0},
// Issue 3176 // Issue 3176
{`ref.String()[0]`, []string{`:byte:98`}, nil}, {`ref.String()[0]`, []string{`:byte:98`}, nil, 1},
{`ref.String()[20]`, nil, errors.New("index out of bounds")}, {`ref.String()[20]`, nil, errors.New("index out of bounds"), 1},
} }
var testcases112 = []testCaseCallFunction{ var testcases112 = []testCaseCallFunction{
// string allocation requires trusted argument order, which we don't have in Go 1.11 // string allocation requires trusted argument order, which we don't have in Go 1.11
{`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil}, {`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
{`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil}, {`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil, 1},
// support calling optimized functions // support calling optimized functions
{`strings.Join(nil, "")`, []string{`:string:""`}, nil}, {`strings.Join(nil, "")`, []string{`:string:""`}, nil, 0},
{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil}, {`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")}, {`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}, {`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil}, {`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil, 0},
{`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil}, {`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil, 0},
{`d.Base.Method()`, []string{`:int:4`}, nil}, {`d.Base.Method()`, []string{`:int:4`}, nil, 0},
{`d.Method()`, []string{`:int:4`}, nil}, {`d.Method()`, []string{`:int:4`}, nil, 0},
} }
var testcases113 = []testCaseCallFunction{ 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 // 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 {`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}, // direct call of a method with value receiver / on a pointer {`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}, // direct call of a method with pointer 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 {`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}, // indirect call of method on interface / containing pointer 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}, // indirect call of method on interface / containing pointer with pointer 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{ 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{ 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{ var testcases117 = []testCaseCallFunction{
{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil}, {`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil, 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}, {`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}, {`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil, 1},
{`issue3364.String()`, []string{`:string:"1 2"`}, nil}, {`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}, {`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}, {`floatsum(1, 2)`, []string{":float64:3"}, nil, 0},
} }
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { 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!!! // 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) { 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 " const unsafePrefix = "-unsafe "
var callExpr, varExpr string var callExpr, varExpr string
@ -1493,7 +1526,11 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
} }
if err != nil { 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) 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) 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) { func TestIssue1531(t *testing.T) {