Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proc: adds pointer pinning to call injection #3787

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions pkg/proc/dwarf_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
derekparker marked this conversation as resolved.
Show resolved Hide resolved
return bi.hasDebugPinner()
}

// 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 @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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-- {
Expand All @@ -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)

Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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 {
Expand Down
Loading