delve/pkg/proc/fncall.go
2023-11-06 07:55:44 -06:00

1296 lines
39 KiB
Go

package proc
import (
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/token"
"reflect"
"sort"
"strconv"
"strings"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/dwarf/regnum"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc/evalop"
)
// This file implements the function call injection introduced in go1.11.
//
// The protocol is described in $GOROOT/src/runtime/asm_amd64.s in the
// comments for function runtime·debugCallVn.
//
// The main entry point is EvalExpressionWithCalls which will set up an
// evalStack object to evaluate the provided expression.
// This object can either finish immediately, if no function calls were
// needed, or return with callInjectionContinue set. When this happens
// EvalExpressionWithCalls will call Continue and return.
//
// The Continue loop will call evalStack.resume when it hits a breakpoint in
// the call injection protocol.
//
// The work of setting up the function call and executing the protocol is
// done by:
//
// - evalop.CallInjectionStart
// - evalop.CallInjectionSetTarget
// - evalCallInjectionCopyArg
// - evalCallInjectionComplete
const (
debugCallFunctionNamePrefix1 = "debugCall"
debugCallFunctionNamePrefix2 = "runtime.debugCall"
maxDebugCallVersion = 2
maxArgFrameSize = 65535
)
var (
errFuncCallUnsupported = errors.New("function calls not supported by this version of Go")
errFuncCallUnsupportedBackend = errors.New("backend does not support function calls")
errFuncCallInProgress = errors.New("cannot call function while another function call is already in progress")
errNoGoroutine = errors.New("no goroutine selected")
errGoroutineNotRunning = errors.New("selected goroutine not running")
errNotEnoughStack = errors.New("not enough stack space")
errTooManyArguments = errors.New("too many arguments")
errNotEnoughArguments = errors.New("not enough arguments")
errNotAGoFunction = errors.New("not a Go function")
errFuncCallNotAllowedStrAlloc = errors.New("literal string can not be allocated because function calls are not allowed without using 'call'")
)
type functionCallState struct {
// savedRegs contains the saved registers
savedRegs Registers
// err contains a saved error
err error
// expr is the expression being evaluated
expr *ast.CallExpr
// fn is the function that is being called
fn *Function
// receiver is the receiver argument for the function
receiver *Variable
// closureAddr is the address of the closure being called
closureAddr uint64
// formalArgs are the formal arguments of fn
formalArgs []funcCallArg
// argFrameSize contains the size of the arguments
argFrameSize int64
// retvars contains the return variables after the function call terminates without panic'ing
retvars []*Variable
// panicvar is a variable used to store the value of the panic, if the
// called function panics.
panicvar *Variable
// undoInjection is set after evalop.CallInjectionSetTarget runs and cleared by evalCallInjectionComplete
// it contains information on how to undo a function call injection without running it
undoInjection *undoInjection
protocolReg uint64
debugCallName string
}
type undoInjection struct {
oldpc, oldlr uint64
}
type callContext struct {
grp *TargetGroup
p *Target
// checkEscape is true if the escape check should be performed.
// See service/api.DebuggerCommand.UnsafeCall in service/api/types.go.
checkEscape bool
// retLoadCfg is the load configuration used to load return values
retLoadCfg LoadConfig
// injectionThread is the thread to use for nested call injections if the
// original injection goroutine isn't running (because we are in Go 1.15)
injectionThread Thread
// stacks is a slice of known goroutine stacks used to check for
// inappropriate escapes
stacks []stack
}
type callInjection struct {
evalStack *evalStack
startThreadID int
endCallInjection func()
}
// 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 {
t := grp.Selected
bi := t.BinInfo()
if !t.SupportsFunctionCalls() {
return errFuncCallUnsupportedBackend
}
producer := bi.Producer()
if producer == "" || !goversion.ProducerAfterOrEqual(bi.Producer(), 1, 12) {
return errFuncCallUnsupported
}
// check that the target goroutine is running
if g == nil {
return errNoGoroutine
}
if g.Status != Grunning || g.Thread == nil {
return errGoroutineNotRunning
}
if callinj := t.fncallForG[g.ID]; callinj != nil && callinj.evalStack != nil {
return errFuncCallInProgress
}
dbgcallfn, _ := debugCallFunction(bi)
if dbgcallfn == nil {
return errFuncCallUnsupported
}
scope, err := GoroutineScope(t, g.Thread)
if err != nil {
return err
}
scope.callCtx = &callContext{
grp: grp,
p: t,
checkEscape: checkEscape,
retLoadCfg: retLoadCfg,
}
scope.loadCfg = &retLoadCfg
endCallInjection, err := t.proc.StartCallInjection()
if err != nil {
return err
}
ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, true)
if err != nil {
return err
}
stack := &evalStack{}
t.fncallForG[g.ID] = &callInjection{
evalStack: stack,
startThreadID: 0,
endCallInjection: endCallInjection,
}
stack.eval(scope, ops)
if stack.callInjectionContinue {
return grp.Continue()
}
return finishEvalExpressionWithCalls(t, g, stack)
}
func finishEvalExpressionWithCalls(t *Target, g *G, stack *evalStack) error {
fncallLog("stashing return values for %d in thread=%d", g.ID, g.Thread.ThreadID())
g.Thread.Common().CallReturn = true
ret, err := stack.result(&stack.scope.callCtx.retLoadCfg)
if err != nil {
if fpe, ispanic := stack.err.(fncallPanicErr); ispanic {
err = nil
g.Thread.Common().returnValues = []*Variable{fpe.panicVar}
}
} else if ret == nil {
g.Thread.Common().returnValues = nil
} else if ret.Addr == 0 && ret.DwarfType == nil && ret.Kind == reflect.Invalid {
// this is a variable returned by a function call with multiple return values
r := make([]*Variable, len(ret.Children))
for i := range ret.Children {
r[i] = &ret.Children[i]
}
g.Thread.Common().returnValues = r
} else {
g.Thread.Common().returnValues = []*Variable{ret}
}
callinj := t.fncallForG[g.ID]
for goid := range t.fncallForG {
if t.fncallForG[goid] == callinj {
delete(t.fncallForG, goid)
}
}
callinj.evalStack = nil
callinj.endCallInjection()
return err
}
func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, stack *evalStack) {
if scope.callCtx == nil {
stack.err = evalop.ErrFuncCallNotAllowed
return
}
thread := scope.g.Thread
stacklo := scope.g.stack.lo
if thread == nil {
// We are doing a nested function call and using Go 1.15, the original
// injection goroutine was suspended and now we are using a different
// goroutine, evaluation still happened on the original goroutine but we
// need to use a different thread to do the nested call injection.
thread = scope.callCtx.injectionThread
g2, err := GetG(thread)
if err != nil {
stack.err = err
return
}
stacklo = g2.stack.lo
}
if thread == nil {
stack.err = errGoroutineNotRunning
return
}
p := scope.callCtx.p
bi := scope.BinInfo
if !p.SupportsFunctionCalls() {
stack.err = errFuncCallUnsupportedBackend
return
}
dbgcallfn, dbgcallversion := debugCallFunction(bi)
if dbgcallfn == nil {
stack.err = errFuncCallUnsupported
return
}
// check that there are at least 256 bytes free on the stack
regs, err := thread.Registers()
if err != nil {
stack.err = err
return
}
regs, err = regs.Copy()
if err != nil {
stack.err = err
return
}
if regs.SP()-bi.Arch.debugCallMinStackSize <= stacklo {
stack.err = errNotEnoughStack
return
}
protocolReg, ok := debugCallProtocolReg(bi.Arch.Name, dbgcallversion)
if !ok {
stack.err = errFuncCallUnsupported
return
}
if bi.Arch.RegistersToDwarfRegisters(0, regs).Reg(protocolReg) == nil {
stack.err = errFuncCallUnsupportedBackend
return
}
fncall := functionCallState{
expr: op.Node,
savedRegs: regs,
protocolReg: protocolReg,
debugCallName: dbgcallfn.Name,
}
if op.HasFunc {
err = funcCallEvalFuncExpr(scope, stack, &fncall)
if err != nil {
stack.err = err
return
}
}
switch bi.Arch.Name {
case "amd64":
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
stack.err = err
return
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
stack.err = err
return
}
case "arm64", "ppc64le":
// debugCallV2 on arm64 needs a special call sequence, callOP can not be used
sp := regs.SP()
var spOffset uint64
if bi.Arch.Name == "arm64" {
spOffset = 2 * uint64(bi.Arch.PtrSize())
} else {
spOffset = 4 * uint64(bi.Arch.PtrSize())
}
sp -= spOffset
if err := setSP(thread, sp); err != nil {
stack.err = err
return
}
if err := writePointer(bi, scope.Mem, sp, regs.LR()); err != nil {
stack.err = err
return
}
if err := setLR(thread, regs.PC()); err != nil {
stack.err = err
return
}
if err := writePointer(bi, scope.Mem, sp-spOffset, uint64(fncall.argFrameSize)); err != nil {
stack.err = err
return
}
regs, err = thread.Registers()
if err != nil {
stack.err = err
return
}
regs, err = regs.Copy()
if err != nil {
stack.err = err
return
}
fncall.savedRegs = regs
err = setPC(thread, dbgcallfn.Entry)
if err != nil {
stack.err = err
return
}
}
fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID())
thread.Breakpoint().Clear() // since we moved address in PC the thread is no longer stopped at a breakpoint, leaving the breakpoint set will confuse Continue
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.callInjectionContinue = true
}
func funcCallFinish(scope *EvalScope, stack *evalStack) {
fncall := stack.fncallPop()
if fncall.err != nil {
if stack.err == nil {
stack.err = fncall.err
} else {
fncallLog("additional fncall error: %v", fncall.err)
}
return
}
if fncall.panicvar != nil {
if stack.err == nil {
stack.err = fncallPanicErr{fncall.panicvar}
} else {
fncallLog("additional fncall panic: %v", fncall.panicvar)
}
return
}
switch len(fncall.retvars) {
case 0:
r := newVariable("", 0, nil, scope.BinInfo, nil)
r.loaded = true
r.Unreadable = errors.New("no return values")
stack.push(r)
case 1:
stack.push(fncall.retvars[0])
default:
// create a fake variable without address or type to return multiple values
r := newVariable("", 0, nil, scope.BinInfo, nil)
r.loaded = true
r.Children = make([]Variable, len(fncall.retvars))
for i := range fncall.retvars {
r.Children[i] = *fncall.retvars[i]
}
stack.push(r)
}
}
// fncallPanicErr is the error returned if a called function panics
type fncallPanicErr struct {
panicVar *Variable
}
func (err fncallPanicErr) Error() string {
return "panic calling a function"
}
func fncallLog(fmtstr string, args ...interface{}) {
logflags.FnCallLogger().Infof(fmtstr, args...)
}
// writePointer writes val as an architecture pointer at addr in mem.
func writePointer(bi *BinaryInfo, mem MemoryReadWriter, addr, val uint64) error {
ptrbuf := make([]byte, bi.Arch.PtrSize())
// TODO: use target architecture endianness instead of LittleEndian
switch len(ptrbuf) {
case 4:
binary.LittleEndian.PutUint32(ptrbuf, uint32(val))
case 8:
binary.LittleEndian.PutUint64(ptrbuf, val)
default:
panic(fmt.Errorf("unsupported pointer size %d", len(ptrbuf)))
}
_, err := mem.WriteMemory(addr, ptrbuf)
return err
}
// callOP simulates a call instruction on the given thread:
// * pushes the current value of PC on the stack (adjusting SP)
// * changes the value of PC to callAddr
// Note: regs are NOT updated!
func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) error {
switch bi.Arch.Name {
case "amd64":
sp := regs.SP()
// push PC on the stack
sp -= uint64(bi.Arch.PtrSize())
if err := setSP(thread, sp); err != nil {
return err
}
if err := writePointer(bi, thread.ProcessMemory(), sp, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)
case "arm64", "ppc64le":
if err := setLR(thread, regs.PC()); err != nil {
return err
}
return setPC(thread, callAddr)
default:
panic("not implemented")
}
}
// funcCallEvalFuncExpr evaluates expr.Fun and returns the function that we're trying to call.
// If allowCalls is false function calls will be disabled even if scope.callCtx != nil
func funcCallEvalFuncExpr(scope *EvalScope, stack *evalStack, fncall *functionCallState) error {
bi := scope.BinInfo
fnvar := stack.peek()
if fnvar.Kind != reflect.Func {
return fmt.Errorf("expression %q is not a function", exprToString(fncall.expr.Fun))
}
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
if fnvar.Unreadable != nil {
return fnvar.Unreadable
}
if fnvar.Base == 0 {
return errors.New("nil pointer dereference")
}
fncall.fn = bi.PCToFunc(uint64(fnvar.Base))
if fncall.fn == nil {
return fmt.Errorf("could not find DIE for function %q", exprToString(fncall.expr.Fun))
}
if !fncall.fn.cu.isgo {
return errNotAGoFunction
}
fncall.closureAddr = fnvar.closureAddr
var err error
fncall.argFrameSize, fncall.formalArgs, err = funcCallArgs(fncall.fn, bi, false)
if err != nil {
return err
}
argnum := len(fncall.expr.Args)
// If the function variable has a child then that child is the method
// receiver. However, if the method receiver is not being used (e.g.
// func (_ X) Foo()) then it will not actually be listed as a formal
// argument. Ensure that we are really off by 1 to add the receiver to
// the function call.
if len(fnvar.Children) > 0 && argnum == (len(fncall.formalArgs)-1) {
argnum++
fncall.receiver = &fnvar.Children[0]
fncall.receiver.Name = exprToString(fncall.expr.Fun)
}
if argnum > len(fncall.formalArgs) {
return errTooManyArguments
}
if argnum < len(fncall.formalArgs) {
return errNotEnoughArguments
}
return nil
}
type funcCallArg struct {
name string
typ godwarf.Type
off int64
dwarfEntry *godwarf.Tree // non-nil if Go 1.17+
isret bool
}
func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, thread Thread) error {
if scope.callCtx.checkEscape {
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, scope.g.stack); err != nil {
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
}
for _, stack := range scope.callCtx.stacks {
if err := escapeCheck(actualArg, formalArg.name, stack); err != nil {
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
}
}
}
//TODO(aarzilli): automatic wrapping in interfaces for cases not handled
// by convertToEface.
formalScope, err := GoroutineScope(scope.target, thread)
if err != nil {
return err
}
var formalArgVar *Variable
if formalArg.dwarfEntry != nil {
var err error
formalArgVar, err = extractVarInfoFromEntry(scope.target, formalScope.BinInfo, formalScope.image(), formalScope.Regs, formalScope.Mem, formalArg.dwarfEntry, 0)
if err != nil {
return err
}
} else {
formalArgVar = newVariable(formalArg.name, uint64(formalArg.off+int64(formalScope.Regs.CFA)), formalArg.typ, scope.BinInfo, scope.Mem)
}
if err := scope.setValue(formalArgVar, actualArg, actualArg.Name); err != nil {
return err
}
return nil
}
func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) {
dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset)
if err != nil {
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
}
if bi.regabi && fn.cu.optimized && fn.Name != "runtime.mallocgc" {
// Debug info for function arguments on optimized functions is currently
// too incomplete to attempt injecting calls to arbitrary optimized
// functions.
// Prior to regabi we could do this because the ABI was simple enough to
// manually encode it in Delve.
// Runtime.mallocgc is an exception, we specifically patch it's DIE to be
// correct for call injection purposes.
return 0, nil, fmt.Errorf("can not call optimized function %s when regabi is in use", fn.Name)
}
varEntries := reader.Variables(dwarfTree, fn.Entry, int(^uint(0)>>1), reader.VariablesSkipInlinedSubroutines)
// typechecks arguments, calculates argument frame size
for _, entry := range varEntries {
if entry.Tag != dwarf.TagFormalParameter {
continue
}
argname, typ, err := readVarEntry(entry.Tree, fn.cu.image)
if err != nil {
return 0, nil, err
}
typ = resolveTypedef(typ)
var formalArg *funcCallArg
if bi.regabi {
formalArg, err = funcCallArgRegABI(fn, bi, entry, argname, typ, &argFrameSize)
} else {
formalArg, err = funcCallArgOldABI(fn, bi, entry, argname, typ, &argFrameSize)
}
if err != nil {
return 0, nil, err
}
if !formalArg.isret || includeRet {
formalArgs = append(formalArgs, *formalArg)
}
}
if bi.regabi {
// The argument frame size is computed conservatively, assuming that
// there's space for each argument on the stack even if its passed in
// registers. Unfortunately this isn't quite enough because the register
// assignment algorithm Go uses can result in an amount of additional
// space used due to alignment requirements, bounded by the number of argument registers.
// Because we currently don't have an easy way to obtain the frame size,
// let's be even more conservative.
// A safe lower-bound on the size of the argument frame includes space for
// each argument plus the total bytes of register arguments.
// This is derived from worst-case alignment padding of up to
// (pointer-word-bytes - 1) per argument passed in registers.
// See: https://github.com/go-delve/delve/pull/2451#discussion_r665761531
// TODO: Make this generic for other platforms.
argFrameSize = alignAddr(argFrameSize, 8)
argFrameSize += int64(bi.Arch.maxRegArgBytes)
}
sort.Slice(formalArgs, func(i, j int) bool {
return formalArgs[i].off < formalArgs[j].off
})
return argFrameSize, formalArgs, nil
}
func funcCallArgOldABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argname string, typ godwarf.Type, pargFrameSize *int64) (*funcCallArg, error) {
const CFA = 0x1000
var off int64
locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry)
if err != nil {
err = fmt.Errorf("could not get argument location of %s: %v", argname, err)
} else {
var pieces []op.Piece
off, pieces, err = op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog, bi.Arch.PtrSize(), nil)
if err != nil {
err = fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
}
if pieces != nil {
err = fmt.Errorf("unsupported location expression for argument %s (uses DW_OP_piece)", argname)
}
off -= CFA
}
if err != nil {
// With Go version 1.12 or later we can trust that the arguments appear
// in the same order as declared, which means we can calculate their
// address automatically.
// With this we can call optimized functions (which sometimes do not have
// an argument address, due to a compiler bug) as well as runtime
// functions (which are always optimized).
off = *pargFrameSize
off = alignAddr(off, typ.Align())
}
if e := off + typ.Size(); e > *pargFrameSize {
*pargFrameSize = e
}
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
return &funcCallArg{name: argname, typ: typ, off: off, isret: isret}, nil
}
func funcCallArgRegABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argname string, typ godwarf.Type, pargFrameSize *int64) (*funcCallArg, error) {
// Conservatively calculate the full stack argument space for ABI0.
*pargFrameSize = alignAddr(*pargFrameSize, typ.Align())
*pargFrameSize += typ.Size()
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
return &funcCallArg{name: argname, typ: typ, dwarfEntry: entry.Tree, isret: isret}, nil
}
// alignAddr rounds up addr to a multiple of align. Align must be a power of 2.
func alignAddr(addr, align int64) int64 {
return (addr + int64(align-1)) &^ int64(align-1)
}
func escapeCheck(v *Variable, name string, stack stack) error {
if v.Unreadable != nil {
return fmt.Errorf("escape check for %s failed, variable unreadable: %v", name, v.Unreadable)
}
switch v.Kind {
case reflect.Ptr:
var w *Variable
if len(v.Children) == 1 {
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
w = &v.Children[0]
} else {
w = v.maybeDereference()
}
return escapeCheckPointer(w.Addr, name, stack)
case reflect.Chan, reflect.String, reflect.Slice:
return escapeCheckPointer(v.Base, name, stack)
case reflect.Map:
sv := v.clone()
sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
sv = sv.maybeDereference()
return escapeCheckPointer(sv.Addr, name, stack)
case reflect.Struct:
t := v.RealType.(*godwarf.StructType)
for _, field := range t.Field {
fv, _ := v.toField(field)
if err := escapeCheck(fv, fmt.Sprintf("%s.%s", name, field.Name), stack); err != nil {
return err
}
}
case reflect.Array:
for i := int64(0); i < v.Len; i++ {
sv, _ := v.sliceAccess(int(i))
if err := escapeCheck(sv, fmt.Sprintf("%s[%d]", name, i), stack); err != nil {
return err
}
}
case reflect.Func:
if err := escapeCheckPointer(v.funcvalAddr(), name, stack); err != nil {
return err
}
}
return nil
}
func escapeCheckPointer(addr uint64, name string, stack stack) error {
if uint64(addr) >= stack.lo && uint64(addr) < stack.hi {
return fmt.Errorf("stack object passed to escaping pointer: %s", name)
}
return nil
}
const (
debugCallRegPrecheckFailed = 8
debugCallRegCompleteCall = 0
debugCallRegReadReturn = 1
debugCallRegReadPanic = 2
debugCallRegRestoreRegisters = 16
)
// funcCallStep executes one step of the function call injection protocol.
func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
p := callScope.callCtx.p
bi := p.BinInfo()
fncall := stack.fncallPeek()
regs, err := thread.Registers()
if err != nil {
fncall.err = err
return true
}
regval := bi.Arch.RegistersToDwarfRegisters(0, regs).Uint64Val(fncall.protocolReg)
if logflags.FnCall() {
loc, _ := thread.Location()
var pc uint64
var fnname string
if loc != nil {
pc = loc.PC
if loc.Fn != nil {
fnname = loc.Fn.Name
}
}
fncallLog("function call interrupt gid=%d (original) thread=%d regval=%#x (PC=%#x in %s %s:%d)", callScope.g.ID, thread.ThreadID(), regval, pc, fnname, loc.File, loc.Line)
}
switch regval {
case debugCallRegPrecheckFailed: // 8
stack.callInjectionContinue = true
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
} else if bi.Arch.Name == "ppc64le" {
archoff = 40
}
// get error from top of the stack and return it to user
errvar, err := readStackVariable(p, thread, regs, archoff, "string", loadFullValue)
if err != nil {
fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
break
}
errvar.Name = "err"
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
case debugCallRegCompleteCall: // 0
p.fncallForG[callScope.g.ID].startThreadID = 0
case debugCallRegRestoreRegisters: // 16
// runtime requests that we restore the registers (all except pc and sp),
// this is also the last step of the function call protocol.
pc, sp := regs.PC(), regs.SP()
if err := thread.RestoreRegisters(fncall.savedRegs); err != nil {
fncall.err = fmt.Errorf("could not restore registers: %v", err)
}
if err := setPC(thread, pc); err != nil {
fncall.err = fmt.Errorf("could not restore PC: %v", err)
}
if err := setSP(thread, sp); err != nil {
fncall.err = fmt.Errorf("could not restore SP: %v", err)
}
fncallLog("stepping thread %d", thread.ThreadID())
if err := stepInstructionOut(callScope.callCtx.grp, p, thread, fncall.debugCallName, fncall.debugCallName); err != nil {
fncall.err = fmt.Errorf("could not step out of %s: %v", fncall.debugCallName, err)
}
if bi.Arch.Name == "amd64" {
// The tail of debugCallV2 corrupts the state of RFLAGS, we must restore
// it one extra time after stepping out of it.
// See https://github.com/go-delve/delve/issues/2985 and
// TestCallInjectionFlagCorruption
rflags := bi.Arch.RegistersToDwarfRegisters(0, fncall.savedRegs).Uint64Val(regnum.AMD64_Rflags)
err := thread.SetReg(regnum.AMD64_Rflags, op.DwarfRegisterFromUint64(rflags))
if err != nil {
fncall.err = fmt.Errorf("could not restore RFLAGS register: %v", err)
}
}
return true
case debugCallRegReadReturn: // 1
// read return arguments from stack
stack.callInjectionContinue = true
if fncall.panicvar != nil || fncall.err != nil {
break
}
retScope, err := ThreadScope(p, thread)
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
}
// pretend we are still inside the function we called
fakeFunctionEntryScope(retScope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
var flags localsFlags
flags |= localsNoDeclLineCheck // if the function we are calling is an autogenerated stub then declaration lines have no meaning
if !bi.regabi {
flags |= localsTrustArgOrder
}
fncall.retvars, err = retScope.Locals(flags)
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
}
fncall.retvars = filterVariables(fncall.retvars, func(v *Variable) bool {
return (v.Flags & VariableReturnArgument) != 0
})
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
}
}
case debugCallRegReadPanic: // 2
// read panic value from stack
stack.callInjectionContinue = true
archoff := uint64(0)
if bi.Arch.Name == "arm64" {
archoff = 8
} else if bi.Arch.Name == "ppc64le" {
archoff = 32
}
fncall.panicvar, err = readStackVariable(p, thread, regs, archoff, "interface {}", callScope.callCtx.retLoadCfg)
if err != nil {
fncall.err = fmt.Errorf("could not get panic: %v", err)
break
}
fncall.panicvar.Name = "~panic"
default:
// Got an unknown protocol register value, this is probably bad but the safest thing
// possible is to ignore it and hope it didn't matter.
stack.callInjectionContinue = true
fncallLog("unknown value of protocol register %#x", regval)
}
return false
}
func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) {
fncall := stack.fncallPeek()
if 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
regs, err := thread.Registers()
if err != nil {
stack.err = err
return
}
if fncall.closureAddr != 0 {
// When calling a function pointer we must set the DX register to the
// address of the function pointer itself.
setClosureReg(thread, fncall.closureAddr)
}
undo := new(undoInjection)
undo.oldpc = regs.PC()
if scope.BinInfo.Arch.Name == "arm64" || scope.BinInfo.Arch.Name == "ppc64le" {
undo.oldlr = regs.LR()
}
callOP(scope.BinInfo, thread, regs, fncall.fn.Entry)
fncall.undoInjection = undo
if fncall.receiver != nil {
err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread)
if err != nil {
stack.err = err
return
}
fncall.formalArgs = fncall.formalArgs[1:]
}
}
func readStackVariable(t *Target, thread Thread, regs Registers, off uint64, typename string, loadCfg LoadConfig) (*Variable, error) {
bi := thread.BinInfo()
scope, err := ThreadScope(t, thread)
if err != nil {
return nil, err
}
typ, err := bi.findType(typename)
if err != nil {
return nil, err
}
v := newVariable("", regs.SP()+off, typ, scope.BinInfo, scope.Mem)
v.loadValue(loadCfg)
if v.Unreadable != nil {
return nil, v.Unreadable
}
v.Flags |= VariableFakeAddress
return v, nil
}
// fakeFunctionEntryScope alters scope to pretend that we are at the entry point of
// fn and CFA and SP are the ones passed as argument.
// This function is used to create a scope for a call frame that doesn't
// exist anymore, to read the return variables of an injected function call,
// or after a stepout command.
func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64) error {
scope.PC = fn.Entry
scope.Fn = fn
scope.File, scope.Line = scope.BinInfo.EntryLineForFunc(fn)
scope.Regs.CFA = cfa
scope.Regs.Reg(scope.Regs.SPRegNum).Uint64Val = sp
scope.Regs.Reg(scope.Regs.PCRegNum).Uint64Val = fn.Entry
fn.cu.image.dwarfReader.Seek(fn.offset)
e, err := fn.cu.image.dwarfReader.Next()
if err != nil {
return err
}
scope.Regs.FrameBase, _, _, _ = scope.BinInfo.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs, nil)
return nil
}
func (scope *EvalScope) allocString(phase int, stack *evalStack, curthread Thread) bool {
switch phase {
case 0:
x := stack.peek()
if !(x.Kind == reflect.String && x.Addr == 0 && (x.Flags&VariableConstant) != 0 && x.Len > 0) {
stack.opidx += 2 // skip the next two allocString phases, we don't need to do an allocation
return false
}
if scope.callCtx == nil {
// do not complain here, setValue will if no other errors happen
stack.opidx += 2
return false
}
mallocv, err := scope.findGlobal("runtime", "mallocgc")
if mallocv == nil {
stack.err = err
return false
}
stack.push(mallocv)
scope.evalCallInjectionStart(&evalop.CallInjectionStart{HasFunc: true, Node: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "mallocgc"},
},
Args: []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: "0"},
&ast.Ident{Name: "nil"},
&ast.Ident{Name: "false"},
},
}}, stack)
if stack.err == nil {
stack.pop() // return value of evalop.CallInjectionStart
}
return true
case 1:
fncall := stack.fncallPeek()
savedLoadCfg := scope.callCtx.retLoadCfg
scope.callCtx.retLoadCfg = loadFullValue
defer func() {
scope.callCtx.retLoadCfg = savedLoadCfg
}()
scope.evalCallInjectionSetTarget(nil, stack, curthread)
strvar := stack.peek()
stack.err = funcCallCopyOneArg(scope, fncall, newConstant(constant.MakeInt64(strvar.Len), scope.Mem), &fncall.formalArgs[0], curthread)
if stack.err != nil {
return false
}
stack.err = funcCallCopyOneArg(scope, fncall, nilVariable, &fncall.formalArgs[1], curthread)
if stack.err != nil {
return false
}
stack.err = funcCallCopyOneArg(scope, fncall, newConstant(constant.MakeBool(false), scope.Mem), &fncall.formalArgs[2], curthread)
if stack.err != nil {
return false
}
return true
case 2:
mallocv := stack.pop()
v := stack.pop()
if mallocv.Unreadable != nil {
stack.err = mallocv.Unreadable
return false
}
if mallocv.DwarfType.String() != "*void" {
stack.err = fmt.Errorf("unexpected return type for mallocgc call: %v", mallocv.DwarfType.String())
return false
}
if len(mallocv.Children) != 1 {
stack.err = errors.New("internal error, could not interpret return value of mallocgc call")
return false
}
v.Base = mallocv.Children[0].Addr
_, stack.err = scope.Mem.WriteMemory(v.Base, []byte(constant.StringVal(v.Value)))
stack.push(v)
return false
}
panic("unreachable")
}
func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
if loc.Fn == nil {
return false
}
if !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) && !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2) {
return false
}
if loc.PC == loc.Fn.Entry {
// call injection just started, did not make any progress before being interrupted by a concurrent breakpoint.
return false
}
off := int64(0)
if thread.BinInfo().Arch.breakInstrMovesPC {
off = -int64(len(thread.BinInfo().Arch.breakpointInstruction))
}
text, err := disassembleCurrentInstruction(t, thread, off)
if err != nil || len(text) == 0 {
return false
}
return text[0].IsHardBreak()
}
// 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) {
if len(t.fncallForG) == 0 {
// we aren't injecting any calls, no need to check the threads.
return false, nil
}
currentThread := t.currentThread
defer func() {
t.currentThread = currentThread
}()
for _, thread := range threads {
loc, err := thread.Location()
if err != nil {
continue
}
if !isCallInjectionStop(t, thread, loc) {
continue
}
regs, _ := thread.Registers()
fncallLog("call injection found thread=%d %s %s:%d PC=%#x SP=%#x", thread.ThreadID(), loc.Fn.Name, loc.File, loc.Line, regs.PC(), regs.SP())
g, callinj, err := findCallInjectionStateForThread(t, thread)
if err != nil {
return false, err
}
arch := thread.BinInfo().Arch
if !arch.breakInstrMovesPC {
setPC(thread, loc.PC+uint64(len(arch.breakpointInstruction)))
}
fncallLog("step for injection on goroutine %d (current) thread=%d (location %s)", g.ID, thread.ThreadID(), loc.Fn.Name)
t.currentThread = thread
callinj.evalStack.resume(g)
if !callinj.evalStack.callInjectionContinue {
err := finishEvalExpressionWithCalls(t, g, callinj.evalStack)
if err != nil {
return done, err
}
done = true
}
}
return done, nil
}
func findCallInjectionStateForThread(t *Target, thread Thread) (*G, *callInjection, error) {
g, err := GetG(thread)
if err != nil {
return nil, nil, fmt.Errorf("could not determine running goroutine for thread %#x currently executing the function call injection protocol: %v", thread.ThreadID(), err)
}
fncallLog("findCallInjectionStateForThread thread=%d goroutine=%d", thread.ThreadID(), g.ID)
notfound := func() error {
return fmt.Errorf("could not recover call injection state for goroutine %d (thread %d)", g.ID, thread.ThreadID())
}
callinj := t.fncallForG[g.ID]
if callinj != nil {
if callinj.evalStack == nil {
return nil, nil, notfound()
}
return g, callinj, nil
}
// In Go 1.15 and later the call injection protocol will switch to a
// different goroutine.
// Here we try to recover the injection goroutine by checking the injection
// thread.
for goid, callinj := range t.fncallForG {
if callinj != nil && callinj.evalStack != nil && callinj.startThreadID != 0 && callinj.startThreadID == thread.ThreadID() {
t.fncallForG[g.ID] = callinj
fncallLog("goroutine %d is the goroutine executing the call injection started in goroutine %d", g.ID, goid)
return g, callinj, nil
}
}
return nil, nil, notfound()
}
// debugCallFunction searches for the debug call function in the binary and
// uses this search to detect the debug call version.
// Returns the debug call function and its version as an integer (the lowest
// valid version is 1) or nil and zero.
func debugCallFunction(bi *BinaryInfo) (*Function, int) {
for version := maxDebugCallVersion; version >= 1; version-- {
name := debugCallFunctionNamePrefix2 + "V" + strconv.Itoa(version)
fn := bi.lookupOneFunc(name)
if fn != nil {
return fn, version
}
}
return nil, 0
}
// debugCallProtocolReg returns the register ID (as defined in pkg/dwarf/regnum)
// of the register used in the debug call protocol, given the debug call version.
// Also returns a bool indicating whether the version is supported.
func debugCallProtocolReg(archName string, version int) (uint64, bool) {
switch archName {
case "amd64":
var protocolReg uint64
switch version {
case 1:
protocolReg = regnum.AMD64_Rax
case 2:
protocolReg = regnum.AMD64_R12
default:
return 0, false
}
return protocolReg, true
case "arm64", "ppc64le":
if version == 2 {
return regnum.ARM64_X0 + 20, true
}
return 0, false
default:
return 0, false
}
}
type fakeEntry map[dwarf.Attr]*dwarf.Field
func (e fakeEntry) Val(attr dwarf.Attr) interface{} {
if e[attr] == nil {
return nil
}
return e[attr].Val
}
func (e fakeEntry) AttrField(attr dwarf.Attr) *dwarf.Field {
return e[attr]
}
func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
ptrToRuntimeType := "*" + bi.runtimeTypeTypename()
var err1 error
t := func(name string) godwarf.Type {
if err1 != nil {
return nil
}
typ, err := bi.findType(name)
if err != nil {
err1 = err
return nil
}
return typ
}
m := func(name string, typ godwarf.Type, reg int, isret bool) *godwarf.Tree {
if err1 != nil {
return nil
}
var e fakeEntry = map[dwarf.Attr]*dwarf.Field{
dwarf.AttrName: &dwarf.Field{Attr: dwarf.AttrName, Val: name, Class: dwarf.ClassString},
dwarf.AttrType: &dwarf.Field{Attr: dwarf.AttrType, Val: typ.Common().Offset, Class: dwarf.ClassReference},
dwarf.AttrLocation: &dwarf.Field{Attr: dwarf.AttrLocation, Val: []byte{byte(op.DW_OP_reg0) + byte(reg)}, Class: dwarf.ClassBlock},
dwarf.AttrVarParam: &dwarf.Field{Attr: dwarf.AttrVarParam, Val: isret, Class: dwarf.ClassFlag},
}
return &godwarf.Tree{
Entry: e,
Tag: dwarf.TagFormalParameter,
}
}
switch bi.Arch.Name {
case "amd64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.AMD64_Rax, false),
m("typ", t(ptrToRuntimeType), regnum.AMD64_Rbx, false),
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
}
return r, err1
case "arm64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.ARM64_X0, false),
m("typ", t(ptrToRuntimeType), regnum.ARM64_X0+1, false),
m("needzero", t("bool"), regnum.ARM64_X0+2, false),
m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
}
return r, err1
case "ppc64le":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.PPC64LE_R0+3, false),
m("typ", t(ptrToRuntimeType), regnum.PPC64LE_R0+4, false),
m("needzero", t("bool"), regnum.PPC64LE_R0+5, false),
m("~r1", t("unsafe.Pointer"), regnum.PPC64LE_R0+3, true),
}
return r, err1
default:
// do nothing
return nil, nil
}
}