*: function call injection for go 1.11
Implements the function call injection protocol introduced in go 1.11 by https://go-review.googlesource.com/c/go/+/109699. This is only the basic support, see TODO comments in pkg/proc/fncall.go for a list of missing features. Updates #119
This commit is contained in:
parent
8588e97b01
commit
2925c0310a
@ -5,6 +5,7 @@ Command | Description
|
||||
[args](#args) | Print function arguments.
|
||||
[break](#break) | Sets a breakpoint.
|
||||
[breakpoints](#breakpoints) | Print out info for active breakpoints.
|
||||
[call](#call) | Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
[check](#check) | Creates a checkpoint at the current position.
|
||||
[checkpoints](#checkpoints) | Print out info for existing checkpoints.
|
||||
[clear](#clear) | Deletes breakpoint.
|
||||
@ -68,6 +69,27 @@ Print out info for active breakpoints.
|
||||
|
||||
Aliases: bp
|
||||
|
||||
## call
|
||||
Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
|
||||
Current limitations:
|
||||
- can only call package-level functions, no function pointers nor methods.
|
||||
- only things that have an address can be used as arguments (no literal
|
||||
constants or results of evaluating some expressions).
|
||||
- only pointers to stack-allocated objects can be passed as argument.
|
||||
- no automatic type conversions are supported, including automatically
|
||||
converting to an interface type.
|
||||
- functions can only be called on running goroutines that are not
|
||||
executing the runtime.
|
||||
- the current goroutine needs to have at least 256 bytes of free space on
|
||||
the stack.
|
||||
- functions can only be called when the goroutine is stopped at a safe
|
||||
point.
|
||||
- calling a function will resume execution of all goroutines.
|
||||
- only supported on linux's native backend.
|
||||
|
||||
|
||||
|
||||
## check
|
||||
Creates a checkpoint at the current position.
|
||||
|
||||
|
@ -38,6 +38,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -38,6 +38,7 @@ dlv attach pid [executable]
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -33,6 +33,7 @@ dlv connect addr
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -37,6 +37,7 @@ dlv core <executable> <core>
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -44,6 +44,7 @@ dlv debug [package]
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -38,6 +38,7 @@ dlv exec <path/to/binary>
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -37,6 +37,7 @@ dlv replay [trace directory]
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -33,6 +33,7 @@ dlv run
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -44,6 +44,7 @@ dlv test [package]
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -46,6 +46,7 @@ dlv trace [package] regexp
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -33,6 +33,7 @@ dlv version
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
37
_fixtures/fncall.go
Normal file
37
_fixtures/fncall.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func callstacktrace() (stacktrace string) {
|
||||
for skip := 0; ; skip++ {
|
||||
pc, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fn := runtime.FuncForPC(pc)
|
||||
stacktrace += fmt.Sprintf("in %s at %s:%d\n", fn.Name(), file, line)
|
||||
}
|
||||
return stacktrace
|
||||
}
|
||||
|
||||
func call1(a, b int) int {
|
||||
fmt.Printf("first: %d second: %d\n", a, b)
|
||||
return a + b
|
||||
}
|
||||
|
||||
func callpanic() {
|
||||
fmt.Printf("about to panic\n")
|
||||
panic("callpanic panicked")
|
||||
}
|
||||
|
||||
var zero = 0
|
||||
|
||||
func main() {
|
||||
one, two := 1, 2
|
||||
runtime.Breakpoint()
|
||||
call1(one, two)
|
||||
fmt.Println(one, two, zero, callpanic, callstacktrace)
|
||||
}
|
@ -95,6 +95,7 @@ func New(docCall bool) *cobra.Command {
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
Defaults to "debugger" when logging is enabled with --log.`)
|
||||
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
|
||||
RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
|
||||
|
@ -12,6 +12,7 @@ var gdbWire = false
|
||||
var lldbServerOutput = false
|
||||
var debugLineErrors = false
|
||||
var rpc = false
|
||||
var fnCall = false
|
||||
|
||||
// GdbWire returns true if the gdbserial package should log all the packets
|
||||
// exchanged with the stub.
|
||||
@ -41,6 +42,11 @@ func RPC() bool {
|
||||
return rpc
|
||||
}
|
||||
|
||||
// FnCall returns true if the function call protocol should be logged.
|
||||
func FnCall() bool {
|
||||
return fnCall
|
||||
}
|
||||
|
||||
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
||||
|
||||
// Setup sets debugger flags based on the contents of logstr.
|
||||
@ -69,6 +75,8 @@ func Setup(logFlag bool, logstr string) error {
|
||||
debugLineErrors = true
|
||||
case "rpc":
|
||||
rpc = true
|
||||
case "fncall":
|
||||
fnCall = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -474,6 +474,11 @@ func (bi *BinaryInfo) Producer() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Type returns the Dwarf type entry at `offset`.
|
||||
func (bi *BinaryInfo) Type(offset dwarf.Offset) (godwarf.Type, error) {
|
||||
return godwarf.ReadType(bi.dwarf, offset, bi.typeCache)
|
||||
}
|
||||
|
||||
// ELF ///////////////////////////////////////////////////////////////
|
||||
|
||||
// This error is used in openSeparateDebugInfo to signal there's no
|
||||
|
@ -1,7 +1,6 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@ -386,8 +385,6 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
|
||||
return nil
|
||||
}
|
||||
|
||||
bi := thread.BinInfo()
|
||||
|
||||
g, err := GetG(thread)
|
||||
if err != nil {
|
||||
return returnInfoError("could not get g", err, thread)
|
||||
@ -408,22 +405,12 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alter the eval scope so that it looks like we are at the entry point of
|
||||
// the function we just returned from.
|
||||
// This involves changing the location (PC, function...) as well as
|
||||
// anything related to the stack pointer (SP, CFA, FrameBase).
|
||||
scope.PC = rbpi.fn.Entry
|
||||
scope.Fn = rbpi.fn
|
||||
scope.File, scope.Line, _ = bi.PCToLine(rbpi.fn.Entry)
|
||||
scope.Regs.CFA = rbpi.frameOffset + int64(g.stackhi)
|
||||
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(rbpi.spOffset + int64(g.stackhi))
|
||||
|
||||
bi.dwarfReader.Seek(rbpi.fn.offset)
|
||||
e, err := bi.dwarfReader.Next()
|
||||
oldFrameOffset := rbpi.frameOffset + int64(g.stackhi)
|
||||
oldSP := uint64(rbpi.spOffset + int64(g.stackhi))
|
||||
err = fakeFunctionEntryScope(scope, rbpi.fn, oldFrameOffset, oldSP)
|
||||
if err != nil {
|
||||
return returnInfoError("could not read function entry", err, thread)
|
||||
}
|
||||
scope.Regs.FrameBase, _, _, _ = bi.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs)
|
||||
|
||||
vars, err := scope.Locals()
|
||||
if err != nil {
|
||||
|
@ -239,6 +239,10 @@ func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (t *Thread) RestoreRegisters(proc.SavedRegisters) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
func (t *Thread) Arch() proc.Arch {
|
||||
return t.p.bi.Arch
|
||||
}
|
||||
@ -263,6 +267,14 @@ func (t *Thread) Common() *proc.CommonThread {
|
||||
return &t.common
|
||||
}
|
||||
|
||||
func (t *Thread) SetPC(uint64) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
func (t *Thread) SetSP(uint64) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
func (p *Process) Breakpoints() *proc.BreakpointMap {
|
||||
return &p.breakpoints
|
||||
}
|
||||
@ -409,3 +421,7 @@ func (r *Registers) Slice() []proc.Register {
|
||||
out = append(out, r.fpregs...)
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Registers) Save() proc.SavedRegisters {
|
||||
return r
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -237,10 +236,6 @@ func (r *LinuxCoreRegisters) Get(n int) (uint64, error) {
|
||||
return 0, proc.UnknownRegisterError
|
||||
}
|
||||
|
||||
func (r *LinuxCoreRegisters) SetPC(proc.Thread, uint64) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
// readCore reads a core file from corePath corresponding to the executable at
|
||||
// exePath. For details on the Linux ELF core format, see:
|
||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
||||
|
545
pkg/proc/fncall.go
Normal file
545
pkg/proc/fncall.go
Normal file
@ -0,0 +1,545 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/parser"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"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.
|
||||
//
|
||||
// There are two main entry points here. The first one is CallFunction which
|
||||
// evaluates a function call expression, sets up the function call on the
|
||||
// selected goroutine and resumes execution of the process.
|
||||
//
|
||||
// The second one is (*FunctionCallState).step() which is called every time
|
||||
// the process stops at a breakpoint inside one of the debug injcetion
|
||||
// functions.
|
||||
|
||||
const (
|
||||
debugCallFunctionNamePrefix1 = "debugCall"
|
||||
debugCallFunctionNamePrefix2 = "runtime.debugCall"
|
||||
debugCallFunctionName = "runtime.debugCallV1"
|
||||
)
|
||||
|
||||
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")
|
||||
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")
|
||||
)
|
||||
|
||||
type functionCallState struct {
|
||||
// inProgress is true if a function call is in progress
|
||||
inProgress bool
|
||||
// finished is true if the function call terminated
|
||||
finished bool
|
||||
// savedRegs contains the saved registers
|
||||
savedRegs SavedRegisters
|
||||
// expr contains an expression describing the current function call
|
||||
expr string
|
||||
// err contains a saved error
|
||||
err error
|
||||
// fn is the function that is being called
|
||||
fn *Function
|
||||
// argmem contains the argument frame of this function call
|
||||
argmem []byte
|
||||
// retvars contains the return variables after the function call terminates without panic'ing
|
||||
retvars []*Variable
|
||||
// retLoadCfg is the load configuration used to load return values
|
||||
retLoadCfg *LoadConfig
|
||||
// panicvar is a variable used to store the value of the panic, if the
|
||||
// called function panics.
|
||||
panicvar *Variable
|
||||
}
|
||||
|
||||
// CallFunction starts a debugger injected function call on the current thread of p.
|
||||
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
|
||||
// description of the protocol.
|
||||
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
||||
bi := p.BinInfo()
|
||||
if !p.Common().fncallEnabled {
|
||||
return ErrFuncCallUnsupportedBackend
|
||||
}
|
||||
fncall := &p.Common().fncallState
|
||||
if fncall.inProgress {
|
||||
return ErrFuncCallInProgress
|
||||
}
|
||||
|
||||
*fncall = functionCallState{}
|
||||
|
||||
dbgcallfn := bi.LookupFunc[debugCallFunctionName]
|
||||
if dbgcallfn == nil {
|
||||
return ErrFuncCallUnsupported
|
||||
}
|
||||
|
||||
// check that the selected goroutine is running
|
||||
g := p.SelectedGoroutine()
|
||||
if g == nil {
|
||||
return ErrNoGoroutine
|
||||
}
|
||||
if g.Status != Grunning || g.Thread == nil {
|
||||
return ErrGoroutineNotRunning
|
||||
}
|
||||
|
||||
// check that there are at least 256 bytes free on the stack
|
||||
regs, err := g.Thread.Registers(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if regs.SP()-256 <= g.stacklo {
|
||||
return ErrNotEnoughStack
|
||||
}
|
||||
_, err = regs.Get(int(x86asm.RAX))
|
||||
if err != nil {
|
||||
return ErrFuncCallUnsupportedBackend
|
||||
}
|
||||
|
||||
fn, argvars, err := funcCallEvalExpr(p, expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
argmem, err := funcCallArgFrame(fn, argvars, g, bi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := callOP(bi, g.Thread, regs, dbgcallfn.Entry); err != nil {
|
||||
return err
|
||||
}
|
||||
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
|
||||
if err := writePointer(bi, g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(len(argmem))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fncall.inProgress = true
|
||||
fncall.savedRegs = regs.Save()
|
||||
fncall.expr = expr
|
||||
fncall.fn = fn
|
||||
fncall.argmem = argmem
|
||||
fncall.retLoadCfg = retLoadCfg
|
||||
|
||||
fncallLog("function call initiated %v frame size %d\n", fn, len(argmem))
|
||||
|
||||
return Continue(p)
|
||||
}
|
||||
|
||||
func fncallLog(fmtstr string, args ...interface{}) {
|
||||
if !logflags.FnCall() {
|
||||
return
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"layer": "proc", "kind": "fncall"}).Debugf(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(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)
|
||||
}
|
||||
|
||||
// funcCallEvalExpr evaluates expr, which must be a function call, returns
|
||||
// the function being called and its arguments.
|
||||
func funcCallEvalExpr(p Process, expr string) (fn *Function, argvars []*Variable, err error) {
|
||||
bi := p.BinInfo()
|
||||
scope, err := GoroutineScope(p.CurrentThread())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
callexpr, iscall := t.(*ast.CallExpr)
|
||||
if !iscall {
|
||||
return nil, nil, ErrNotACallExpr
|
||||
}
|
||||
|
||||
//TODO(aarzilli): must evaluate <var>.<method> and treat them appropriately
|
||||
fnvar, err := scope.evalAST(callexpr.Fun)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if fnvar.Kind != reflect.Func {
|
||||
return nil, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
|
||||
}
|
||||
fn = bi.PCToFunc(uint64(fnvar.Base))
|
||||
if fn == nil {
|
||||
return nil, nil, fmt.Errorf("could not find DIE for function %q", exprToString(callexpr.Fun))
|
||||
}
|
||||
if !fn.cu.isgo {
|
||||
return nil, nil, ErrNotAGoFunction
|
||||
}
|
||||
|
||||
argvars = make([]*Variable, len(callexpr.Args))
|
||||
for i := range callexpr.Args {
|
||||
argvars[i], err = scope.evalAST(callexpr.Args[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
argvars[i].Name = exprToString(callexpr.Args[i])
|
||||
}
|
||||
|
||||
return fn, argvars, nil
|
||||
}
|
||||
|
||||
type funcCallArg struct {
|
||||
name string
|
||||
typ godwarf.Type
|
||||
off int64
|
||||
}
|
||||
|
||||
// funcCallArgFrame checks type and pointer escaping for the arguments and
|
||||
// returns the argument frame.
|
||||
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) {
|
||||
const CFA = 0x1000
|
||||
vrdr := reader.Variables(bi.dwarf, fn.offset, fn.Entry, int(^uint(0)>>1), false)
|
||||
formalArgs := []funcCallArg{}
|
||||
|
||||
// typechecks arguments, calculates argument frame size
|
||||
argFrameSize := int64(0)
|
||||
for vrdr.Next() {
|
||||
e := vrdr.Entry()
|
||||
if e.Tag != dwarf.TagFormalParameter {
|
||||
continue
|
||||
}
|
||||
entry, argname, typ, err := readVarEntry(e, bi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typ = resolveTypedef(typ)
|
||||
locprog, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported location expression for argument %s", argname)
|
||||
}
|
||||
off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
|
||||
}
|
||||
|
||||
off -= CFA
|
||||
|
||||
if e := off + typ.Size(); e > argFrameSize {
|
||||
argFrameSize = e
|
||||
}
|
||||
|
||||
if isret, _ := entry.Val(dwarf.AttrVarParam).(bool); !isret {
|
||||
formalArgs = append(formalArgs, funcCallArg{name: argname, typ: typ, off: off})
|
||||
}
|
||||
}
|
||||
if err := vrdr.Err(); err != nil {
|
||||
return nil, fmt.Errorf("DWARF read error: %v", err)
|
||||
}
|
||||
if len(actualArgs) > len(formalArgs) {
|
||||
return nil, ErrTooManyArguments
|
||||
}
|
||||
if len(actualArgs) < len(formalArgs) {
|
||||
return nil, ErrNotEnoughArguments
|
||||
}
|
||||
|
||||
sort.Slice(formalArgs, func(i, j int) bool {
|
||||
return formalArgs[i].off < formalArgs[j].off
|
||||
})
|
||||
|
||||
// constructs arguments frame
|
||||
argmem = make([]byte, argFrameSize)
|
||||
for i := range formalArgs {
|
||||
formalArg := &formalArgs[i]
|
||||
actualArg := actualArgs[i]
|
||||
|
||||
if actualArg.Addr == 0 {
|
||||
//TODO(aarzilli): at least some of this needs to be supported
|
||||
return nil, ErrNoAddrUnsupported
|
||||
}
|
||||
|
||||
if actualArg.RealType != formalArg.typ {
|
||||
return nil, fmt.Errorf("cannot use %s (type %s) as type %s in argument to %s", actualArg.Name, actualArg.DwarfType.String(), formalArg.typ.String(), fn.Name)
|
||||
}
|
||||
|
||||
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
||||
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
|
||||
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err)
|
||||
}
|
||||
|
||||
//TODO(aarzilli): automatic type conversions
|
||||
//TODO(aarzilli): automatic wrapping in interfaces?
|
||||
|
||||
buf := make([]byte, actualArg.RealType.Size())
|
||||
sz, err := actualArg.mem.ReadMemory(buf, actualArg.Addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read argument %s: %v", actualArg.Name, err)
|
||||
}
|
||||
if int64(sz) != actualArg.RealType.Size() {
|
||||
return nil, fmt.Errorf("short read for argument %s: %d != %d %x", actualArg.Name, sz, actualArg.RealType.Size(), buf)
|
||||
}
|
||||
|
||||
copy(argmem[formalArg.off:], buf)
|
||||
}
|
||||
|
||||
return argmem, nil
|
||||
}
|
||||
|
||||
func escapeCheck(v *Variable, name string, g *G) error {
|
||||
switch v.Kind {
|
||||
case reflect.Ptr:
|
||||
w := v.maybeDereference()
|
||||
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:
|
||||
//TODO(aarzilli): check closure argument?
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeCheckPointer(addr uintptr, name string, g *G) error {
|
||||
if uint64(addr) >= g.stacklo && uint64(addr) < g.stackhi {
|
||||
return fmt.Errorf("stack object passed to escaping pointer %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
debugCallAXPrecheckFailed = 8
|
||||
debugCallAXCompleteCall = 0
|
||||
debugCallAXReadReturn = 1
|
||||
debugCallAXReadPanic = 2
|
||||
debugCallAXRestoreRegisters = 16
|
||||
)
|
||||
|
||||
func (fncall *functionCallState) step(p Process) {
|
||||
bi := p.BinInfo()
|
||||
|
||||
thread := p.CurrentThread()
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
fncall.err = err
|
||||
fncall.finished = true
|
||||
fncall.inProgress = false
|
||||
return
|
||||
}
|
||||
|
||||
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:
|
||||
// write arguments to the stack, call final function
|
||||
n, err := thread.WriteMemory(uintptr(regs.SP()), fncall.argmem)
|
||||
if err != nil {
|
||||
fncall.err = fmt.Errorf("could not write arguments: %v", err)
|
||||
}
|
||||
if n != len(fncall.argmem) {
|
||||
fncall.err = fmt.Errorf("short argument write: %d %d", n, len(fncall.argmem))
|
||||
}
|
||||
//TODO(aarzilli): if fncall.fn is a function closure CX needs to be set here
|
||||
callOP(bi, thread, regs, fncall.fn.Entry)
|
||||
|
||||
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.
|
||||
fncall.finished = true
|
||||
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)
|
||||
}
|
||||
|
||||
case debugCallAXReadReturn:
|
||||
// read return arguments from stack
|
||||
if fncall.retLoadCfg == nil || fncall.panicvar != nil {
|
||||
break
|
||||
}
|
||||
scope, err := ThreadScope(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(scope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
|
||||
|
||||
fncall.retvars, err = scope.Locals()
|
||||
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, *fncall.retLoadCfg)
|
||||
|
||||
case debugCallAXReadPanic:
|
||||
// read panic value from stack
|
||||
if fncall.retLoadCfg == nil {
|
||||
return
|
||||
}
|
||||
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", *fncall.retLoadCfg)
|
||||
if err != nil {
|
||||
fncall.err = fmt.Errorf("could not get panic: %v", err)
|
||||
break
|
||||
}
|
||||
fncall.panicvar.Name = "~panic"
|
||||
fncall.panicvar.loadValue(*fncall.retLoadCfg)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
scope.BinInfo.dwarfReader.Seek(fn.offset)
|
||||
e, err := scope.BinInfo.dwarfReader.Next()
|
||||
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
|
||||
}
|
@ -1197,6 +1197,11 @@ func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
||||
return &t.regs, nil
|
||||
}
|
||||
|
||||
func (t *Thread) RestoreRegisters(regs proc.SavedRegisters) error {
|
||||
//TODO(aarzilli): implement
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (t *Thread) Arch() proc.Arch {
|
||||
return t.p.bi.Arch
|
||||
}
|
||||
@ -1279,25 +1284,29 @@ func (p *Process) loadGInstr() []byte {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (regs *gdbRegisters) init(regsInfo []gdbRegisterInfo) {
|
||||
regs.regs = make(map[string]gdbRegister)
|
||||
regs.regsInfo = regsInfo
|
||||
|
||||
regsz := 0
|
||||
for _, reginfo := range regsInfo {
|
||||
if endoff := reginfo.Offset + (reginfo.Bitsize / 8); endoff > regsz {
|
||||
regsz = endoff
|
||||
}
|
||||
}
|
||||
regs.buf = make([]byte, regsz)
|
||||
for _, reginfo := range regsInfo {
|
||||
regs.regs[reginfo.Name] = gdbRegister{regnum: reginfo.Regnum, value: regs.buf[reginfo.Offset : reginfo.Offset+reginfo.Bitsize/8]}
|
||||
}
|
||||
}
|
||||
|
||||
// reloadRegisters loads the current value of the thread's registers.
|
||||
// It will also load the address of the thread's G.
|
||||
// Loading the address of G can be done in one of two ways reloadGAlloc, if
|
||||
// the stub can allocate memory, or reloadGAtPC, if the stub can't.
|
||||
func (t *Thread) reloadRegisters() error {
|
||||
if t.regs.regs == nil {
|
||||
t.regs.regs = make(map[string]gdbRegister)
|
||||
t.regs.regsInfo = t.p.conn.regsInfo
|
||||
|
||||
regsz := 0
|
||||
for _, reginfo := range t.p.conn.regsInfo {
|
||||
if endoff := reginfo.Offset + (reginfo.Bitsize / 8); endoff > regsz {
|
||||
regsz = endoff
|
||||
}
|
||||
}
|
||||
t.regs.buf = make([]byte, regsz)
|
||||
for _, reginfo := range t.p.conn.regsInfo {
|
||||
t.regs.regs[reginfo.Name] = gdbRegister{regnum: reginfo.Regnum, value: t.regs.buf[reginfo.Offset : reginfo.Offset+reginfo.Bitsize/8]}
|
||||
}
|
||||
t.regs.init(t.p.conn.regsInfo)
|
||||
}
|
||||
|
||||
if t.p.gcmdok {
|
||||
@ -1503,7 +1512,7 @@ func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
pc := regs.PC()
|
||||
if bp, ok := thread.p.FindBreakpoint(pc); ok {
|
||||
if thread.regs.PC() != bp.Addr {
|
||||
if err := thread.regs.SetPC(thread, bp.Addr); err != nil {
|
||||
if err := thread.SetPC(bp.Addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -1529,6 +1538,9 @@ func (regs *gdbRegisters) setPC(value uint64) {
|
||||
func (regs *gdbRegisters) SP() uint64 {
|
||||
return binary.LittleEndian.Uint64(regs.regs[regnameSP].value)
|
||||
}
|
||||
func (regs *gdbRegisters) setSP(value uint64) {
|
||||
binary.LittleEndian.PutUint64(regs.regs[regnameSP].value, value)
|
||||
}
|
||||
|
||||
func (regs *gdbRegisters) BP() uint64 {
|
||||
return binary.LittleEndian.Uint64(regs.regs[regnameBP].value)
|
||||
@ -1715,13 +1727,21 @@ func (regs *gdbRegisters) Get(n int) (uint64, error) {
|
||||
return 0, proc.UnknownRegisterError
|
||||
}
|
||||
|
||||
func (regs *gdbRegisters) SetPC(thread proc.Thread, pc uint64) error {
|
||||
regs.setPC(pc)
|
||||
t := thread.(*Thread)
|
||||
func (t *Thread) SetPC(pc uint64) error {
|
||||
t.regs.setPC(pc)
|
||||
if t.p.gcmdok {
|
||||
return t.p.conn.writeRegisters(t.strID, t.regs.buf)
|
||||
}
|
||||
reg := regs.regs[regnamePC]
|
||||
reg := t.regs.regs[regnamePC]
|
||||
return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
|
||||
}
|
||||
|
||||
func (t *Thread) SetSP(sp uint64) error {
|
||||
t.regs.setSP(sp)
|
||||
if t.p.gcmdok {
|
||||
return t.p.conn.writeRegisters(t.strID, t.regs.buf)
|
||||
}
|
||||
reg := t.regs.regs[regnameSP]
|
||||
return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
|
||||
}
|
||||
|
||||
@ -1766,3 +1786,10 @@ func (regs *gdbRegisters) Slice() []proc.Register {
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (regs *gdbRegisters) Save() proc.SavedRegisters {
|
||||
savedRegs := &gdbRegisters{}
|
||||
savedRegs.init(regs.regsInfo)
|
||||
copy(savedRegs.buf, regs.buf)
|
||||
return savedRegs
|
||||
}
|
||||
|
@ -109,7 +109,13 @@ type BreakpointManipulation interface {
|
||||
// CommonProcess contains fields used by this package, common to all
|
||||
// implementations of the Process interface.
|
||||
type CommonProcess struct {
|
||||
allGCache []*G
|
||||
allGCache []*G
|
||||
fncallState functionCallState
|
||||
fncallEnabled bool
|
||||
}
|
||||
|
||||
func NewCommonProcess(fncallEnabled bool) CommonProcess {
|
||||
return CommonProcess{fncallEnabled: fncallEnabled}
|
||||
}
|
||||
|
||||
// ClearAllGCache clears the cached contents of the cache for runtime.allgs.
|
||||
|
@ -54,6 +54,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
return nil, proc.NotExecutableErr
|
||||
}
|
||||
dbp := New(0)
|
||||
dbp.common = proc.NewCommonProcess(true)
|
||||
dbp.execPtraceFunc(func() {
|
||||
process = exec.Command(cmd[0])
|
||||
process.Args = cmd
|
||||
@ -87,6 +88,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
// Attach to an existing process with the given PID.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
dbp := New(pid)
|
||||
dbp.common = proc.NewCommonProcess(true)
|
||||
|
||||
var err error
|
||||
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
|
||||
|
@ -77,6 +77,7 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
err = proc.LinuxX86XstateRead(xstateargs[:iov.Len], false, ®set)
|
||||
regset.Xsave = xstateargs[:iov.Len]
|
||||
err = proc.LinuxX86XstateRead(regset.Xsave, false, ®set)
|
||||
return regset, err
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ package native
|
||||
import "C"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@ -112,8 +113,7 @@ func (r *Regs) GAddr() (uint64, bool) {
|
||||
}
|
||||
|
||||
// SetPC sets the RIP register to the value specified by `pc`.
|
||||
func (r *Regs) SetPC(t proc.Thread, pc uint64) error {
|
||||
thread := t.(*Thread)
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not set pc")
|
||||
@ -121,6 +121,11 @@ func (r *Regs) SetPC(t proc.Thread, pc uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSP sets the RSP register to the value specified by `pc`.
|
||||
func (thread *Thread) SetSP(sp uint64) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *Regs) Get(n int) (uint64, error) {
|
||||
reg := x86asm.Reg(n)
|
||||
const (
|
||||
@ -358,18 +363,10 @@ func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (proc.Registers, error) {
|
||||
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("could not save register contents")
|
||||
}
|
||||
return &Regs{rip: uint64(thread.os.registers.__rip), rsp: uint64(thread.os.registers.__rsp)}, nil
|
||||
type savedRegisters struct {
|
||||
}
|
||||
|
||||
func (thread *Thread) restoreRegisters() error {
|
||||
kret := C.set_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not save register contents")
|
||||
}
|
||||
func (r *Regs) Save() proc.SavedRegisters {
|
||||
//TODO(aarzilli): implement this to support function calls
|
||||
return nil
|
||||
}
|
||||
|
@ -11,8 +11,9 @@ import (
|
||||
|
||||
// Regs is a wrapper for sys.PtraceRegs.
|
||||
type Regs struct {
|
||||
regs *sys.PtraceRegs
|
||||
fpregs []proc.Register
|
||||
regs *sys.PtraceRegs
|
||||
fpregs []proc.Register
|
||||
fpregset *proc.LinuxX86Xstate
|
||||
}
|
||||
|
||||
func (r *Regs) Slice() []proc.Register {
|
||||
@ -90,10 +91,27 @@ func (r *Regs) GAddr() (uint64, bool) {
|
||||
}
|
||||
|
||||
// SetPC sets RIP to the value specified by 'pc'.
|
||||
func (r *Regs) SetPC(t proc.Thread, pc uint64) (err error) {
|
||||
thread := t.(*Thread)
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
ir, err := registers(thread, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := ir.(*Regs)
|
||||
r.regs.SetPC(pc)
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
||||
return err
|
||||
}
|
||||
|
||||
// SetSP sets RSP to the value specified by 'sp'
|
||||
func (thread *Thread) SetSP(sp uint64) (err error) {
|
||||
var ir proc.Registers
|
||||
ir, err = registers(thread, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := ir.(*Regs)
|
||||
r.regs.Rsp = sp
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
||||
return
|
||||
}
|
||||
|
||||
@ -263,9 +281,11 @@ func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Regs{®s, nil}
|
||||
r := &Regs{®s, nil, nil}
|
||||
if floatingPoint {
|
||||
r.fpregs, err = thread.fpRegisters()
|
||||
var fpregset proc.LinuxX86Xstate
|
||||
r.fpregs, fpregset, err = thread.fpRegisters()
|
||||
r.fpregset = &fpregset
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -283,8 +303,7 @@ const (
|
||||
_XSAVE_SSE_REGION_LEN = 416
|
||||
)
|
||||
|
||||
func (thread *Thread) fpRegisters() (regs []proc.Register, err error) {
|
||||
var fpregs proc.LinuxX86Xstate
|
||||
func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs proc.LinuxX86Xstate, err error) {
|
||||
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
|
||||
regs = fpregs.Decode()
|
||||
if err != nil {
|
||||
@ -292,3 +311,15 @@ func (thread *Thread) fpRegisters() (regs []proc.Register, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type savedRegisters struct {
|
||||
regs sys.PtraceRegs
|
||||
fpregs proc.LinuxX86Xstate
|
||||
}
|
||||
|
||||
func (r *Regs) Save() proc.SavedRegisters {
|
||||
savedRegs := &savedRegisters{}
|
||||
savedRegs.regs = *r.regs
|
||||
savedRegs.fpregs = *r.fpregset
|
||||
return savedRegs
|
||||
}
|
||||
|
@ -131,8 +131,7 @@ func (r *Regs) GAddr() (uint64, bool) {
|
||||
}
|
||||
|
||||
// SetPC sets the RIP register to the value specified by `pc`.
|
||||
func (r *Regs) SetPC(t proc.Thread, pc uint64) error {
|
||||
thread := t.(*Thread)
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
context := newCONTEXT()
|
||||
context.ContextFlags = _CONTEXT_ALL
|
||||
|
||||
@ -146,6 +145,21 @@ func (r *Regs) SetPC(t proc.Thread, pc uint64) error {
|
||||
return _SetThreadContext(thread.os.hThread, context)
|
||||
}
|
||||
|
||||
// SetSP sets the RSP register to the value specified by `sp`.
|
||||
func (thread *Thread) SetSP(sp uint64) error {
|
||||
context := newCONTEXT()
|
||||
context.ContextFlags = _CONTEXT_ALL
|
||||
|
||||
err := _GetThreadContext(thread.os.hThread, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context.Rsp = sp
|
||||
|
||||
return _SetThreadContext(thread.os.hThread, context)
|
||||
}
|
||||
|
||||
func (r *Regs) Get(n int) (uint64, error) {
|
||||
reg := x86asm.Reg(n)
|
||||
const (
|
||||
@ -350,10 +364,10 @@ func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (proc.Registers, error) {
|
||||
return nil, fmt.Errorf("not implemented: saveRegisters")
|
||||
type savedRegisters struct {
|
||||
}
|
||||
|
||||
func (thread *Thread) restoreRegisters() error {
|
||||
return fmt.Errorf("not implemented: restoreRegisters")
|
||||
func (r *Regs) Save() proc.SavedRegisters {
|
||||
//TODO(aarzilli): implement this to support function calls
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
@ -106,15 +107,6 @@ func (thread *Thread) Common() *proc.CommonThread {
|
||||
return &thread.common
|
||||
}
|
||||
|
||||
// SetPC sets the PC for this thread.
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return regs.SetPC(thread, pc)
|
||||
}
|
||||
|
||||
// SetCurrentBreakpoint sets the current breakpoint that this
|
||||
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
||||
func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
@ -159,6 +151,14 @@ func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
||||
return registers(t, floatingPoint)
|
||||
}
|
||||
|
||||
func (t *Thread) RestoreRegisters(savedRegs proc.SavedRegisters) error {
|
||||
sr, ok := savedRegs.(*savedRegisters)
|
||||
if !ok {
|
||||
return errors.New("unknown saved register set")
|
||||
}
|
||||
return t.restoreRegisters(sr)
|
||||
}
|
||||
|
||||
func (t *Thread) PC() (uint64, error) {
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
|
@ -4,6 +4,7 @@ package native
|
||||
// #include "proc_darwin.h"
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@ -144,3 +145,7 @@ func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters(sr *savedRegisters) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
@ -80,18 +82,26 @@ func (t *Thread) Blocked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Thread) saveRegisters() (proc.Registers, error) {
|
||||
var err error
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(t.ID, &t.os.registers) })
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save register contents")
|
||||
}
|
||||
return &Regs{&t.os.registers, nil}, nil
|
||||
}
|
||||
func (t *Thread) restoreRegisters(sr *savedRegisters) error {
|
||||
var restoreRegistersErr error
|
||||
t.dbp.execPtraceFunc(func() {
|
||||
restoreRegistersErr = sys.PtraceSetRegs(t.ID, &sr.regs)
|
||||
if restoreRegistersErr != nil {
|
||||
return
|
||||
}
|
||||
if sr.fpregs.Xsave != nil {
|
||||
iov := sys.Iovec{Base: &sr.fpregs.Xsave[0], Len: uint64(len(sr.fpregs.Xsave))}
|
||||
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters() (err error) {
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(t.ID, &t.os.registers) })
|
||||
return
|
||||
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.fpregs.PtraceFpRegs)), 0, 0)
|
||||
return
|
||||
})
|
||||
if restoreRegistersErr == syscall.Errno(0) {
|
||||
restoreRegistersErr = nil
|
||||
}
|
||||
return restoreRegistersErr
|
||||
}
|
||||
|
||||
func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
@ -113,5 +123,8 @@ func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
|
||||
return
|
||||
}
|
||||
t.dbp.execPtraceFunc(func() { _, err = sys.PtracePeekData(t.ID, addr, data) })
|
||||
if err == nil {
|
||||
n = len(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -149,3 +149,7 @@ func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
|
||||
}
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters(sr *savedRegisters) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var NotExecutableErr = errors.New("not an executable file")
|
||||
@ -135,26 +136,45 @@ func Continue(dbp Process) error {
|
||||
|
||||
switch {
|
||||
case curbp.Breakpoint == nil:
|
||||
// runtime.Breakpoint or manual stop
|
||||
if recorded, _ := dbp.Recorded(); onRuntimeBreakpoint(curthread) && !recorded {
|
||||
// runtime.Breakpoint, manual stop or debugCallV1-related stop
|
||||
recorded, _ := dbp.Recorded()
|
||||
if recorded {
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
|
||||
loc, err := curthread.Location()
|
||||
if err != nil || loc.Fn == nil {
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
|
||||
switch {
|
||||
case loc.Fn.Name == "runtime.breakpoint":
|
||||
// Single-step current thread until we exit runtime.breakpoint and
|
||||
// runtime.Breakpoint.
|
||||
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change
|
||||
// to the compiler requires 4 steps.
|
||||
for {
|
||||
if err = curthread.StepInstruction(); err != nil {
|
||||
return err
|
||||
}
|
||||
loc, err := curthread.Location()
|
||||
if err != nil || loc.Fn == nil || (loc.Fn.Name != "runtime.breakpoint" && loc.Fn.Name != "runtime.Breakpoint") {
|
||||
if g := dbp.SelectedGoroutine(); g != nil {
|
||||
g.CurrentLoc = *loc
|
||||
}
|
||||
break
|
||||
}
|
||||
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
|
||||
return err
|
||||
}
|
||||
return conditionErrors(threads)
|
||||
case strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) || strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2):
|
||||
fncall := &dbp.Common().fncallState
|
||||
if !fncall.inProgress {
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
fncall.step(dbp)
|
||||
// only stop execution if the function call finished
|
||||
if fncall.finished {
|
||||
fncall.inProgress = false
|
||||
if fncall.err != nil {
|
||||
return fncall.err
|
||||
}
|
||||
curthread.Common().returnValues = fncall.returnValues()
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
default:
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
return conditionErrors(threads)
|
||||
case curbp.Active && curbp.Internal:
|
||||
if curbp.Kind == StepBreakpoint {
|
||||
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
||||
@ -239,6 +259,25 @@ func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
|
||||
return dbp.SwitchThread(trapthread.ThreadID())
|
||||
}
|
||||
|
||||
// stepInstructionOut repeatedly calls StepInstruction until the current
|
||||
// function is neither fnname1 or fnname2.
|
||||
// This function is used to step out of runtime.Breakpoint as well as
|
||||
// runtime.debugCallV1.
|
||||
func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error {
|
||||
for {
|
||||
if err := curthread.StepInstruction(); err != nil {
|
||||
return err
|
||||
}
|
||||
loc, err := curthread.Location()
|
||||
if err != nil || loc.Fn == nil || (loc.Fn.Name != fnname1 && loc.Fn.Name != fnname2) {
|
||||
if g := dbp.SelectedGoroutine(); g != nil {
|
||||
g.CurrentLoc = *loc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step will continue until another source line is reached.
|
||||
// Will step into functions.
|
||||
func Step(dbp Process) (err error) {
|
||||
@ -308,6 +347,10 @@ func StepOut(dbp Process) error {
|
||||
if _, err := dbp.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dbp.Breakpoints().HasInternalBreakpoints() {
|
||||
return fmt.Errorf("next while nexting")
|
||||
}
|
||||
|
||||
selg := dbp.SelectedGoroutine()
|
||||
curthread := dbp.CurrentThread()
|
||||
|
||||
|
@ -23,8 +23,9 @@ type Registers interface {
|
||||
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
||||
GAddr() (uint64, bool)
|
||||
Get(int) (uint64, error)
|
||||
SetPC(Thread, uint64) error
|
||||
Slice() []Register
|
||||
// Save saves a copy of this object that will survive restarts
|
||||
Save() SavedRegisters
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
@ -33,6 +34,9 @@ type Register struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type SavedRegisters interface {
|
||||
}
|
||||
|
||||
// AppendWordReg appends a word (16 bit) register to regs.
|
||||
func AppendWordReg(regs []Register, name string, value uint16) []Register {
|
||||
var buf bytes.Buffer
|
||||
@ -266,7 +270,8 @@ type PtraceFpRegs struct {
|
||||
// Manual, Volume 1: Basic Architecture.
|
||||
type LinuxX86Xstate struct {
|
||||
PtraceFpRegs
|
||||
AvxState bool // contains AVX state
|
||||
Xsave []byte // raw xsave area
|
||||
AvxState bool // contains AVX state
|
||||
YmmSpace [256]byte
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,8 @@ type Thread interface {
|
||||
Breakpoint() BreakpointState
|
||||
ThreadID() int
|
||||
Registers(floatingPoint bool) (Registers, error)
|
||||
// RestoreRegisters restores saved registers
|
||||
RestoreRegisters(SavedRegisters) error
|
||||
Arch() Arch
|
||||
BinInfo() *BinaryInfo
|
||||
StepInstruction() error
|
||||
@ -32,6 +34,9 @@ type Thread interface {
|
||||
SetCurrentBreakpoint() error
|
||||
// Common returns the CommonThread structure for this thread
|
||||
Common() *CommonThread
|
||||
|
||||
SetPC(uint64) error
|
||||
SetSP(uint64) error
|
||||
}
|
||||
|
||||
// Location represents the location of a thread.
|
||||
@ -504,14 +509,6 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
|
||||
return FrameToScope(thread.BinInfo(), thread, g, locations...), nil
|
||||
}
|
||||
|
||||
func onRuntimeBreakpoint(thread Thread) bool {
|
||||
loc, err := thread.Location()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
|
||||
}
|
||||
|
||||
// onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command
|
||||
func onNextGoroutine(thread Thread, breakpoints *BreakpointMap) (bool, error) {
|
||||
var bp *Breakpoint
|
||||
|
@ -149,6 +149,7 @@ type G struct {
|
||||
stkbarVar *Variable // stkbar field of g struct
|
||||
stkbarPos int // stkbarPos field of g struct
|
||||
stackhi uint64 // value of stack.hi
|
||||
stacklo uint64 // value of stack.lo
|
||||
|
||||
SystemStack bool // SystemStack is true if this goroutine is currently executing on a system stack.
|
||||
|
||||
@ -364,11 +365,6 @@ func (scope *EvalScope) DwarfReader() *reader.Reader {
|
||||
return scope.BinInfo.DwarfReader()
|
||||
}
|
||||
|
||||
// Type returns the Dwarf type entry at `offset`.
|
||||
func (scope *EvalScope) Type(offset dwarf.Offset) (godwarf.Type, error) {
|
||||
return godwarf.ReadType(scope.BinInfo.dwarf, offset, scope.BinInfo.typeCache)
|
||||
}
|
||||
|
||||
// PtrSize returns the size of a pointer.
|
||||
func (scope *EvalScope) PtrSize() int {
|
||||
return scope.BinInfo.Arch.PtrSize()
|
||||
@ -434,11 +430,14 @@ func (gvar *Variable) parseG() (*G, error) {
|
||||
}
|
||||
|
||||
}
|
||||
var stackhi uint64
|
||||
var stackhi, stacklo uint64
|
||||
if stackVar := gvar.fieldVariable("stack"); stackVar != nil {
|
||||
if stackhiVar := stackVar.fieldVariable("hi"); stackhiVar != nil {
|
||||
stackhi, _ = constant.Uint64Val(stackhiVar.Value)
|
||||
}
|
||||
if stackloVar := stackVar.fieldVariable("lo"); stackloVar != nil {
|
||||
stacklo, _ = constant.Uint64Val(stackloVar.Value)
|
||||
}
|
||||
}
|
||||
|
||||
stkbarVar, _ := gvar.structMember("stkbar")
|
||||
@ -464,6 +463,7 @@ func (gvar *Variable) parseG() (*G, error) {
|
||||
stkbarVar: stkbarVar,
|
||||
stkbarPos: int(stkbarPos),
|
||||
stackhi: stackhi,
|
||||
stacklo: stacklo,
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
@ -715,10 +715,20 @@ func (scope *EvalScope) findGlobal(name string) (*Variable, error) {
|
||||
return scope.extractVarInfoFromEntry(entry)
|
||||
}
|
||||
}
|
||||
for _, fn := range scope.BinInfo.Functions {
|
||||
if fn.Name == name || strings.HasSuffix(fn.Name, "/"+name) {
|
||||
//TODO(aarzilli): convert function entry into a function type?
|
||||
r := scope.newVariable(fn.Name, uintptr(fn.Entry), &godwarf.FuncType{}, scope.Mem)
|
||||
r.Value = constant.MakeString(fn.Name)
|
||||
r.Base = uintptr(fn.Entry)
|
||||
r.loaded = true
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
for offset, ctyp := range scope.BinInfo.consts {
|
||||
for _, cval := range ctyp.values {
|
||||
if cval.fullName == name || strings.HasSuffix(cval.fullName, "/"+name) {
|
||||
t, err := scope.Type(offset)
|
||||
t, err := scope.BinInfo.Type(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -809,6 +819,27 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, name string, typ godwarf.Type, err error) {
|
||||
entry, _ = reader.LoadAbstractOrigin(varEntry, bi.dwarfReader)
|
||||
|
||||
name, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("malformed variable DIE (name)")
|
||||
}
|
||||
|
||||
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("malformed variable DIE (offset)")
|
||||
}
|
||||
|
||||
typ, err = bi.Type(offset)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
return entry, name, typ, nil
|
||||
}
|
||||
|
||||
// Extracts the name and type of a variable from a dwarf entry
|
||||
// then executes the instructions given in the DW_AT_location attribute to grab the variable's address
|
||||
func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variable, error) {
|
||||
@ -820,19 +851,7 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl
|
||||
return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", varEntry.Tag.String())
|
||||
}
|
||||
|
||||
entry, _ := reader.LoadAbstractOrigin(varEntry, scope.BinInfo.dwarfReader)
|
||||
|
||||
n, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
|
||||
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
|
||||
t, err := scope.Type(offset)
|
||||
entry, n, t, err := readVarEntry(varEntry, scope.BinInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1130,6 +1149,7 @@ func (v *Variable) loadChanInfo() {
|
||||
if sv.Unreadable != nil || sv.Addr == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = sv.Addr
|
||||
structType, ok := sv.DwarfType.(*godwarf.StructType)
|
||||
if !ok {
|
||||
v.Unreadable = errors.New("bad channel type")
|
||||
|
@ -128,6 +128,24 @@ See also: "help on", "help cond" and "help clear"`},
|
||||
{aliases: []string{"step-instruction", "si"}, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."},
|
||||
{aliases: []string{"next", "n"}, cmdFn: c.next, helpMsg: "Step over to next source line."},
|
||||
{aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
|
||||
{aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
|
||||
Current limitations:
|
||||
- can only call package-level functions, no function pointers nor methods.
|
||||
- only things that have an address can be used as arguments (no literal
|
||||
constants or results of evaluating some expressions).
|
||||
- only pointers to stack-allocated objects can be passed as argument.
|
||||
- no automatic type conversions are supported, including automatically
|
||||
converting to an interface type.
|
||||
- functions can only be called on running goroutines that are not
|
||||
executing the runtime.
|
||||
- the current goroutine needs to have at least 256 bytes of free space on
|
||||
the stack.
|
||||
- functions can only be called when the goroutine is stopped at a safe
|
||||
point.
|
||||
- calling a function will resume execution of all goroutines.
|
||||
- only supported on linux's native backend.
|
||||
`},
|
||||
{aliases: []string{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
|
||||
{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: `Switch to the specified thread.
|
||||
|
||||
@ -936,6 +954,20 @@ func (c *Commands) stepout(t *Term, ctx callContext, args string) error {
|
||||
return continueUntilCompleteNext(t, state, "stepout")
|
||||
}
|
||||
|
||||
func (c *Commands) call(t *Term, ctx callContext, args string) error {
|
||||
if err := scopePrefixSwitch(t, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := exitedToError(t.client.Call(args))
|
||||
c.frame = 0
|
||||
if err != nil {
|
||||
printfileNoState(t)
|
||||
return err
|
||||
}
|
||||
printcontext(t, state)
|
||||
return continueUntilCompleteNext(t, state, "call")
|
||||
}
|
||||
|
||||
func clear(t *Term, ctx callContext, args string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
|
@ -275,6 +275,8 @@ type DebuggerCommand struct {
|
||||
// When ReturnInfoLoadConfig is not nil it will be used to load the value
|
||||
// of any return variables.
|
||||
ReturnInfoLoadConfig *LoadConfig
|
||||
// Expr is the expression argument for a Call command
|
||||
Expr string `json:"expr,omitempty"`
|
||||
}
|
||||
|
||||
// Informations about the current breakpoint
|
||||
@ -310,6 +312,8 @@ const (
|
||||
SwitchGoroutine = "switchGoroutine"
|
||||
// Halt suspends the process.
|
||||
Halt = "halt"
|
||||
// Call resumes process execution injecting a function call.
|
||||
Call = "call"
|
||||
)
|
||||
|
||||
type AssemblyFlavour int
|
||||
|
@ -38,6 +38,8 @@ type Client interface {
|
||||
Step() (*api.DebuggerState, error)
|
||||
// StepOut continues to the return address of the current function
|
||||
StepOut() (*api.DebuggerState, error)
|
||||
// Call resumes process execution while making a function call.
|
||||
Call(expr string) (*api.DebuggerState, error)
|
||||
|
||||
// SingleStep will step a single cpu instruction.
|
||||
StepInstruction() (*api.DebuggerState, error)
|
||||
|
@ -558,6 +558,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
case api.Continue:
|
||||
d.log.Debug("continuing")
|
||||
err = proc.Continue(d.target)
|
||||
case api.Call:
|
||||
d.log.Debugf("function call %s", command.Expr)
|
||||
err = proc.CallFunction(d.target, command.Expr, api.LoadConfigToProc(command.ReturnInfoLoadConfig))
|
||||
case api.Rewind:
|
||||
d.log.Debug("rewinding")
|
||||
if err := d.target.Direction(proc.Backward); err != nil {
|
||||
@ -618,7 +621,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
||||
}
|
||||
|
||||
for i := range state.Threads {
|
||||
if state.Threads[i].Breakpoint == nil {
|
||||
if state.Threads[i].Breakpoint == nil || state.Threads[i].BreakpointInfo != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,12 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) {
|
||||
return state, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Call(expr string) (*api.DebuggerState, error) {
|
||||
state := new(api.DebuggerState)
|
||||
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, Expr: expr}, state)
|
||||
return state, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
|
||||
state := new(api.DebuggerState)
|
||||
err := c.call("Command", &api.DebuggerCommand{Name: api.StepInstruction}, state)
|
||||
|
@ -139,6 +139,12 @@ func (c *RPCClient) StepOut() (*api.DebuggerState, error) {
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Call(expr string) (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.StepInstruction}, &out)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
protest "github.com/derekparker/delve/pkg/proc/test"
|
||||
|
||||
"github.com/derekparker/delve/pkg/goversion"
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/derekparker/delve/service/rpc2"
|
||||
@ -28,6 +29,8 @@ var testBackend string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.StringVar(&testBackend, "backend", "", "selects backend")
|
||||
var logOutput string
|
||||
flag.StringVar(&logOutput, "log-output", "", "configures log output")
|
||||
flag.Parse()
|
||||
if testBackend == "" {
|
||||
testBackend = os.Getenv("PROCTEST")
|
||||
@ -35,6 +38,7 @@ func TestMain(m *testing.M) {
|
||||
testBackend = "native"
|
||||
}
|
||||
}
|
||||
logflags.Setup(logOutput != "", logOutput)
|
||||
os.Exit(protest.RunTestsWithFixtures(m))
|
||||
}
|
||||
|
||||
@ -1495,3 +1499,123 @@ func TestAcceptMulticlient(t *testing.T) {
|
||||
client2.Detach(true)
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func mustHaveDebugCalls(t *testing.T, c service.Client) {
|
||||
locs, err := c.FindLocation(api.EvalScope{-1, 0}, "runtime.debugCallV1")
|
||||
if len(locs) == 0 || err != nil {
|
||||
t.Skip("function calls not supported on this version of go")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCall(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&normalLoadConfig)
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
beforeCallFn := state.CurrentThread.Function.Name
|
||||
state, err := c.Call("call1(one, two)")
|
||||
assertNoError(err, t, "Call()")
|
||||
t.Logf("returned to %q", state.CurrentThread.Function.Name)
|
||||
if state.CurrentThread.Function.Name != beforeCallFn {
|
||||
t.Fatalf("did not return to the calling function %q %q", beforeCallFn, state.CurrentThread.Function.Name)
|
||||
}
|
||||
if state.CurrentThread.ReturnValues == nil {
|
||||
t.Fatal("no return values on return from call")
|
||||
}
|
||||
t.Logf("Return values %v", state.CurrentThread.ReturnValues)
|
||||
if len(state.CurrentThread.ReturnValues) != 1 {
|
||||
t.Fatal("not enough return values")
|
||||
}
|
||||
if state.CurrentThread.ReturnValues[0].Value != "3" {
|
||||
t.Fatalf("wrong return value %s", state.CurrentThread.ReturnValues[0].Value)
|
||||
}
|
||||
state = <-c.Continue()
|
||||
if !state.Exited {
|
||||
t.Fatalf("expected process to exit after call %v", state.CurrentThread)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallBadPos(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
loc, err := c.FindLocation(api.EvalScope{-1, 0}, "fmt/print.go:649")
|
||||
assertNoError(err, t, "could not find location")
|
||||
|
||||
_, err = c.CreateBreakpoint(&api.Breakpoint{File: loc[0].File, Line: loc[0].Line})
|
||||
assertNoError(err, t, "CreateBreakpoin")
|
||||
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
|
||||
state = <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
|
||||
state, err = c.Call("main.call1(main.zero, main.zero)")
|
||||
if err == nil || err.Error() != "call not at safe point" {
|
||||
t.Fatalf("wrong error or no error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallPanic(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&normalLoadConfig)
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
state, err := c.Call("callpanic()")
|
||||
assertNoError(err, t, "Call()")
|
||||
t.Logf("at: %s:%d", state.CurrentThread.File, state.CurrentThread.Line)
|
||||
if state.CurrentThread.ReturnValues == nil {
|
||||
t.Fatal("no return values on return from call")
|
||||
}
|
||||
t.Logf("Return values %v", state.CurrentThread.ReturnValues)
|
||||
if len(state.CurrentThread.ReturnValues) != 1 {
|
||||
t.Fatal("not enough return values")
|
||||
}
|
||||
if state.CurrentThread.ReturnValues[0].Name != "~panic" {
|
||||
t.Fatal("not a panic")
|
||||
}
|
||||
if state.CurrentThread.ReturnValues[0].Children[0].Value != "callpanic panicked" {
|
||||
t.Fatalf("wrong panic value %s", state.CurrentThread.ReturnValues[0].Children[0].Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallStacktrace(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&api.LoadConfig{false, 0, 2048, 0, 0})
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
state, err := c.Call("callstacktrace()")
|
||||
assertNoError(err, t, "Call()")
|
||||
t.Logf("at: %s:%d", state.CurrentThread.File, state.CurrentThread.Line)
|
||||
if state.CurrentThread.ReturnValues == nil {
|
||||
t.Fatal("no return values on return from call")
|
||||
}
|
||||
if len(state.CurrentThread.ReturnValues) != 1 || state.CurrentThread.ReturnValues[0].Kind != reflect.String {
|
||||
t.Fatal("not enough return values")
|
||||
}
|
||||
st := state.CurrentThread.ReturnValues[0].Value
|
||||
t.Logf("Returned stacktrace:\n%s", st)
|
||||
|
||||
if !strings.Contains(st, "main.callstacktrace") || !strings.Contains(st, "main.main") || !strings.Contains(st, "runtime.main") {
|
||||
t.Fatal("bad stacktrace returned")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -739,6 +739,9 @@ func TestEvalExpression(t *testing.T) {
|
||||
// shortcircuited logical operators
|
||||
{"nilstruct != nil && nilstruct.A == 1", false, "false", "false", "", nil},
|
||||
{"nilstruct == nil || nilstruct.A == 1", false, "true", "true", "", nil},
|
||||
|
||||
{"afunc", true, `main.afunc`, `main.afunc`, `func()`, nil},
|
||||
{"main.afunc2", true, `main.afunc2`, `main.afunc2`, `func()`, nil},
|
||||
}
|
||||
|
||||
ver, _ := goversion.Parse(runtime.Version())
|
||||
|
Loading…
Reference in New Issue
Block a user