Skip to content

Commit

Permalink
proc: adds pointer pinning to call injection
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aarzilli committed Jul 30, 2024
1 parent 64f3d34 commit 5e4583b
Show file tree
Hide file tree
Showing 9 changed files with 543 additions and 154 deletions.
12 changes: 12 additions & 0 deletions pkg/proc/dwarf_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proc

import (
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/proc/evalop"
"golang.org/x/arch/x86/x86asm"
)

Expand Down Expand Up @@ -30,3 +31,14 @@ func NewCompositeMemory(p *Target, pieces []op.Piece, base uint64) (*compositeMe
func IsJNZ(inst archInst) bool {
return inst.(*x86Inst).Op == x86asm.JNE
}

// HasDebugPinner returns true if the target has runtime.debugPinner.
func (bi *BinaryInfo) HasDebugPinner() bool {
return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
}

// DebugPinCount returns the number of addresses pinned during the last
// function call injection.
func DebugPinCount() int {
return debugPinCount
}
125 changes: 104 additions & 21 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,17 @@ func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) {
return FrameToScope(t, thread.ProcessMemory(), g, threadID, locations...), nil
}

func (scope *EvalScope) evalopFlags() evalop.Flags {
flags := evalop.Flags(0)
if scope.BinInfo.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil {
flags |= evalop.HasDebugPinner
}
return flags
}

// EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, false)
ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -627,7 +635,7 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {

// SetVariable sets the value of the named variable
func (scope *EvalScope) SetVariable(name, value string) error {
ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value)
ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value, scope.evalopFlags())
if err != nil {
return err
}
Expand Down Expand Up @@ -813,9 +821,13 @@ type evalStack struct {
scope *EvalScope
curthread Thread
lastRetiredFncall *functionCallState
debugPinner *Variable
}

func (s *evalStack) push(v *Variable) {
if v == nil {
panic(fmt.Errorf("internal debugger error, nil pushed onto variables stack"))
}
s.stack = append(s.stack, v)
}

Expand Down Expand Up @@ -929,7 +941,7 @@ func (stack *evalStack) run() {
stack.executeOp()
// If the instruction we just executed requests the call injection
// protocol by setting callInjectionContinue we switch to it.
if stack.callInjectionContinue {
if stack.callInjectionContinue && stack.err == nil {
scope.callCtx.injectionThread = nil
return
}
Expand All @@ -944,25 +956,35 @@ func (stack *evalStack) run() {
// injections before returning.

if len(stack.fncalls) > 0 {
fncallLog("undoing calls (%v)", stack.err)
fncall := stack.fncallPeek()
if fncall == stack.lastRetiredFncall {
stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err)
return
}
if fncall.undoInjection != nil {
// setTargetExecuted is set if evalop.CallInjectionSetTarget has been
// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
// call in evalop.CallInjectionSetTarget before continuing.
switch scope.BinInfo.Arch.Name {
case "amd64":
regs, _ := curthread.Registers()
setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
setPC(curthread, fncall.undoInjection.oldpc)
case "arm64", "ppc64le":
setLR(curthread, fncall.undoInjection.oldlr)
setPC(curthread, fncall.undoInjection.oldpc)
default:
panic("not implemented")
if fncall.undoInjection.doComplete2 {
// doComplete2 is set if CallInjectionComplete{DoPinning: true} has been
// executed but CallInjectionComplete2 hasn't.
regs, err := curthread.Registers()
if err == nil {
callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
}
} else {
// undoInjection is set if evalop.CallInjectionSetTarget has been
// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
// call in evalop.CallInjectionSetTarget before continuing.
switch scope.BinInfo.Arch.Name {
case "amd64":
regs, _ := curthread.Registers()
setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
setPC(curthread, fncall.undoInjection.oldpc)
case "arm64", "ppc64le":
setLR(curthread, fncall.undoInjection.oldlr)
setPC(curthread, fncall.undoInjection.oldpc)
default:
panic("not implemented")
}
}
}
stack.lastRetiredFncall = fncall
Expand Down Expand Up @@ -1122,6 +1144,11 @@ func (stack *evalStack) executeOp() {
case *evalop.Pop:
stack.pop()

case *evalop.Roll:
rolled := stack.stack[len(stack.stack)-op.N-1]
copy(stack.stack[len(stack.stack)-op.N-1:], stack.stack[len(stack.stack)-op.N:])
stack.stack[len(stack.stack)-1] = rolled

case *evalop.BuiltinCall:
vars := make([]*Variable, len(op.Args))
for i := len(op.Args) - 1; i >= 0; i-- {
Expand All @@ -1144,9 +1171,29 @@ func (stack *evalStack) executeOp() {
stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)

case *evalop.CallInjectionComplete:
stack.fncallPeek().undoInjection = nil
fncall := stack.fncallPeek()
fncall.doPinning = op.DoPinning
if op.DoPinning {
fncall.undoInjection.doComplete2 = true
} else {
fncall.undoInjection = nil
}
stack.callInjectionContinue = true

case *evalop.CallInjectionComplete2:
fncall := stack.fncallPeek()
if len(fncall.addrsToPin) != 0 {
stack.err = fmt.Errorf("internal debugger error: CallInjectionComplete2 called when there still are addresses to pin")
}
fncall.undoInjection = nil
regs, err := curthread.Registers()
if err == nil {
callInjectionComplete2(scope, scope.BinInfo, stack.fncallPeek(), regs, curthread)
stack.callInjectionContinue = true
} else {
stack.err = err
}

case *evalop.CallInjectionStartSpecial:
stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)

Expand All @@ -1158,6 +1205,26 @@ func (stack *evalStack) executeOp() {
rhv := stack.pop()
stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe))

case *evalop.PushPinAddress:
debugPinCount++
fncall := stack.fncallPeek()
addrToPin := fncall.addrsToPin[len(fncall.addrsToPin)-1]
fncall.addrsToPin = fncall.addrsToPin[:len(fncall.addrsToPin)-1]
typ, err := scope.BinInfo.findType("unsafe.Pointer")
if ptyp, ok := typ.(*godwarf.PtrType); err == nil && ok {
v := newVariable("", 0, typ, scope.BinInfo, scope.Mem)
v.Children = []Variable{*(newVariable("", uint64(addrToPin), ptyp.Type, scope.BinInfo, scope.Mem))}
stack.push(v)
} else {
stack.err = fmt.Errorf("can not pin address: %v", err)
}

case *evalop.SetDebugPinner:
stack.debugPinner = stack.pop()

case *evalop.PushDebugPinner:
stack.push(stack.debugPinner)

default:
stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op)
}
Expand Down Expand Up @@ -1258,7 +1325,7 @@ func (stack *evalStack) pushIdent(scope *EvalScope, name string) (found bool) {
}

func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t)
ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t, scope.evalopFlags())
if err != nil {
return nil, err
}
Expand All @@ -1274,9 +1341,14 @@ func exprToString(t ast.Expr) string {
}

func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
x := stack.peek()
if op.Pop {
stack.pop()
var x *Variable

switch op.When {
case evalop.JumpIfTrue, evalop.JumpIfFalse, evalop.JumpIfAllocStringChecksFail:
x = stack.peek()
if op.Pop {
stack.pop()
}
}

var v bool
Expand All @@ -1293,6 +1365,17 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
return
}
return
case evalop.JumpAlways:
stack.opidx = op.Target - 1
return
case evalop.JumpIfPinningDone:
fncall := stack.fncallPeek()
if len(fncall.addrsToPin) == 0 {
stack.opidx = op.Target - 1
}
return
default:
panic("internal error, bad jump condition")
}

if x.Kind != reflect.Bool {
Expand Down
Loading

0 comments on commit 5e4583b

Please sign in to comment.