提交 2925c031 编写于 作者: A aarzilli 提交者: Derek Parker

*: 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
上级 8588e97b
......@@ -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 ".")
```
......
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
......
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/,
......
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, &regset)
regset.Xsave = xstateargs[:iov.Len]
err = proc.LinuxX86XstateRead(regset.Xsave, false, &regset)
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{&regs, nil}
r := &Regs{&regs, 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
}
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())
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册