2018-05-04 17:31:45 +00:00
|
|
|
package proc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"debug/dwarf"
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/constant"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
|
2019-01-04 18:39:25 +00:00
|
|
|
"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/logflags"
|
2018-05-04 17:31:45 +00:00
|
|
|
"golang.org/x/arch/x86/x86asm"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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·debugCallV1.
|
|
|
|
//
|
2019-05-09 15:29:58 +00:00
|
|
|
// The main entry point is EvalExpressionWithCalls which will start a goroutine to
|
|
|
|
// evaluate the provided expression.
|
|
|
|
// This goroutine can either return immediately, if no function calls were
|
|
|
|
// needed, or write a continue request to the scope.callCtx.continueRequest
|
|
|
|
// channel. When this happens EvalExpressionWithCalls will call Continue and
|
|
|
|
// return.
|
2018-05-04 17:31:45 +00:00
|
|
|
//
|
2019-05-09 15:29:58 +00:00
|
|
|
// The Continue loop will write to scope.callCtx.continueCompleted 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 evalFunctionCall and funcCallStep.
|
2018-05-04 17:31:45 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
debugCallFunctionNamePrefix1 = "debugCall"
|
|
|
|
debugCallFunctionNamePrefix2 = "runtime.debugCall"
|
|
|
|
debugCallFunctionName = "runtime.debugCallV1"
|
2019-05-30 15:08:37 +00:00
|
|
|
maxArgFrameSize = 65535
|
2018-05-04 17:31:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-08-31 18:08:18 +00:00
|
|
|
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")
|
|
|
|
errNotACallExpr = errors.New("not a function call")
|
|
|
|
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")
|
|
|
|
errNoAddrUnsupported = errors.New("arguments to a function call must have an address")
|
|
|
|
errNotAGoFunction = errors.New("not a Go function")
|
2019-05-09 15:29:58 +00:00
|
|
|
errFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
|
2018-05-04 17:31:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type functionCallState struct {
|
|
|
|
// savedRegs contains the saved registers
|
2018-08-03 15:17:01 +00:00
|
|
|
savedRegs Registers
|
2018-05-04 17:31:45 +00:00
|
|
|
// err contains a saved error
|
|
|
|
err error
|
2019-05-30 15:08:37 +00:00
|
|
|
// expr is the expression being evaluated
|
|
|
|
expr *ast.CallExpr
|
2018-05-04 17:31:45 +00:00
|
|
|
// fn is the function that is being called
|
|
|
|
fn *Function
|
2019-05-30 15:08:37 +00:00
|
|
|
// receiver is the receiver argument for the function
|
|
|
|
receiver *Variable
|
2018-07-31 16:32:30 +00:00
|
|
|
// closureAddr is the address of the closure being called
|
|
|
|
closureAddr uint64
|
2019-05-30 15:08:37 +00:00
|
|
|
// formalArgs are the formal arguments of fn
|
|
|
|
formalArgs []funcCallArg
|
|
|
|
// argFrameSize contains the size of the arguments
|
|
|
|
argFrameSize int64
|
2018-05-04 17:31:45 +00:00
|
|
|
// 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
|
2019-05-30 15:08:37 +00:00
|
|
|
// lateCallFailure is set to true if the function call could not be
|
|
|
|
// completed after we started evaluating the arguments.
|
|
|
|
lateCallFailure bool
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-09 15:29:58 +00:00
|
|
|
type callContext struct {
|
|
|
|
p Process
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// Write to continueRequest to request a call to Continue from the
|
|
|
|
// debugger's main goroutine.
|
|
|
|
// Read from continueCompleted to wait for the target process to stop at
|
|
|
|
// one of the interaction point of the function call protocol.
|
|
|
|
// To signal that evaluation is completed a value will be written to
|
|
|
|
// continueRequest having cont == false and the return values in ret.
|
|
|
|
continueRequest chan<- continueRequest
|
|
|
|
continueCompleted <-chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type continueRequest struct {
|
|
|
|
cont bool
|
|
|
|
err error
|
|
|
|
ret *Variable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (callCtx *callContext) doContinue() {
|
|
|
|
callCtx.continueRequest <- continueRequest{cont: true}
|
|
|
|
<-callCtx.continueCompleted
|
|
|
|
}
|
|
|
|
|
|
|
|
func (callCtx *callContext) doReturn(ret *Variable, err error) {
|
|
|
|
if callCtx == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
callCtx.continueRequest <- continueRequest{cont: false, ret: ret, err: err}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(p Process, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
|
2018-05-04 17:31:45 +00:00
|
|
|
bi := p.BinInfo()
|
|
|
|
if !p.Common().fncallEnabled {
|
2018-08-31 18:08:18 +00:00
|
|
|
return errFuncCallUnsupportedBackend
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2019-05-09 15:29:58 +00:00
|
|
|
if p.Common().continueCompleted != nil {
|
2018-08-31 18:08:18 +00:00
|
|
|
return errFuncCallInProgress
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbgcallfn := bi.LookupFunc[debugCallFunctionName]
|
|
|
|
if dbgcallfn == nil {
|
2018-08-31 18:08:18 +00:00
|
|
|
return errFuncCallUnsupported
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check that the selected goroutine is running
|
|
|
|
g := p.SelectedGoroutine()
|
|
|
|
if g == nil {
|
2018-08-31 18:08:18 +00:00
|
|
|
return errNoGoroutine
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
if g.Status != Grunning || g.Thread == nil {
|
2018-08-31 18:08:18 +00:00
|
|
|
return errGoroutineNotRunning
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-09 15:29:58 +00:00
|
|
|
scope, err := GoroutineScope(p.CurrentThread())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
continueRequest := make(chan continueRequest)
|
|
|
|
continueCompleted := make(chan struct{})
|
|
|
|
|
|
|
|
scope.callCtx = &callContext{
|
|
|
|
p: p,
|
|
|
|
checkEscape: checkEscape,
|
|
|
|
retLoadCfg: retLoadCfg,
|
|
|
|
continueRequest: continueRequest,
|
|
|
|
continueCompleted: continueCompleted,
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Common().continueRequest = continueRequest
|
|
|
|
p.Common().continueCompleted = continueCompleted
|
|
|
|
|
|
|
|
go scope.EvalExpression(expr, retLoadCfg)
|
|
|
|
|
|
|
|
contReq, ok := <-continueRequest
|
|
|
|
if contReq.cont {
|
|
|
|
return Continue(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return finishEvalExpressionWithCalls(p, contReq, ok)
|
|
|
|
}
|
|
|
|
|
|
|
|
func finishEvalExpressionWithCalls(p Process, contReq continueRequest, ok bool) error {
|
|
|
|
var err error
|
|
|
|
if !ok {
|
|
|
|
err = errors.New("internal error EvalExpressionWithCalls didn't return anything")
|
|
|
|
} else if contReq.err != nil {
|
|
|
|
if fpe, ispanic := contReq.err.(fncallPanicErr); ispanic {
|
|
|
|
p.CurrentThread().Common().returnValues = []*Variable{fpe.panicVar}
|
|
|
|
} else {
|
|
|
|
err = contReq.err
|
|
|
|
}
|
|
|
|
} else if contReq.ret.Addr == 0 && contReq.ret.DwarfType == nil {
|
|
|
|
// this is a variable returned by a function call with multiple return values
|
|
|
|
r := make([]*Variable, len(contReq.ret.Children))
|
|
|
|
for i := range contReq.ret.Children {
|
|
|
|
r[i] = &contReq.ret.Children[i]
|
|
|
|
}
|
|
|
|
p.CurrentThread().Common().returnValues = r
|
|
|
|
} else {
|
|
|
|
p.CurrentThread().Common().returnValues = []*Variable{contReq.ret}
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Common().continueRequest = nil
|
|
|
|
close(p.Common().continueCompleted)
|
|
|
|
p.Common().continueCompleted = nil
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// evalFunctionCall evaluates a function call.
|
|
|
|
// If this is a built-in function it's evaluated directly.
|
|
|
|
// Otherwise this will start the function call injection protocol and
|
|
|
|
// request that the target process resumes.
|
|
|
|
// See the comment describing the field EvalScope.callCtx for a description
|
|
|
|
// of the preconditions that make starting the function call protocol
|
|
|
|
// possible.
|
|
|
|
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
|
|
|
|
// description of the protocol.
|
|
|
|
func (scope *EvalScope) evalFunctionCall(node *ast.CallExpr) (*Variable, error) {
|
|
|
|
r, err := scope.evalBuiltinCall(node)
|
|
|
|
if r != nil || err != nil {
|
|
|
|
// it was a builtin call
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
if scope.callCtx == nil {
|
|
|
|
return nil, errFuncCallNotAllowed
|
|
|
|
}
|
|
|
|
|
|
|
|
p := scope.callCtx.p
|
|
|
|
bi := scope.BinInfo
|
|
|
|
if !p.Common().fncallEnabled {
|
|
|
|
return nil, errFuncCallUnsupportedBackend
|
|
|
|
}
|
|
|
|
|
|
|
|
dbgcallfn := bi.LookupFunc[debugCallFunctionName]
|
|
|
|
if dbgcallfn == nil {
|
|
|
|
return nil, errFuncCallUnsupported
|
|
|
|
}
|
|
|
|
|
|
|
|
// check that the selected goroutine is running
|
|
|
|
g := p.SelectedGoroutine()
|
|
|
|
if g == nil {
|
|
|
|
return nil, errNoGoroutine
|
|
|
|
}
|
|
|
|
if g.Status != Grunning || g.Thread == nil {
|
|
|
|
return nil, errGoroutineNotRunning
|
|
|
|
}
|
|
|
|
|
2018-05-04 17:31:45 +00:00
|
|
|
// check that there are at least 256 bytes free on the stack
|
|
|
|
regs, err := g.Thread.Registers(true)
|
|
|
|
if err != nil {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2018-08-03 15:17:01 +00:00
|
|
|
regs = regs.Copy()
|
2018-05-04 17:31:45 +00:00
|
|
|
if regs.SP()-256 <= g.stacklo {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, errNotEnoughStack
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
_, err = regs.Get(int(x86asm.RAX))
|
|
|
|
if err != nil {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, errFuncCallUnsupportedBackend
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
fncall := functionCallState{
|
|
|
|
expr: node,
|
|
|
|
savedRegs: regs,
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
err = funcCallEvalFuncExpr(scope, &fncall, false)
|
2018-05-04 17:31:45 +00:00
|
|
|
if err != nil {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := callOP(bi, g.Thread, regs, dbgcallfn.Entry); err != nil {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
|
2019-05-30 15:08:37 +00:00
|
|
|
if err := writePointer(bi, g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
|
2019-05-09 15:29:58 +00:00
|
|
|
return nil, err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
fncallLog("function call initiated %v frame size %d\n", fncall.fn, fncall.argFrameSize)
|
2018-05-04 17:31:45 +00:00
|
|
|
|
2019-05-09 15:29:58 +00:00
|
|
|
spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(g.stackhi)
|
|
|
|
bpoff := int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(g.stackhi)
|
|
|
|
fboff := scope.Regs.FrameBase - int64(g.stackhi)
|
|
|
|
|
|
|
|
for {
|
|
|
|
scope.callCtx.doContinue()
|
|
|
|
|
|
|
|
g = p.SelectedGoroutine()
|
|
|
|
if g != nil {
|
|
|
|
// adjust the value of registers inside scope
|
|
|
|
for regnum := range scope.Regs.Regs {
|
|
|
|
switch uint64(regnum) {
|
|
|
|
case scope.Regs.PCRegNum, scope.Regs.SPRegNum, scope.Regs.BPRegNum:
|
|
|
|
// leave these alone
|
|
|
|
default:
|
|
|
|
// every other register is dirty and unrecoverable
|
|
|
|
scope.Regs.Regs[regnum] = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(spoff + int64(g.stackhi))
|
|
|
|
scope.Regs.Regs[scope.Regs.BPRegNum].Uint64Val = uint64(bpoff + int64(g.stackhi))
|
|
|
|
scope.Regs.FrameBase = fboff + int64(g.stackhi)
|
|
|
|
scope.Regs.CFA = scope.frameOffset + int64(g.stackhi)
|
|
|
|
}
|
|
|
|
|
|
|
|
finished := funcCallStep(scope, &fncall)
|
|
|
|
if finished {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if fncall.err != nil {
|
|
|
|
return nil, fncall.err
|
|
|
|
}
|
|
|
|
|
|
|
|
if fncall.panicvar != nil {
|
|
|
|
return nil, fncallPanicErr{fncall.panicvar}
|
|
|
|
}
|
|
|
|
switch len(fncall.retvars) {
|
|
|
|
case 0:
|
|
|
|
r := scope.newVariable("", 0, nil, nil)
|
|
|
|
r.loaded = true
|
|
|
|
r.Unreadable = errors.New("no return values")
|
|
|
|
return r, nil
|
|
|
|
case 1:
|
|
|
|
return fncall.retvars[0], nil
|
|
|
|
default:
|
|
|
|
// create a fake variable without address or type to return multiple values
|
|
|
|
r := scope.newVariable("", 0, nil, nil)
|
|
|
|
r.loaded = true
|
|
|
|
r.Children = make([]Variable, len(fncall.retvars))
|
|
|
|
for i := range fncall.retvars {
|
|
|
|
r.Children[i] = *fncall.retvars[i]
|
|
|
|
}
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fncallPanicErr is the error returned if a called function panics
|
|
|
|
type fncallPanicErr struct {
|
|
|
|
panicVar *Variable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err fncallPanicErr) Error() string {
|
|
|
|
return fmt.Sprintf("panic calling a function")
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func fncallLog(fmtstr string, args ...interface{}) {
|
2019-03-27 21:58:36 +00:00
|
|
|
logflags.FnCallLogger().Infof(fmtstr, args...)
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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(uintptr(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 {
|
|
|
|
sp := regs.SP()
|
|
|
|
// push PC on the stack
|
|
|
|
sp -= uint64(bi.Arch.PtrSize())
|
|
|
|
if err := thread.SetSP(sp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := writePointer(bi, thread, sp, regs.PC()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return thread.SetPC(callAddr)
|
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
// 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, fncall *functionCallState, allowCalls bool) error {
|
2019-05-09 15:29:58 +00:00
|
|
|
bi := scope.BinInfo
|
2018-05-04 17:31:45 +00:00
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
if !allowCalls {
|
|
|
|
callCtx := scope.callCtx
|
|
|
|
scope.callCtx = nil
|
|
|
|
defer func() {
|
|
|
|
scope.callCtx = callCtx
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
fnvar, err := scope.evalAST(fncall.expr.Fun)
|
|
|
|
if err == errFuncCallNotAllowed {
|
|
|
|
// we can't determine the frame size because callexpr.Fun can't be
|
|
|
|
// evaluated without enabling function calls, just set up an argument
|
|
|
|
// frame for the maximum possible argument size.
|
|
|
|
fncall.argFrameSize = maxArgFrameSize
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
if fnvar.Kind != reflect.Func {
|
2019-05-30 15:08:37 +00:00
|
|
|
return fmt.Errorf("expression %q is not a function", exprToString(fncall.expr.Fun))
|
2018-07-31 16:32:30 +00:00
|
|
|
}
|
2018-10-29 11:22:03 +00:00
|
|
|
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
|
2018-07-31 16:32:30 +00:00
|
|
|
if fnvar.Unreadable != nil {
|
2019-05-30 15:08:37 +00:00
|
|
|
return fnvar.Unreadable
|
2018-07-31 16:32:30 +00:00
|
|
|
}
|
|
|
|
if fnvar.Base == 0 {
|
2019-05-30 15:08:37 +00:00
|
|
|
return errors.New("nil pointer dereference")
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
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))
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
if !fncall.fn.cu.isgo {
|
|
|
|
return errNotAGoFunction
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
fncall.closureAddr = fnvar.closureAddr
|
|
|
|
|
|
|
|
fncall.argFrameSize, fncall.formalArgs, err = funcCallArgs(fncall.fn, bi, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
argnum := len(fncall.expr.Args)
|
2018-05-04 17:31:45 +00:00
|
|
|
|
2018-07-31 12:50:10 +00:00
|
|
|
if len(fnvar.Children) > 0 {
|
2019-05-30 15:08:37 +00:00
|
|
|
argnum++
|
|
|
|
fncall.receiver = &fnvar.Children[0]
|
|
|
|
fncall.receiver.Name = exprToString(fncall.expr.Fun)
|
2018-07-31 12:50:10 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
|
|
|
|
if argnum > len(fncall.formalArgs) {
|
|
|
|
return errTooManyArguments
|
|
|
|
}
|
|
|
|
if argnum < len(fncall.formalArgs) {
|
|
|
|
return errNotEnoughArguments
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
return nil
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type funcCallArg struct {
|
2018-07-30 19:02:35 +00:00
|
|
|
name string
|
|
|
|
typ godwarf.Type
|
|
|
|
off int64
|
|
|
|
isret bool
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
// funcCallEvalArgs evaluates the arguments of the function call, copying
|
|
|
|
// the into the argument frame starting at argFrameAddr.
|
|
|
|
func funcCallEvalArgs(scope *EvalScope, fncall *functionCallState, argFrameAddr uint64) error {
|
|
|
|
g := scope.callCtx.p.SelectedGoroutine()
|
|
|
|
if g == nil {
|
|
|
|
// this should never happen
|
|
|
|
return errNoGoroutine
|
2018-07-30 19:02:35 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
|
|
|
|
if fncall.receiver != nil {
|
|
|
|
err := funcCallCopyOneArg(g, scope, fncall, fncall.receiver, &fncall.formalArgs[0], argFrameAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fncall.formalArgs = fncall.formalArgs[1:]
|
2018-07-30 19:02:35 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
for i := range fncall.formalArgs {
|
|
|
|
formalArg := &fncall.formalArgs[i]
|
2018-07-30 19:02:35 +00:00
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
actualArg, err := scope.evalAST(fncall.expr.Args[i])
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error evaluating %q as argument %s in function %s: %v", exprToString(fncall.expr.Args[i]), formalArg.name, fncall.fn.Name, err)
|
|
|
|
}
|
|
|
|
actualArg.Name = exprToString(fncall.expr.Args[i])
|
|
|
|
|
|
|
|
err = funcCallCopyOneArg(g, scope, fncall, actualArg, formalArg, argFrameAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-07-30 19:02:35 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
}
|
2018-07-30 19:02:35 +00:00
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
return nil
|
|
|
|
}
|
2018-07-30 19:02:35 +00:00
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
func funcCallCopyOneArg(g *G, scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, argFrameAddr uint64) error {
|
|
|
|
if scope.callCtx.checkEscape {
|
|
|
|
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
|
|
|
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
|
|
|
|
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
|
2018-07-30 19:02:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
|
|
|
|
// by convertToEface.
|
|
|
|
|
|
|
|
formalArgVar := newVariable(formalArg.name, uintptr(formalArg.off+int64(argFrameAddr)), formalArg.typ, scope.BinInfo, scope.Mem)
|
|
|
|
if err := formalArgVar.setValue(actualArg, actualArg.Name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-07-30 19:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) {
|
2018-05-04 17:31:45 +00:00
|
|
|
const CFA = 0x1000
|
2019-05-08 21:06:38 +00:00
|
|
|
vrdr := reader.Variables(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, fn.cu.image.StaticBase), int(^uint(0)>>1), false)
|
2018-05-04 17:31:45 +00:00
|
|
|
|
|
|
|
// typechecks arguments, calculates argument frame size
|
|
|
|
for vrdr.Next() {
|
|
|
|
e := vrdr.Entry()
|
|
|
|
if e.Tag != dwarf.TagFormalParameter {
|
|
|
|
continue
|
|
|
|
}
|
2019-05-08 21:06:38 +00:00
|
|
|
entry, argname, typ, err := readVarEntry(e, fn.cu.image)
|
2018-05-04 17:31:45 +00:00
|
|
|
if err != nil {
|
2018-07-30 19:02:35 +00:00
|
|
|
return 0, nil, err
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
typ = resolveTypedef(typ)
|
2018-07-28 19:12:07 +00:00
|
|
|
locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry)
|
|
|
|
if err != nil {
|
2018-07-30 19:02:35 +00:00
|
|
|
return 0, nil, fmt.Errorf("could not get argument location of %s: %v", argname, err)
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
|
|
|
|
if err != nil {
|
2018-07-30 19:02:35 +00:00
|
|
|
return 0, nil, fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
off -= CFA
|
|
|
|
|
|
|
|
if e := off + typ.Size(); e > argFrameSize {
|
|
|
|
argFrameSize = e
|
|
|
|
}
|
|
|
|
|
2018-07-30 19:02:35 +00:00
|
|
|
if isret, _ := entry.Val(dwarf.AttrVarParam).(bool); !isret || includeRet {
|
|
|
|
formalArgs = append(formalArgs, funcCallArg{name: argname, typ: typ, off: off, isret: isret})
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := vrdr.Err(); err != nil {
|
2018-07-30 19:02:35 +00:00
|
|
|
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(formalArgs, func(i, j int) bool {
|
|
|
|
return formalArgs[i].off < formalArgs[j].off
|
|
|
|
})
|
|
|
|
|
2018-07-30 19:02:35 +00:00
|
|
|
return argFrameSize, formalArgs, nil
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func escapeCheck(v *Variable, name string, g *G) error {
|
|
|
|
switch v.Kind {
|
|
|
|
case reflect.Ptr:
|
2018-08-18 08:53:21 +00:00
|
|
|
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()
|
|
|
|
}
|
2018-05-04 17:31:45 +00:00
|
|
|
return escapeCheckPointer(w.Addr, name, g)
|
|
|
|
case reflect.Chan, reflect.String, reflect.Slice:
|
|
|
|
return escapeCheckPointer(v.Base, name, g)
|
|
|
|
case reflect.Map:
|
|
|
|
sv := v.clone()
|
|
|
|
sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
|
|
|
|
sv = sv.maybeDereference()
|
|
|
|
return escapeCheckPointer(sv.Addr, name, g)
|
|
|
|
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), g); 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), g); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case reflect.Func:
|
2018-07-31 16:32:30 +00:00
|
|
|
if err := escapeCheckPointer(uintptr(v.funcvalAddr()), name, g); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func escapeCheckPointer(addr uintptr, name string, g *G) error {
|
|
|
|
if uint64(addr) >= g.stacklo && uint64(addr) < g.stackhi {
|
2018-07-31 16:32:30 +00:00
|
|
|
return fmt.Errorf("stack object passed to escaping pointer: %s", name)
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
debugCallAXPrecheckFailed = 8
|
|
|
|
debugCallAXCompleteCall = 0
|
|
|
|
debugCallAXReadReturn = 1
|
|
|
|
debugCallAXReadPanic = 2
|
|
|
|
debugCallAXRestoreRegisters = 16
|
|
|
|
)
|
|
|
|
|
2019-05-09 15:29:58 +00:00
|
|
|
// funcCallStep executes one step of the function call injection protocol.
|
2019-05-30 15:08:37 +00:00
|
|
|
func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
|
|
|
|
p := callScope.callCtx.p
|
2018-05-04 17:31:45 +00:00
|
|
|
bi := p.BinInfo()
|
|
|
|
|
|
|
|
thread := p.CurrentThread()
|
|
|
|
regs, err := thread.Registers(false)
|
|
|
|
if err != nil {
|
|
|
|
fncall.err = err
|
2019-05-09 15:29:58 +00:00
|
|
|
return true
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2018-08-03 15:17:01 +00:00
|
|
|
regs = regs.Copy()
|
2018-05-04 17:31:45 +00:00
|
|
|
|
|
|
|
rax, _ := regs.Get(int(x86asm.RAX))
|
|
|
|
|
|
|
|
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 rax=%#x (PC=%#x in %s)\n", rax, pc, fnname)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch rax {
|
|
|
|
case debugCallAXPrecheckFailed:
|
|
|
|
// get error from top of the stack and return it to user
|
|
|
|
errvar, err := readTopstackVariable(thread, regs, "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 debugCallAXCompleteCall:
|
2019-05-30 15:08:37 +00:00
|
|
|
// evaluate arguments of the target function, copy them into its argument frame and call the function
|
|
|
|
if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
|
|
|
|
// if we couldn't figure out which function we are calling before
|
|
|
|
// (because the function we are calling is the return value of a call to
|
|
|
|
// another function) now we have to figure it out by recursively
|
|
|
|
// evaluating the function calls.
|
|
|
|
// This also needs to be done if the function call has a receiver
|
|
|
|
// argument or a closure address (because those addresses could be on the stack
|
|
|
|
// and have changed position between the start of the call and now).
|
|
|
|
|
|
|
|
err := funcCallEvalFuncExpr(callScope, fncall, true)
|
|
|
|
if err != nil {
|
|
|
|
fncall.err = err
|
|
|
|
fncall.lateCallFailure = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
//TODO: double check that function call size isn't too big
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
|
|
|
|
// instead of evaluating the arguments we start first by pushing the call
|
|
|
|
// on the stack, this is the opposite of what would happen normally but
|
|
|
|
// it's necessary because otherwise the GC wouldn't be able to deal with
|
|
|
|
// the argument frame.
|
2018-07-31 16:32:30 +00:00
|
|
|
if fncall.closureAddr != 0 {
|
|
|
|
// When calling a function pointer we must set the DX register to the
|
|
|
|
// address of the function pointer itself.
|
|
|
|
thread.SetDX(fncall.closureAddr)
|
|
|
|
}
|
2018-05-04 17:31:45 +00:00
|
|
|
callOP(bi, thread, regs, fncall.fn.Entry)
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
err := funcCallEvalArgs(callScope, fncall, regs.SP())
|
|
|
|
if err != nil {
|
|
|
|
// rolling back the call, note: this works because we called regs.Copy() above
|
|
|
|
thread.SetSP(regs.SP())
|
|
|
|
thread.SetPC(regs.PC())
|
|
|
|
fncall.err = err
|
|
|
|
fncall.lateCallFailure = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-05-04 17:31:45 +00:00
|
|
|
case debugCallAXRestoreRegisters:
|
|
|
|
// 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 := thread.SetPC(pc); err != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not restore PC: %v", err)
|
|
|
|
}
|
|
|
|
if err := thread.SetSP(sp); err != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not restore SP: %v", err)
|
|
|
|
}
|
|
|
|
if err := stepInstructionOut(p, thread, debugCallFunctionName, debugCallFunctionName); err != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not step out of %s: %v", debugCallFunctionName, err)
|
|
|
|
}
|
2019-05-09 15:29:58 +00:00
|
|
|
return true
|
2018-05-04 17:31:45 +00:00
|
|
|
|
|
|
|
case debugCallAXReadReturn:
|
|
|
|
// read return arguments from stack
|
2019-05-30 15:08:37 +00:00
|
|
|
if fncall.panicvar != nil || fncall.lateCallFailure {
|
2018-05-04 17:31:45 +00:00
|
|
|
break
|
|
|
|
}
|
2019-05-30 15:08:37 +00:00
|
|
|
retScope, err := ThreadScope(thread)
|
2018-05-04 17:31:45 +00:00
|
|
|
if err != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not get return values: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// pretend we are still inside the function we called
|
2019-05-30 15:08:37 +00:00
|
|
|
fakeFunctionEntryScope(retScope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
|
2018-05-04 17:31:45 +00:00
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
fncall.retvars, err = retScope.Locals()
|
2018-05-04 17:31:45 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
|
2019-05-30 15:08:37 +00:00
|
|
|
loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
|
2018-05-04 17:31:45 +00:00
|
|
|
|
|
|
|
case debugCallAXReadPanic:
|
|
|
|
// read panic value from stack
|
2019-05-30 15:08:37 +00:00
|
|
|
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
|
2018-05-04 17:31:45 +00:00
|
|
|
if err != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not get panic: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
fncall.panicvar.Name = "~panic"
|
2019-05-30 15:08:37 +00:00
|
|
|
fncall.panicvar.loadValue(callScope.callCtx.retLoadCfg)
|
2018-05-04 17:31:45 +00:00
|
|
|
if fncall.panicvar.Unreadable != nil {
|
|
|
|
fncall.err = fmt.Errorf("could not get panic: %v", fncall.panicvar.Unreadable)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Got an unknown AX value, this is probably bad but the safest thing
|
|
|
|
// possible is to ignore it and hope it didn't matter.
|
|
|
|
fncallLog("unknown value of AX %#x", rax)
|
|
|
|
}
|
2019-05-09 15:29:58 +00:00
|
|
|
|
|
|
|
return false
|
2018-05-04 17:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func readTopstackVariable(thread Thread, regs Registers, typename string, loadCfg LoadConfig) (*Variable, error) {
|
|
|
|
bi := thread.BinInfo()
|
|
|
|
scope, err := ThreadScope(thread)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
typ, err := bi.findType(typename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
v := scope.newVariable("", uintptr(regs.SP()), typ, scope.Mem)
|
|
|
|
v.loadValue(loadCfg)
|
|
|
|
if v.Unreadable != nil {
|
|
|
|
return nil, v.Unreadable
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fakeEntryScope 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.PCToLine(fn.Entry)
|
|
|
|
|
|
|
|
scope.Regs.CFA = cfa
|
|
|
|
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = sp
|
|
|
|
|
2019-05-08 21:06:38 +00:00
|
|
|
fn.cu.image.dwarfReader.Seek(fn.offset)
|
|
|
|
e, err := fn.cu.image.dwarfReader.Next()
|
2018-05-04 17:31:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
scope.Regs.FrameBase, _, _, _ = scope.BinInfo.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fncall *functionCallState) returnValues() []*Variable {
|
|
|
|
if fncall.panicvar != nil {
|
|
|
|
return []*Variable{fncall.panicvar}
|
|
|
|
}
|
|
|
|
return fncall.retvars
|
|
|
|
}
|