misc: cleanup and documentation
This commit is contained in:
parent
6d50aba71d
commit
0188dc2c8b
@ -24,13 +24,19 @@ import (
|
||||
|
||||
const version string = "0.10.0-alpha"
|
||||
|
||||
// Build is the current git hash.
|
||||
var Build string
|
||||
|
||||
var (
|
||||
Log bool
|
||||
Headless bool
|
||||
Addr string
|
||||
InitFile string
|
||||
// Log is whether to log debug statements.
|
||||
Log bool
|
||||
// Headless is whether to run without terminal.
|
||||
Headless bool
|
||||
// Addr is the debugging server listen address.
|
||||
Addr string
|
||||
// InitFile is the path to initialization file.
|
||||
InitFile string
|
||||
// BuildFlags is the flags passed during compiler invocation.
|
||||
BuildFlags string
|
||||
)
|
||||
|
||||
@ -215,7 +221,6 @@ starts and attaches to it, and enables you to immediately begin debugging your p
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}()
|
||||
os.Exit(status)
|
||||
},
|
||||
@ -304,7 +309,7 @@ func connect(addr string, conf *config.Config) int {
|
||||
var client service.Client
|
||||
client = rpc.NewClient(addr)
|
||||
term := terminal.New(client, conf)
|
||||
err, status := term.Run()
|
||||
status, err := term.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@ -342,7 +347,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int {
|
||||
client = rpc.NewClient(listener.Addr().String())
|
||||
term := terminal.New(client, conf)
|
||||
term.InitFile = InitFile
|
||||
err, status = term.Run()
|
||||
status, err = term.Run()
|
||||
} else {
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, sys.SIGINT)
|
||||
|
26
proc/arch.go
26
proc/arch.go
@ -2,16 +2,17 @@ package proc
|
||||
|
||||
import "runtime"
|
||||
|
||||
// Arch defines an interface for representing a
|
||||
// CPU architecture.
|
||||
type Arch interface {
|
||||
SetGStructOffset(ver GoVersion, iscgo bool)
|
||||
PtrSize() int
|
||||
BreakpointInstruction() []byte
|
||||
BreakpointSize() int
|
||||
GStructOffset() uint64
|
||||
HardwareBreakpointUsage() []bool
|
||||
SetHardwareBreakpointUsage(int, bool)
|
||||
}
|
||||
|
||||
// AMD64 represents hte AMD64 CPU architecture.
|
||||
type AMD64 struct {
|
||||
ptrSize int
|
||||
breakInstruction []byte
|
||||
@ -20,6 +21,8 @@ type AMD64 struct {
|
||||
hardwareBreakpointUsage []bool
|
||||
}
|
||||
|
||||
// AMD64Arch returns an initialized AMD64
|
||||
// struct.
|
||||
func AMD64Arch() *AMD64 {
|
||||
var breakInstr = []byte{0xCC}
|
||||
|
||||
@ -31,6 +34,9 @@ func AMD64Arch() *AMD64 {
|
||||
}
|
||||
}
|
||||
|
||||
// SetGStructOffset sets the offset of the G struct on the AMD64
|
||||
// arch struct. The offset is depandant on the Go compiler Version
|
||||
// and whether or not the target program was externally linked.
|
||||
func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
@ -43,26 +49,26 @@ func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// PtrSize returns the size of a pointer
|
||||
// on this architecture.
|
||||
func (a *AMD64) PtrSize() int {
|
||||
return a.ptrSize
|
||||
}
|
||||
|
||||
// BreakpointInstruction returns the Breakpoint
|
||||
// instruction for this architecture.
|
||||
func (a *AMD64) BreakpointInstruction() []byte {
|
||||
return a.breakInstruction
|
||||
}
|
||||
|
||||
// BreakpointSize returns the size of the
|
||||
// breakpoint instruction on this architecture.
|
||||
func (a *AMD64) BreakpointSize() int {
|
||||
return a.breakInstructionLen
|
||||
}
|
||||
|
||||
// GStructOffset returns the offset of the G
|
||||
// struct in thread local storage.
|
||||
func (a *AMD64) GStructOffset() uint64 {
|
||||
return a.gStructOffset
|
||||
}
|
||||
|
||||
func (a *AMD64) HardwareBreakpointUsage() []bool {
|
||||
return a.hardwareBreakpointUsage
|
||||
}
|
||||
|
||||
func (a *AMD64) SetHardwareBreakpointUsage(reg int, set bool) {
|
||||
a.hardwareBreakpointUsage[reg] = set
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package proc
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Represents a single breakpoint. Stores information on the break
|
||||
// Breakpoint represents a breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type Breakpoint struct {
|
||||
@ -40,7 +40,7 @@ func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
// Returned when trying to set a breakpoint at
|
||||
// BreakpointExistsError is returned when trying to set a breakpoint at
|
||||
// an address that already has a breakpoint set for it.
|
||||
type BreakpointExistsError struct {
|
||||
file string
|
||||
@ -117,10 +117,11 @@ func (bp *Breakpoint) checkCondition(thread *Thread) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return g.Id == bp.Cond
|
||||
return g.ID == bp.Cond
|
||||
}
|
||||
|
||||
// Error thrown when trying to clear a breakpoint that does not exist.
|
||||
// NoBreakpointError is returned when trying to
|
||||
// clear a breakpoint that does not exist.
|
||||
type NoBreakpointError struct {
|
||||
addr uint64
|
||||
}
|
||||
|
33
proc/eval.go
33
proc/eval.go
@ -15,7 +15,7 @@ import (
|
||||
"github.com/derekparker/delve/dwarf/reader"
|
||||
)
|
||||
|
||||
// Returns the value of the given expression
|
||||
// EvalExpression returns the value of the given expression.
|
||||
func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) {
|
||||
t, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
@ -375,7 +375,7 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
sz = 128
|
||||
}
|
||||
|
||||
typ := &dwarf.ComplexType{dwarf.BasicType{dwarf.CommonType{ByteSize: int64(sz / 8), Name: fmt.Sprintf("complex%d", sz)}, sz, 0}}
|
||||
typ := &dwarf.ComplexType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: int64(sz / 8), Name: fmt.Sprintf("complex%d", sz)}, BitSize: sz, BitOffset: 0}}
|
||||
|
||||
r := newVariable("", 0, typ, realev.thread)
|
||||
r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value))
|
||||
@ -445,10 +445,9 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
|
||||
}
|
||||
}
|
||||
return nil, origErr
|
||||
} else {
|
||||
v = v.maybeDereference()
|
||||
v.Name = node.Name
|
||||
}
|
||||
v = v.maybeDereference()
|
||||
v.Name = node.Name
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
@ -605,13 +604,12 @@ func (scope *EvalScope) evalPointerDeref(node *ast.StarExpr) (*Variable, error)
|
||||
if len(xev.Children) == 1 {
|
||||
// this branch is here to support pointers constructed with typecasts from ints
|
||||
return &(xev.Children[0]), nil
|
||||
} else {
|
||||
rv := xev.maybeDereference()
|
||||
if rv.Addr == 0 {
|
||||
return nil, fmt.Errorf("nil pointer dereference")
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
rv := xev.maybeDereference()
|
||||
if rv.Addr == 0 {
|
||||
return nil, fmt.Errorf("nil pointer dereference")
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Evaluates expressions &<subexpr>
|
||||
@ -627,7 +625,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) {
|
||||
xev.OnlyAddr = true
|
||||
|
||||
typename := "*" + xev.DwarfType.String()
|
||||
rv := newVariable("", 0, &dwarf.PtrType{dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, xev.DwarfType}, scope.Thread)
|
||||
rv := newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}, scope.Thread)
|
||||
rv.Children = []Variable{*xev}
|
||||
rv.loaded = true
|
||||
|
||||
@ -692,9 +690,8 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
|
||||
r := newVariable("", 0, xv.DwarfType, xv.thread)
|
||||
r.Value = rc
|
||||
return r, nil
|
||||
} else {
|
||||
return newConstant(rc, xv.thread), nil
|
||||
}
|
||||
return newConstant(rc, xv.thread), nil
|
||||
}
|
||||
|
||||
func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) {
|
||||
@ -827,11 +824,11 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
|
||||
if typ == nil {
|
||||
return newConstant(rc, xv.thread), nil
|
||||
} else {
|
||||
r := newVariable("", 0, typ, xv.thread)
|
||||
r.Value = rc
|
||||
return r, nil
|
||||
}
|
||||
|
||||
r := newVariable("", 0, typ, xv.thread)
|
||||
r.Value = rc
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GoVersion represents the Go version of
|
||||
// the Go compiler version used to compile
|
||||
// the target binary.
|
||||
type GoVersion struct {
|
||||
Major int
|
||||
Minor int
|
||||
@ -70,36 +73,40 @@ func parseVersionString(ver string) (GoVersion, bool) {
|
||||
return GoVersion{}, false
|
||||
}
|
||||
|
||||
func (a *GoVersion) AfterOrEqual(b GoVersion) bool {
|
||||
if a.Major < b.Major {
|
||||
// AfterOrEqual returns whether one GoVersion is after or
|
||||
// equal to the other.
|
||||
func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
|
||||
if v.Major < b.Major {
|
||||
return false
|
||||
} else if a.Major > b.Major {
|
||||
} else if v.Major > b.Major {
|
||||
return true
|
||||
}
|
||||
|
||||
if a.Minor < b.Minor {
|
||||
if v.Minor < b.Minor {
|
||||
return false
|
||||
} else if a.Minor > b.Minor {
|
||||
} else if v.Minor > b.Minor {
|
||||
return true
|
||||
}
|
||||
|
||||
if a.Rev < b.Rev {
|
||||
if v.Rev < b.Rev {
|
||||
return false
|
||||
} else if a.Rev > b.Rev {
|
||||
} else if v.Rev > b.Rev {
|
||||
return true
|
||||
}
|
||||
|
||||
if a.Beta < b.Beta {
|
||||
if v.Beta < b.Beta {
|
||||
return false
|
||||
}
|
||||
|
||||
if a.RC < b.RC {
|
||||
if v.RC < b.RC {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDevel returns whether the GoVersion
|
||||
// is a development version.
|
||||
func (v *GoVersion) IsDevel() bool {
|
||||
return v.Major < 0
|
||||
}
|
||||
|
91
proc/proc.go
91
proc/proc.go
@ -58,6 +58,10 @@ type Process struct {
|
||||
ptraceDoneChan chan interface{}
|
||||
}
|
||||
|
||||
// New returns an initialized Process struct. Before returning,
|
||||
// it will also launch a goroutine in order to handle ptrace(2)
|
||||
// functions. For more information, see the documentation on
|
||||
// `handlePtraceFuncs`.
|
||||
func New(pid int) *Process {
|
||||
dbp := &Process{
|
||||
Pid: pid,
|
||||
@ -113,13 +117,13 @@ func (dbp *Process) Detach(kill bool) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns whether or not Delve thinks the debugged
|
||||
// Exited returns whether the debugged
|
||||
// process has exited.
|
||||
func (dbp *Process) Exited() bool {
|
||||
return dbp.exited
|
||||
}
|
||||
|
||||
// Returns whether or not Delve thinks the debugged
|
||||
// Running returns whether the debugged
|
||||
// process is currently executing.
|
||||
func (dbp *Process) Running() bool {
|
||||
for _, th := range dbp.Threads {
|
||||
@ -130,7 +134,7 @@ func (dbp *Process) Running() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Finds the executable and then uses it
|
||||
// LoadInformation finds the executable and then uses it
|
||||
// to parse the following information:
|
||||
// * Dwarf .debug_frame section
|
||||
// * Dwarf .debug_line section
|
||||
@ -153,6 +157,7 @@ func (dbp *Process) LoadInformation(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindFileLocation returns the PC for a given file:line.
|
||||
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
|
||||
pc, _, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
||||
if err != nil {
|
||||
@ -161,7 +166,7 @@ func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// Finds address of a function's line
|
||||
// FindFunctionLocation finds address of a function's line
|
||||
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
|
||||
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
||||
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
|
||||
@ -203,26 +208,26 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
||||
return origfn.Entry, nil
|
||||
}
|
||||
|
||||
// Sends out a request that the debugged process halt
|
||||
// execution. Sends SIGSTOP to all threads.
|
||||
// RequestManualStop sets the `halt` flag and
|
||||
// sends SIGSTOP to all threads.
|
||||
func (dbp *Process) RequestManualStop() error {
|
||||
dbp.halt = true
|
||||
return dbp.requestManualStop()
|
||||
}
|
||||
|
||||
// Sets a breakpoint at addr, and stores it in the process wide
|
||||
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
||||
// break point table. Setting a break point must be thread specific due to
|
||||
// ptrace actions needing the thread to be in a signal-delivery-stop.
|
||||
func (dbp *Process) SetBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, false)
|
||||
return dbp.setBreakpoint(dbp.CurrentThread.ID, addr, false)
|
||||
}
|
||||
|
||||
// Sets a temp breakpoint, for the 'next' command.
|
||||
// SetTempBreakpoint sets a temp breakpoint. Used during 'next' operations.
|
||||
func (dbp *Process) SetTempBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, true)
|
||||
return dbp.setBreakpoint(dbp.CurrentThread.ID, addr, true)
|
||||
}
|
||||
|
||||
// Clears a breakpoint.
|
||||
// ClearBreakpoint clears the breakpoint at addr.
|
||||
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||
bp, ok := dbp.FindBreakpoint(addr)
|
||||
if !ok {
|
||||
@ -238,12 +243,12 @@ func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
// Returns the status of the current main thread context.
|
||||
// Status returns the status of the current main thread context.
|
||||
func (dbp *Process) Status() *sys.WaitStatus {
|
||||
return dbp.CurrentThread.Status
|
||||
}
|
||||
|
||||
// Step over function calls.
|
||||
// Next continues execution until the next source line.
|
||||
func (dbp *Process) Next() (err error) {
|
||||
for i := range dbp.Breakpoints {
|
||||
if dbp.Breakpoints[i].Temp {
|
||||
@ -272,7 +277,7 @@ func (dbp *Process) Next() (err error) {
|
||||
switch t := err.(type) {
|
||||
case ThreadBlockedError, NoReturnAddr: // Noop
|
||||
case GoroutineExitingError:
|
||||
goroutineExiting = t.goid == g.Id
|
||||
goroutineExiting = t.goid == g.ID
|
||||
default:
|
||||
dbp.clearTempBreakpoints()
|
||||
return
|
||||
@ -282,7 +287,7 @@ func (dbp *Process) Next() (err error) {
|
||||
if !goroutineExiting {
|
||||
for i := range dbp.Breakpoints {
|
||||
if dbp.Breakpoints[i].Temp {
|
||||
dbp.Breakpoints[i].Cond = g.Id
|
||||
dbp.Breakpoints[i].Cond = g.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,6 +325,9 @@ func (dbp *Process) setChanRecvBreakpoints() (int, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Continue continues execution of the debugged
|
||||
// process. It will continue until it hits a breakpoint
|
||||
// or is otherwise stopped.
|
||||
func (dbp *Process) Continue() error {
|
||||
for {
|
||||
if err := dbp.resume(); err != nil {
|
||||
@ -373,21 +381,22 @@ func (dbp *Process) Continue() error {
|
||||
func (dbp *Process) pickCurrentThread(trapthread *Thread) error {
|
||||
for _, th := range dbp.Threads {
|
||||
if th.onTriggeredTempBreakpoint() {
|
||||
return dbp.SwitchThread(th.Id)
|
||||
return dbp.SwitchThread(th.ID)
|
||||
}
|
||||
}
|
||||
if trapthread.onTriggeredBreakpoint() {
|
||||
return dbp.SwitchThread(trapthread.Id)
|
||||
return dbp.SwitchThread(trapthread.ID)
|
||||
}
|
||||
for _, th := range dbp.Threads {
|
||||
if th.onTriggeredBreakpoint() {
|
||||
return dbp.SwitchThread(th.Id)
|
||||
return dbp.SwitchThread(th.ID)
|
||||
}
|
||||
}
|
||||
return dbp.SwitchThread(trapthread.Id)
|
||||
return dbp.SwitchThread(trapthread.ID)
|
||||
}
|
||||
|
||||
// Single step, will execute a single instruction.
|
||||
// Step will continue the debugged process for exactly
|
||||
// one instruction.
|
||||
func (dbp *Process) Step() (err error) {
|
||||
fn := func() error {
|
||||
for _, th := range dbp.Threads {
|
||||
@ -404,7 +413,7 @@ func (dbp *Process) Step() (err error) {
|
||||
return dbp.run(fn)
|
||||
}
|
||||
|
||||
// Change from current thread to the thread specified by `tid`.
|
||||
// SwitchThread changes from current thread to the thread specified by `tid`.
|
||||
func (dbp *Process) SwitchThread(tid int) error {
|
||||
if th, ok := dbp.Threads[tid]; ok {
|
||||
dbp.CurrentThread = th
|
||||
@ -414,7 +423,8 @@ func (dbp *Process) SwitchThread(tid int) error {
|
||||
return fmt.Errorf("thread %d does not exist", tid)
|
||||
}
|
||||
|
||||
// Change from current thread to the thread running the specified goroutine
|
||||
// SwitchGoroutine changes from current thread to the thread
|
||||
// running the specified goroutine.
|
||||
func (dbp *Process) SwitchGoroutine(gid int) error {
|
||||
g, err := dbp.FindGoroutine(gid)
|
||||
if err != nil {
|
||||
@ -425,13 +435,13 @@ func (dbp *Process) SwitchGoroutine(gid int) error {
|
||||
return nil
|
||||
}
|
||||
if g.thread != nil {
|
||||
return dbp.SwitchThread(g.thread.Id)
|
||||
return dbp.SwitchThread(g.thread.ID)
|
||||
}
|
||||
dbp.SelectedGoroutine = g
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns an array of G structures representing the information
|
||||
// GoroutinesInfo returns an array of G structures representing the information
|
||||
// Delve cares about from the internal runtime G structure.
|
||||
func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
||||
if dbp.allGCache != nil {
|
||||
@ -450,7 +460,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
||||
}
|
||||
g, _ := dbp.Threads[i].GetG()
|
||||
if g != nil {
|
||||
threadg[g.Id] = dbp.Threads[i]
|
||||
threadg[g.ID] = dbp.Threads[i]
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,7 +491,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if thread, allocated := threadg[g.Id]; allocated {
|
||||
if thread, allocated := threadg[g.ID]; allocated {
|
||||
loc, err := thread.Location()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -498,7 +508,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
||||
return allg, nil
|
||||
}
|
||||
|
||||
// Stop all threads.
|
||||
// Halt stops all threads.
|
||||
func (dbp *Process) Halt() (err error) {
|
||||
for _, th := range dbp.Threads {
|
||||
if err := th.Halt(); err != nil {
|
||||
@ -508,43 +518,44 @@ func (dbp *Process) Halt() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtains register values from what Delve considers to be the current
|
||||
// thread of the traced process.
|
||||
// Registers obtains register values from the
|
||||
// "current" thread of the traced process.
|
||||
func (dbp *Process) Registers() (Registers, error) {
|
||||
return dbp.CurrentThread.Registers()
|
||||
}
|
||||
|
||||
// Returns the PC of the current thread.
|
||||
// PC returns the PC of the current thread.
|
||||
func (dbp *Process) PC() (uint64, error) {
|
||||
return dbp.CurrentThread.PC()
|
||||
}
|
||||
|
||||
// Returns the PC of the current thread.
|
||||
// CurrentBreakpoint returns the breakpoint the current thread
|
||||
// is stopped at.
|
||||
func (dbp *Process) CurrentBreakpoint() *Breakpoint {
|
||||
return dbp.CurrentThread.CurrentBreakpoint
|
||||
}
|
||||
|
||||
// Returns a reader for the dwarf data
|
||||
// DwarfReader returns a reader for the dwarf data
|
||||
func (dbp *Process) DwarfReader() *reader.Reader {
|
||||
return reader.New(dbp.dwarf)
|
||||
}
|
||||
|
||||
// Returns list of source files that comprise the debugged binary.
|
||||
// Sources returns list of source files that comprise the debugged binary.
|
||||
func (dbp *Process) Sources() map[string]*gosym.Obj {
|
||||
return dbp.goSymTable.Files
|
||||
}
|
||||
|
||||
// Returns list of functions present in the debugged program.
|
||||
// Funcs returns list of functions present in the debugged program.
|
||||
func (dbp *Process) Funcs() []gosym.Func {
|
||||
return dbp.goSymTable.Funcs
|
||||
}
|
||||
|
||||
// Converts an instruction address to a file/line/function.
|
||||
// PCToLine converts an instruction address to a file/line/function.
|
||||
func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) {
|
||||
return dbp.goSymTable.PCToLine(pc)
|
||||
}
|
||||
|
||||
// Finds the breakpoint for the given ID.
|
||||
// FindBreakpointByID finds the breakpoint for the given ID.
|
||||
func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
|
||||
for _, bp := range dbp.Breakpoints {
|
||||
if bp.ID == id {
|
||||
@ -554,7 +565,7 @@ func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Finds the breakpoint for the given pc.
|
||||
// FindBreakpoint finds the breakpoint for the given pc.
|
||||
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
|
||||
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
|
||||
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
|
||||
@ -695,6 +706,8 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error)
|
||||
return
|
||||
}
|
||||
|
||||
// FindGoroutine returns a G struct representing the goroutine
|
||||
// specified by `gid`.
|
||||
func (dbp *Process) FindGoroutine(gid int) (*G, error) {
|
||||
if gid == -1 {
|
||||
return dbp.SelectedGoroutine, nil
|
||||
@ -705,13 +718,15 @@ func (dbp *Process) FindGoroutine(gid int) (*G, error) {
|
||||
return nil, err
|
||||
}
|
||||
for i := range gs {
|
||||
if gs[i].Id == gid {
|
||||
if gs[i].ID == gid {
|
||||
return gs[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
||||
}
|
||||
|
||||
// ConvertEvalScope returns a new EvalScope in the context of the
|
||||
// specified goroutine ID and stack frame.
|
||||
func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
|
||||
g, err := dbp.FindGoroutine(gid)
|
||||
if err != nil {
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
sys "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Darwin specific information.
|
||||
// OSProcessDetails holds Darwin specific information.
|
||||
type OSProcessDetails struct {
|
||||
task C.mach_port_name_t // mach task for the debugged process.
|
||||
exceptionPort C.mach_port_t // mach port for receiving mach exceptions.
|
||||
@ -32,7 +32,7 @@ type OSProcessDetails struct {
|
||||
portSet C.mach_port_t
|
||||
}
|
||||
|
||||
// Create and begin debugging a new process. Uses a
|
||||
// Launch creates and begins debugging a new process. Uses a
|
||||
// custom fork/exec process in order to take advantage of
|
||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||
// Mach exceptions.
|
||||
@ -98,6 +98,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return initializeDebugProcess(dbp, "", true)
|
||||
}
|
||||
|
||||
// Kill kills the process.
|
||||
func (dbp *Process) Kill() (err error) {
|
||||
if dbp.exited {
|
||||
return nil
|
||||
@ -124,7 +125,7 @@ func (dbp *Process) Kill() (err error) {
|
||||
func (dbp *Process) requestManualStop() (err error) {
|
||||
var (
|
||||
task = C.mach_port_t(dbp.os.task)
|
||||
thread = C.mach_port_t(dbp.CurrentThread.os.thread_act)
|
||||
thread = C.mach_port_t(dbp.CurrentThread.os.threadAct)
|
||||
exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
|
||||
)
|
||||
kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
|
||||
@ -177,14 +178,14 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
|
||||
return thread, nil
|
||||
}
|
||||
thread := &Thread{
|
||||
Id: port,
|
||||
ID: port,
|
||||
dbp: dbp,
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
dbp.Threads[port] = thread
|
||||
thread.os.thread_act = C.thread_act_t(port)
|
||||
thread.os.threadAct = C.thread_act_t(port)
|
||||
if dbp.CurrentThread == nil {
|
||||
dbp.SwitchThread(thread.Id)
|
||||
dbp.SwitchThread(thread.ID)
|
||||
}
|
||||
return thread, nil
|
||||
}
|
||||
|
@ -23,17 +23,19 @@ import (
|
||||
|
||||
// Process statuses
|
||||
const (
|
||||
STATUS_SLEEPING = 'S'
|
||||
STATUS_RUNNING = 'R'
|
||||
STATUS_TRACE_STOP = 't'
|
||||
STATUS_ZOMBIE = 'Z'
|
||||
StatusSleeping = 'S'
|
||||
StatusRunning = 'R'
|
||||
StatusTraceStop = 't'
|
||||
StatusZombie = 'Z'
|
||||
)
|
||||
|
||||
// OSProcessDetails contains Linux specific
|
||||
// process details.
|
||||
type OSProcessDetails struct {
|
||||
comm string
|
||||
}
|
||||
|
||||
// Create and begin debugging a new process. First entry in
|
||||
// Launch creates and begins debugging a new process. First entry in
|
||||
// `cmd` is the program to run, and then rest are the arguments
|
||||
// to be supplied to that process.
|
||||
func Launch(cmd []string) (*Process, error) {
|
||||
@ -66,6 +68,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return initializeDebugProcess(New(pid), "", true)
|
||||
}
|
||||
|
||||
// Kill kills the target process.
|
||||
func (dbp *Process) Kill() (err error) {
|
||||
if dbp.exited {
|
||||
return nil
|
||||
@ -128,7 +131,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
|
||||
}
|
||||
|
||||
dbp.Threads[tid] = &Thread{
|
||||
Id: tid,
|
||||
ID: tid,
|
||||
dbp: dbp,
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
@ -280,7 +283,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
if err = th.Continue(); err != nil {
|
||||
if err == sys.ESRCH {
|
||||
// thread died while we were adding it
|
||||
delete(dbp.Threads, th.Id)
|
||||
delete(dbp.Threads, th.ID)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
|
||||
@ -352,31 +355,30 @@ func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||
if (pid != dbp.Pid) || (options != 0) {
|
||||
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
|
||||
return wpid, &s, err
|
||||
} else {
|
||||
// If we call wait4/waitpid on a thread that is the leader of its group,
|
||||
// with options == 0, while ptracing and the thread leader has exited leaving
|
||||
// zombies of its own then waitpid hangs forever this is apparently intended
|
||||
// behaviour in the linux kernel because it's just so convenient.
|
||||
// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
|
||||
// calls and exiting when either wait4 succeeds or we find out that the thread
|
||||
// has become a zombie.
|
||||
// References:
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
|
||||
// https://sourceware.org/bugzilla/attachment.cgi?id=5685
|
||||
for {
|
||||
wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if wpid != 0 {
|
||||
return wpid, &s, err
|
||||
}
|
||||
if status(pid, dbp.os.comm) == STATUS_ZOMBIE {
|
||||
return pid, nil, nil
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
// If we call wait4/waitpid on a thread that is the leader of its group,
|
||||
// with options == 0, while ptracing and the thread leader has exited leaving
|
||||
// zombies of its own then waitpid hangs forever this is apparently intended
|
||||
// behaviour in the linux kernel because it's just so convenient.
|
||||
// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
|
||||
// calls and exiting when either wait4 succeeds or we find out that the thread
|
||||
// has become a zombie.
|
||||
// References:
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
|
||||
// https://sourceware.org/bugzilla/attachment.cgi?id=5685
|
||||
for {
|
||||
wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if wpid != 0 {
|
||||
return wpid, &s, err
|
||||
}
|
||||
if status(pid, dbp.os.comm) == StatusZombie {
|
||||
return pid, nil, nil
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +398,7 @@ func (dbp *Process) exitGuard(err error) error {
|
||||
if err != sys.ESRCH {
|
||||
return err
|
||||
}
|
||||
if status(dbp.Pid, dbp.os.comm) == STATUS_ZOMBIE {
|
||||
if status(dbp.Pid, dbp.os.comm) == StatusZombie {
|
||||
_, err := dbp.trapWait(-1)
|
||||
return err
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func TestHalt(t *testing.T) {
|
||||
assertNoError(p.Continue(), t, "Continue")
|
||||
for _, th := range p.Threads {
|
||||
if th.running != false {
|
||||
t.Fatal("expected running = false for thread", th.Id)
|
||||
t.Fatal("expected running = false for thread", th.ID)
|
||||
}
|
||||
_, err := th.Registers()
|
||||
assertNoError(err, t, "Registers")
|
||||
@ -155,7 +155,7 @@ func TestHalt(t *testing.T) {
|
||||
t.Fatal("expected thread to be stopped, but was not")
|
||||
}
|
||||
if th.running != false {
|
||||
t.Fatal("expected running = false for thread", th.Id)
|
||||
t.Fatal("expected running = false for thread", th.ID)
|
||||
}
|
||||
_, err := th.Registers()
|
||||
assertNoError(err, t, "Registers")
|
||||
@ -343,8 +343,8 @@ func TestNextConcurrent(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
g, err := p.CurrentThread.GetG()
|
||||
assertNoError(err, t, "GetG()")
|
||||
if p.SelectedGoroutine.Id != g.Id {
|
||||
t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.Id, p.SelectedGoroutine.Id)
|
||||
if p.SelectedGoroutine.ID != g.ID {
|
||||
t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.SelectedGoroutine.ID)
|
||||
}
|
||||
if ln != tc.begin {
|
||||
t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
|
||||
@ -381,8 +381,8 @@ func TestNextConcurrentVariant2(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
g, err := p.CurrentThread.GetG()
|
||||
assertNoError(err, t, "GetG()")
|
||||
if p.SelectedGoroutine.Id != g.Id {
|
||||
t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.Id, p.SelectedGoroutine.Id)
|
||||
if p.SelectedGoroutine.ID != g.ID {
|
||||
t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.SelectedGoroutine.ID)
|
||||
}
|
||||
if ln != tc.begin {
|
||||
t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
|
||||
@ -552,7 +552,7 @@ func TestSwitchThread(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var nt int
|
||||
ct := p.CurrentThread.Id
|
||||
ct := p.CurrentThread.ID
|
||||
for tid := range p.Threads {
|
||||
if tid != ct {
|
||||
nt = tid
|
||||
@ -567,7 +567,7 @@ func TestSwitchThread(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p.CurrentThread.Id != nt {
|
||||
if p.CurrentThread.ID != nt {
|
||||
t.Fatal("Did not switch threads")
|
||||
}
|
||||
})
|
||||
@ -659,7 +659,7 @@ func TestStacktrace2(t *testing.T) {
|
||||
for i := range locations {
|
||||
t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
|
||||
}
|
||||
t.Fatalf("Stack error at main.f()\n", locations)
|
||||
t.Fatalf("Stack error at main.f()\n%v\n", locations)
|
||||
}
|
||||
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
@ -669,7 +669,7 @@ func TestStacktrace2(t *testing.T) {
|
||||
for i := range locations {
|
||||
t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
|
||||
}
|
||||
t.Fatalf("Stack error at main.g()\n", locations)
|
||||
t.Fatalf("Stack error at main.g()\n%v\n", locations)
|
||||
}
|
||||
})
|
||||
|
||||
@ -1020,17 +1020,17 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
}
|
||||
|
||||
if frame < 0 {
|
||||
t.Logf("Goroutine %d: could not find correct frame", g.Id)
|
||||
t.Logf("Goroutine %d: could not find correct frame", g.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
scope, err := p.ConvertEvalScope(g.Id, frame)
|
||||
scope, err := p.ConvertEvalScope(g.ID, frame)
|
||||
assertNoError(err, t, "ConvertEvalScope()")
|
||||
t.Logf("scope = %v", scope)
|
||||
v, err := scope.EvalVariable("i")
|
||||
t.Logf("v = %v", v)
|
||||
if err != nil {
|
||||
t.Logf("Goroutine %d: %v\n", g.Id, err)
|
||||
t.Logf("Goroutine %d: %v\n", g.ID, err)
|
||||
continue
|
||||
}
|
||||
vval, _ := constant.Int64Val(v.Value)
|
||||
@ -1049,7 +1049,7 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
assertNoError(err, t, "GetG()")
|
||||
|
||||
for i := 0; i <= 3; i++ {
|
||||
scope, err := p.ConvertEvalScope(g.Id, i+1)
|
||||
scope, err := p.ConvertEvalScope(g.ID, i+1)
|
||||
assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1))
|
||||
v, err := scope.EvalVariable("n")
|
||||
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
|
||||
@ -1215,7 +1215,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
|
||||
assertNoError(err, t, "evalVariable")
|
||||
id, _ := constant.Int64Val(v.Value)
|
||||
m[id] = i
|
||||
fmt.Printf("\tgoroutine (%d) %d: %d\n", th.Id, id, i)
|
||||
fmt.Printf("\tgoroutine (%d) %d: %d\n", th.ID, id, i)
|
||||
}
|
||||
|
||||
total := int64(0)
|
||||
|
@ -2,14 +2,17 @@ package proc
|
||||
|
||||
import sys "golang.org/x/sys/unix"
|
||||
|
||||
// PtraceDetach executes the PT_DETACH ptrace call.
|
||||
func PtraceDetach(tid, sig int) error {
|
||||
return ptrace(sys.PT_DETACH, tid, 1, uintptr(sig))
|
||||
}
|
||||
|
||||
// PtraceCont executes the PTRACE_CONT ptrace call.
|
||||
func PtraceCont(tid, sig int) error {
|
||||
return ptrace(sys.PTRACE_CONT, tid, 1, 0)
|
||||
}
|
||||
|
||||
// PtraceSingleStep returns PT_STEP ptrace call.
|
||||
func PtraceSingleStep(tid int) error {
|
||||
return ptrace(sys.PT_STEP, tid, 1, 0)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
sys "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// PtraceDetach calls ptrace(PTRACE_DETACH).
|
||||
func PtraceDetach(tid, sig int) error {
|
||||
_, _, err := sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_DETACH, uintptr(tid), 1, uintptr(sig), 0, 0)
|
||||
if err != syscall.Errno(0) {
|
||||
@ -15,14 +16,17 @@ func PtraceDetach(tid, sig int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PtraceCont executes ptrace PTRACE_CONT
|
||||
func PtraceCont(tid, sig int) error {
|
||||
return sys.PtraceCont(tid, sig)
|
||||
}
|
||||
|
||||
// PtraceSingleStep executes ptrace PTRACE_SINGLE_STEP.
|
||||
func PtraceSingleStep(tid int) error {
|
||||
return sys.PtraceSingleStep(tid)
|
||||
}
|
||||
|
||||
// PtracePokeUser execute ptrace PTRACE_POKE_USER.
|
||||
func PtracePokeUser(tid int, off, addr uintptr) error {
|
||||
_, _, err := sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_POKEUSR, uintptr(tid), uintptr(off), uintptr(addr), 0, 0)
|
||||
if err != syscall.Errno(0) {
|
||||
@ -31,6 +35,7 @@ func PtracePokeUser(tid int, off, addr uintptr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PtracePeekUser execute ptrace PTRACE_PEEK_USER.
|
||||
func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
|
||||
var val uintptr
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, syscall.PTRACE_PEEKUSR, uintptr(tid), uintptr(off), uintptr(unsafe.Pointer(&val)), 0, 0)
|
||||
|
@ -2,7 +2,7 @@ package proc
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An interface for a generic register type. The
|
||||
// Registers is an interface for a generic register type. The
|
||||
// interface encapsulates the generic values / actions
|
||||
// we need independant of arch. The concrete register types
|
||||
// will be different depending on OS/Arch.
|
||||
@ -15,18 +15,18 @@ type Registers interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// Obtains register values from the debugged process.
|
||||
func (thread *Thread) Registers() (Registers, error) {
|
||||
regs, err := registers(thread)
|
||||
// Registers obtains register values from the debugged process.
|
||||
func (t *Thread) Registers() (Registers, error) {
|
||||
regs, err := registers(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get registers: %s", err)
|
||||
}
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
// Returns the current PC for this thread.
|
||||
func (thread *Thread) PC() (uint64, error) {
|
||||
regs, err := thread.Registers()
|
||||
// PC returns the current PC for this thread.
|
||||
func (t *Thread) PC() (uint64, error) {
|
||||
regs, err := t.Registers()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -7,29 +7,30 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Regs represents CPU registers on an AMD64 processor.
|
||||
type Regs struct {
|
||||
rax uint64
|
||||
rbx uint64
|
||||
rcx uint64
|
||||
rdx uint64
|
||||
rdi uint64
|
||||
rsi uint64
|
||||
rbp uint64
|
||||
rsp uint64
|
||||
r8 uint64
|
||||
r9 uint64
|
||||
r10 uint64
|
||||
r11 uint64
|
||||
r12 uint64
|
||||
r13 uint64
|
||||
r14 uint64
|
||||
r15 uint64
|
||||
rip uint64
|
||||
rflags uint64
|
||||
cs uint64
|
||||
fs uint64
|
||||
gs uint64
|
||||
gs_base uint64
|
||||
rax uint64
|
||||
rbx uint64
|
||||
rcx uint64
|
||||
rdx uint64
|
||||
rdi uint64
|
||||
rsi uint64
|
||||
rbp uint64
|
||||
rsp uint64
|
||||
r8 uint64
|
||||
r9 uint64
|
||||
r10 uint64
|
||||
r11 uint64
|
||||
r12 uint64
|
||||
r13 uint64
|
||||
r14 uint64
|
||||
r15 uint64
|
||||
rip uint64
|
||||
rflags uint64
|
||||
cs uint64
|
||||
fs uint64
|
||||
gs uint64
|
||||
gsBase uint64
|
||||
}
|
||||
|
||||
func (r *Regs) String() string {
|
||||
@ -59,7 +60,7 @@ func (r *Regs) String() string {
|
||||
{"Cs", r.cs},
|
||||
{"Fs", r.fs},
|
||||
{"Gs", r.gs},
|
||||
{"Gs_base", r.gs_base},
|
||||
{"Gs_base", r.gsBase},
|
||||
}
|
||||
for _, reg := range regs {
|
||||
fmt.Fprintf(&buf, "%8s = %0#16x\n", reg.k, reg.v)
|
||||
@ -67,24 +68,33 @@ func (r *Regs) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PC returns the current program counter
|
||||
// i.e. the RIP CPU register.
|
||||
func (r *Regs) PC() uint64 {
|
||||
return r.rip
|
||||
}
|
||||
|
||||
// SP returns the stack pointer location,
|
||||
// i.e. the RSP register.
|
||||
func (r *Regs) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.rcx
|
||||
}
|
||||
|
||||
// TLS returns the value of the register
|
||||
// that contains the location of the thread
|
||||
// local storage segment.
|
||||
func (r *Regs) TLS() uint64 {
|
||||
return r.gs_base
|
||||
return r.gsBase
|
||||
}
|
||||
|
||||
// SetPC sets the RIP register to the value specified by `pc`.
|
||||
func (r *Regs) SetPC(thread *Thread, pc uint64) error {
|
||||
kret := C.set_pc(thread.os.thread_act, C.uint64_t(pc))
|
||||
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not set pc")
|
||||
}
|
||||
@ -94,11 +104,11 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
|
||||
func registers(thread *Thread) (Registers, error) {
|
||||
var state C.x86_thread_state64_t
|
||||
var identity C.thread_identifier_info_data_t
|
||||
kret := C.get_registers(C.mach_port_name_t(thread.os.thread_act), &state)
|
||||
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("could not get registers")
|
||||
}
|
||||
kret = C.get_identity(C.mach_port_name_t(thread.os.thread_act), &identity)
|
||||
kret = C.get_identity(C.mach_port_name_t(thread.os.threadAct), &identity)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("could not get thread identity informations")
|
||||
}
|
||||
@ -115,34 +125,34 @@ func registers(thread *Thread) (Registers, error) {
|
||||
https://chromium.googlesource.com/crashpad/crashpad/+/master/snapshot/mac/process_reader.cc
|
||||
*/
|
||||
regs := &Regs{
|
||||
rax: uint64(state.__rax),
|
||||
rbx: uint64(state.__rbx),
|
||||
rcx: uint64(state.__rcx),
|
||||
rdx: uint64(state.__rdx),
|
||||
rdi: uint64(state.__rdi),
|
||||
rsi: uint64(state.__rsi),
|
||||
rbp: uint64(state.__rbp),
|
||||
rsp: uint64(state.__rsp),
|
||||
r8: uint64(state.__r8),
|
||||
r9: uint64(state.__r9),
|
||||
r10: uint64(state.__r10),
|
||||
r11: uint64(state.__r11),
|
||||
r12: uint64(state.__r12),
|
||||
r13: uint64(state.__r13),
|
||||
r14: uint64(state.__r14),
|
||||
r15: uint64(state.__r15),
|
||||
rip: uint64(state.__rip),
|
||||
rflags: uint64(state.__rflags),
|
||||
cs: uint64(state.__cs),
|
||||
fs: uint64(state.__fs),
|
||||
gs: uint64(state.__gs),
|
||||
gs_base: uint64(identity.thread_handle),
|
||||
rax: uint64(state.__rax),
|
||||
rbx: uint64(state.__rbx),
|
||||
rcx: uint64(state.__rcx),
|
||||
rdx: uint64(state.__rdx),
|
||||
rdi: uint64(state.__rdi),
|
||||
rsi: uint64(state.__rsi),
|
||||
rbp: uint64(state.__rbp),
|
||||
rsp: uint64(state.__rsp),
|
||||
r8: uint64(state.__r8),
|
||||
r9: uint64(state.__r9),
|
||||
r10: uint64(state.__r10),
|
||||
r11: uint64(state.__r11),
|
||||
r12: uint64(state.__r12),
|
||||
r13: uint64(state.__r13),
|
||||
r14: uint64(state.__r14),
|
||||
r15: uint64(state.__r15),
|
||||
rip: uint64(state.__rip),
|
||||
rflags: uint64(state.__rflags),
|
||||
cs: uint64(state.__cs),
|
||||
fs: uint64(state.__fs),
|
||||
gs: uint64(state.__gs),
|
||||
gsBase: uint64(identity.thread_handle),
|
||||
}
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (Registers, error) {
|
||||
kret := C.get_registers(C.mach_port_name_t(thread.os.thread_act), &thread.os.registers)
|
||||
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")
|
||||
}
|
||||
@ -150,7 +160,7 @@ func (thread *Thread) saveRegisters() (Registers, error) {
|
||||
}
|
||||
|
||||
func (thread *Thread) restoreRegisters() error {
|
||||
kret := C.set_registers(C.mach_port_name_t(thread.os.thread_act), &thread.os.registers)
|
||||
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")
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import "fmt"
|
||||
import "bytes"
|
||||
import sys "golang.org/x/sys/unix"
|
||||
|
||||
// Regs is a wrapper for sys.PtraceRegs.
|
||||
type Regs struct {
|
||||
regs *sys.PtraceRegs
|
||||
}
|
||||
@ -48,25 +49,31 @@ func (r *Regs) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PC returns the value of RIP register.
|
||||
func (r *Regs) PC() uint64 {
|
||||
return r.regs.PC()
|
||||
}
|
||||
|
||||
// SP returns the value of RSP register.
|
||||
func (r *Regs) SP() uint64 {
|
||||
return r.regs.Rsp
|
||||
}
|
||||
|
||||
// CX returns the value of RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.regs.Rcx
|
||||
}
|
||||
|
||||
// TLS returns the address of the thread
|
||||
// local storage memory segment.
|
||||
func (r *Regs) TLS() uint64 {
|
||||
return r.regs.Fs_base
|
||||
}
|
||||
|
||||
// SetPC sets RIP to the value specified by 'pc'.
|
||||
func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) {
|
||||
r.regs.SetPC(pc)
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, r.regs) })
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
||||
return
|
||||
}
|
||||
|
||||
@ -75,7 +82,7 @@ func registers(thread *Thread) (Registers, error) {
|
||||
regs sys.PtraceRegs
|
||||
err error
|
||||
)
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.Id, ®s) })
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, ®s) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NoReturnAddr is returned when return address
|
||||
// could not be found during stack tracae.
|
||||
type NoReturnAddr struct {
|
||||
fn string
|
||||
}
|
||||
@ -13,6 +15,7 @@ func (nra NoReturnAddr) Error() string {
|
||||
return fmt.Sprintf("could not find return address for %s", nra.fn)
|
||||
}
|
||||
|
||||
// Stackframe represents a frame in a system stack.
|
||||
type Stackframe struct {
|
||||
// Address the function above this one on the call stack will return to.
|
||||
Current Location
|
||||
@ -22,14 +25,15 @@ type Stackframe struct {
|
||||
Ret uint64
|
||||
}
|
||||
|
||||
// Scope returns a new EvalScope using this frame.
|
||||
func (frame *Stackframe) Scope(thread *Thread) *EvalScope {
|
||||
return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA}
|
||||
}
|
||||
|
||||
// Takes an offset from RSP and returns the address of the
|
||||
// instruction the current function is going to return to.
|
||||
func (thread *Thread) ReturnAddress() (uint64, error) {
|
||||
locations, err := thread.Stacktrace(2)
|
||||
// ReturnAddress returns the return address of the function
|
||||
// this thread is executing.
|
||||
func (t *Thread) ReturnAddress() (uint64, error) {
|
||||
locations, err := t.Stacktrace(2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -39,17 +43,17 @@ func (thread *Thread) ReturnAddress() (uint64, error) {
|
||||
return locations[1].Current.PC, nil
|
||||
}
|
||||
|
||||
// Returns the stack trace for thread.
|
||||
// Stacktrace returns the stack trace for thread.
|
||||
// Note the locations in the array are return addresses not call addresses.
|
||||
func (thread *Thread) Stacktrace(depth int) ([]Stackframe, error) {
|
||||
regs, err := thread.Registers()
|
||||
func (t *Thread) Stacktrace(depth int) ([]Stackframe, error) {
|
||||
regs, err := t.Registers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return thread.dbp.stacktrace(regs.PC(), regs.SP(), depth)
|
||||
return t.dbp.stacktrace(regs.PC(), regs.SP(), depth)
|
||||
}
|
||||
|
||||
// Returns the stack trace for a goroutine.
|
||||
// GoroutineStacktrace returns the stack trace for a goroutine.
|
||||
// Note the locations in the array are return addresses not call addresses.
|
||||
func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Stackframe, error) {
|
||||
if g.thread != nil {
|
||||
@ -59,17 +63,23 @@ func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Stackframe, error) {
|
||||
return locs, err
|
||||
}
|
||||
|
||||
// GoroutineLocation returns the location of the given
|
||||
// goroutine.
|
||||
func (dbp *Process) GoroutineLocation(g *G) *Location {
|
||||
f, l, fn := dbp.PCToLine(g.PC)
|
||||
return &Location{PC: g.PC, File: f, Line: l, Fn: fn}
|
||||
}
|
||||
|
||||
// NullAddrError is an error for a null address.
|
||||
type NullAddrError struct{}
|
||||
|
||||
func (n NullAddrError) Error() string {
|
||||
return "NULL address"
|
||||
}
|
||||
|
||||
// StackIterator holds information
|
||||
// required to iterate and walk the program
|
||||
// stack.
|
||||
type StackIterator struct {
|
||||
pc, sp uint64
|
||||
top bool
|
||||
@ -83,6 +93,7 @@ func newStackIterator(dbp *Process, pc, sp uint64) *StackIterator {
|
||||
return &StackIterator{pc: pc, sp: sp, top: true, dbp: dbp, err: nil, atend: false}
|
||||
}
|
||||
|
||||
// Next points the iterator to the next stack frame.
|
||||
func (it *StackIterator) Next() bool {
|
||||
if it.err != nil || it.atend {
|
||||
return false
|
||||
@ -112,6 +123,7 @@ func (it *StackIterator) Next() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Frame returns the frame the iterator is pointing at.
|
||||
func (it *StackIterator) Frame() Stackframe {
|
||||
if it.err != nil {
|
||||
panic(it.err)
|
||||
@ -119,6 +131,7 @@ func (it *StackIterator) Frame() Stackframe {
|
||||
return it.frame
|
||||
}
|
||||
|
||||
// Err returns the error encountered during stack iteration.
|
||||
func (it *StackIterator) Err() error {
|
||||
return it.err
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
// Thread represents a single thread in the traced process
|
||||
// Id represents the thread id or port, Process holds a reference to the
|
||||
// ID represents the thread id or port, Process holds a reference to the
|
||||
// Process struct that contains info on the process as
|
||||
// a whole, and Status represents the last result of a `wait` call
|
||||
// on this thread.
|
||||
type Thread struct {
|
||||
Id int // Thread ID or mach port
|
||||
ID int // Thread ID or mach port
|
||||
Status *sys.WaitStatus // Status returned from last wait call
|
||||
CurrentBreakpoint *Breakpoint // Breakpoint thread is currently stopped at
|
||||
BreakpointConditionMet bool // Output of evaluating the breakpoint's condition
|
||||
@ -28,7 +28,7 @@ type Thread struct {
|
||||
os *OSSpecificDetails
|
||||
}
|
||||
|
||||
// Represents the location of a thread.
|
||||
// Location represents the location of a thread.
|
||||
// Holds information on the current instruction
|
||||
// address, the source file:line, and the function.
|
||||
type Location struct {
|
||||
@ -97,7 +97,7 @@ func (thread *Thread) Step() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the threads location, including the file:line
|
||||
// Location returns the threads location, including the file:line
|
||||
// of the corresponding source code, the function we're in
|
||||
// and the current instruction address.
|
||||
func (thread *Thread) Location() (*Location, error) {
|
||||
@ -109,6 +109,8 @@ func (thread *Thread) Location() (*Location, error) {
|
||||
return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
|
||||
}
|
||||
|
||||
// ThreadBlockedError is returned when the thread
|
||||
// is blocked in the scheduler.
|
||||
type ThreadBlockedError struct{}
|
||||
|
||||
func (tbe ThreadBlockedError) Error() string {
|
||||
@ -145,7 +147,9 @@ func (thread *Thread) setNextBreakpoints() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Go routine is exiting.
|
||||
// GoroutineExitingError is returned when the
|
||||
// goroutine specified by `goid` is in the process
|
||||
// of exiting.
|
||||
type GoroutineExitingError struct {
|
||||
goid int
|
||||
}
|
||||
@ -202,7 +206,7 @@ func (thread *Thread) next(curpc uint64, fde *frame.FrameDescriptionEntry, file
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return GoroutineExitingError{goid: g.Id}
|
||||
return GoroutineExitingError{goid: g.ID}
|
||||
}
|
||||
}
|
||||
pcs = append(pcs, ret)
|
||||
@ -236,7 +240,7 @@ func (thread *Thread) setNextTempBreakpoints(curpc uint64, pcs []uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets the PC for this thread.
|
||||
// SetPC sets the PC for this thread.
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
@ -245,7 +249,7 @@ func (thread *Thread) SetPC(pc uint64) error {
|
||||
return regs.SetPC(thread, pc)
|
||||
}
|
||||
|
||||
// Returns information on the G (goroutine) that is executing on this thread.
|
||||
// GetG returns information on the G (goroutine) that is executing on this thread.
|
||||
//
|
||||
// The G structure for a thread is stored in thread local storage. Here we simply
|
||||
// calculate the address and read and parse the G struct.
|
||||
@ -284,14 +288,14 @@ func (thread *Thread) GetG() (g *G, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns whether the thread is stopped at
|
||||
// Stopped returns whether the thread is stopped at
|
||||
// the operating system level. Actual implementation
|
||||
// is OS dependant, look in OS thread file.
|
||||
func (thread *Thread) Stopped() bool {
|
||||
return thread.stopped()
|
||||
}
|
||||
|
||||
// Stops this thread from executing. Actual
|
||||
// Halt stops this thread from executing. Actual
|
||||
// implementation is OS dependant. Look in OS
|
||||
// thread file.
|
||||
func (thread *Thread) Halt() (err error) {
|
||||
@ -307,6 +311,7 @@ func (thread *Thread) Halt() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Scope returns the current EvalScope for this thread.
|
||||
func (thread *Thread) Scope() (*EvalScope, error) {
|
||||
locations, err := thread.Stacktrace(0)
|
||||
if err != nil {
|
||||
@ -315,6 +320,8 @@ func (thread *Thread) Scope() (*EvalScope, error) {
|
||||
return locations[0].Scope(thread), nil
|
||||
}
|
||||
|
||||
// SetCurrentBreakpoint sets the current breakpoint that this
|
||||
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
||||
func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
thread.CurrentBreakpoint = nil
|
||||
pc, err := thread.PC()
|
||||
@ -329,7 +336,7 @@ func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
thread.BreakpointConditionMet = bp.checkCondition(thread)
|
||||
if thread.onTriggeredBreakpoint() {
|
||||
if g, err := thread.GetG(); err == nil {
|
||||
thread.CurrentBreakpoint.HitCount[g.Id]++
|
||||
thread.CurrentBreakpoint.HitCount[g.ID]++
|
||||
}
|
||||
thread.CurrentBreakpoint.TotalHitCount++
|
||||
}
|
||||
@ -337,16 +344,16 @@ func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *Thread) onTriggeredBreakpoint() bool {
|
||||
return (th.CurrentBreakpoint != nil) && th.BreakpointConditionMet
|
||||
func (thread *Thread) onTriggeredBreakpoint() bool {
|
||||
return (thread.CurrentBreakpoint != nil) && thread.BreakpointConditionMet
|
||||
}
|
||||
|
||||
func (th *Thread) onTriggeredTempBreakpoint() bool {
|
||||
return th.onTriggeredBreakpoint() && th.CurrentBreakpoint.Temp
|
||||
func (thread *Thread) onTriggeredTempBreakpoint() bool {
|
||||
return thread.onTriggeredBreakpoint() && thread.CurrentBreakpoint.Temp
|
||||
}
|
||||
|
||||
func (th *Thread) onRuntimeBreakpoint() bool {
|
||||
loc, err := th.Location()
|
||||
func (thread *Thread) onRuntimeBreakpoint() bool {
|
||||
loc, err := thread.Location()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -8,36 +8,40 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// OSSpecificDetails holds information specific to the OSX/Darwin
|
||||
// operating system / kernel.
|
||||
type OSSpecificDetails struct {
|
||||
thread_act C.thread_act_t
|
||||
registers C.x86_thread_state64_t
|
||||
threadAct C.thread_act_t
|
||||
registers C.x86_thread_state64_t
|
||||
}
|
||||
|
||||
// ErrContinueThread is the error returned when a thread could not
|
||||
// be continued.
|
||||
var ErrContinueThread = fmt.Errorf("could not continue thread")
|
||||
|
||||
func (t *Thread) halt() (err error) {
|
||||
kret := C.thread_suspend(t.os.thread_act)
|
||||
kret := C.thread_suspend(t.os.threadAct)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret)))
|
||||
err = fmt.Errorf("could not suspend thread %d %s", t.Id, errStr)
|
||||
err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Thread) singleStep() error {
|
||||
kret := C.single_step(t.os.thread_act)
|
||||
kret := C.single_step(t.os.threadAct)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not single step")
|
||||
}
|
||||
for {
|
||||
port := C.mach_port_wait(t.dbp.os.portSet, C.int(0))
|
||||
if port == C.mach_port_t(t.Id) {
|
||||
if port == C.mach_port_t(t.ID) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
kret = C.clear_trap_flag(t.os.thread_act)
|
||||
kret = C.clear_trap_flag(t.os.threadAct)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not clear CPU trap flag")
|
||||
}
|
||||
@ -52,20 +56,20 @@ func (t *Thread) resume() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
kret := C.resume_thread(t.os.thread_act)
|
||||
kret := C.resume_thread(t.os.threadAct)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return ErrContinueThread
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (thread *Thread) blocked() bool {
|
||||
func (t *Thread) blocked() bool {
|
||||
// TODO(dp) cache the func pc to remove this lookup
|
||||
pc, err := thread.PC()
|
||||
pc, err := t.PC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fn := thread.dbp.goSymTable.PCToFunc(pc)
|
||||
fn := t.dbp.goSymTable.PCToFunc(pc)
|
||||
if fn == nil {
|
||||
return false
|
||||
}
|
||||
@ -77,37 +81,37 @@ func (thread *Thread) blocked() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (thread *Thread) stopped() bool {
|
||||
return C.thread_blocked(thread.os.thread_act) > C.int(0)
|
||||
func (t *Thread) stopped() bool {
|
||||
return C.thread_blocked(t.os.threadAct) > C.int(0)
|
||||
}
|
||||
|
||||
func (thread *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
|
||||
func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var (
|
||||
vm_data = unsafe.Pointer(&data[0])
|
||||
vm_addr = C.mach_vm_address_t(addr)
|
||||
length = C.mach_msg_type_number_t(len(data))
|
||||
vmData = unsafe.Pointer(&data[0])
|
||||
vmAddr = C.mach_vm_address_t(addr)
|
||||
length = C.mach_msg_type_number_t(len(data))
|
||||
)
|
||||
if ret := C.write_memory(thread.dbp.os.task, vm_addr, vm_data, length); ret < 0 {
|
||||
if ret := C.write_memory(t.dbp.os.task, vmAddr, vmData, length); ret < 0 {
|
||||
return 0, fmt.Errorf("could not write memory")
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readMemory(addr uintptr, size int) ([]byte, error) {
|
||||
func (t *Thread) readMemory(addr uintptr, size int) ([]byte, error) {
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var (
|
||||
buf = make([]byte, size)
|
||||
vm_data = unsafe.Pointer(&buf[0])
|
||||
vm_addr = C.mach_vm_address_t(addr)
|
||||
length = C.mach_msg_type_number_t(size)
|
||||
buf = make([]byte, size)
|
||||
vmData = unsafe.Pointer(&buf[0])
|
||||
vmAddr = C.mach_vm_address_t(addr)
|
||||
length = C.mach_msg_type_number_t(size)
|
||||
)
|
||||
|
||||
ret := C.read_memory(thread.dbp.os.task, vm_addr, vm_data, length)
|
||||
ret := C.read_memory(t.dbp.os.task, vmAddr, vmData, length)
|
||||
if ret < 0 {
|
||||
return nil, fmt.Errorf("could not read memory")
|
||||
}
|
||||
|
@ -6,48 +6,48 @@ import (
|
||||
sys "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Not actually used, but necessary
|
||||
// to be defined.
|
||||
// OSSpecificDetails hold Linux specific
|
||||
// process details.
|
||||
type OSSpecificDetails struct {
|
||||
registers sys.PtraceRegs
|
||||
}
|
||||
|
||||
func (t *Thread) halt() (err error) {
|
||||
err = sys.Tgkill(t.dbp.Pid, t.Id, sys.SIGSTOP)
|
||||
err = sys.Tgkill(t.dbp.Pid, t.ID, sys.SIGSTOP)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("halt err %s on thread %d", err, t.Id)
|
||||
err = fmt.Errorf("halt err %s on thread %d", err, t.ID)
|
||||
return
|
||||
}
|
||||
_, _, err = t.dbp.wait(t.Id, 0)
|
||||
_, _, err = t.dbp.wait(t.ID, 0)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("wait err %s on thread %d", err, t.Id)
|
||||
err = fmt.Errorf("wait err %s on thread %d", err, t.ID)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (thread *Thread) stopped() bool {
|
||||
state := status(thread.Id, thread.dbp.os.comm)
|
||||
return state == STATUS_TRACE_STOP
|
||||
func (t *Thread) stopped() bool {
|
||||
state := status(t.ID, t.dbp.os.comm)
|
||||
return state == StatusTraceStop
|
||||
}
|
||||
|
||||
func (t *Thread) resume() (err error) {
|
||||
t.running = true
|
||||
t.dbp.execPtraceFunc(func() { err = PtraceCont(t.Id, 0) })
|
||||
t.dbp.execPtraceFunc(func() { err = PtraceCont(t.ID, 0) })
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Thread) singleStep() (err error) {
|
||||
for {
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceSingleStep(t.Id) })
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceSingleStep(t.ID) })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wpid, status, err := t.dbp.wait(t.Id, 0)
|
||||
wpid, status, err := t.dbp.wait(t.ID, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wpid == t.Id && status.StopSignal() == sys.SIGTRAP {
|
||||
if wpid == t.ID && status.StopSignal() == sys.SIGTRAP {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -62,33 +62,33 @@ func (t *Thread) blocked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (Registers, error) {
|
||||
func (t *Thread) saveRegisters() (Registers, error) {
|
||||
var err error
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.Id, &thread.os.registers) })
|
||||
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{&thread.os.registers}, nil
|
||||
return &Regs{&t.os.registers}, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) restoreRegisters() (err error) {
|
||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, &thread.os.registers) })
|
||||
func (t *Thread) restoreRegisters() (err error) {
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(t.ID, &t.os.registers) })
|
||||
return
|
||||
}
|
||||
|
||||
func (thread *Thread) writeMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
func (t *Thread) writeMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
thread.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(thread.Id, addr, data) })
|
||||
t.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(t.ID, addr, data) })
|
||||
return
|
||||
}
|
||||
|
||||
func (thread *Thread) readMemory(addr uintptr, size int) (data []byte, err error) {
|
||||
func (t *Thread) readMemory(addr uintptr, size int) (data []byte, err error) {
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
data = make([]byte, size)
|
||||
thread.dbp.execPtraceFunc(func() { _, err = sys.PtracePeekData(thread.Id, addr, data) })
|
||||
t.dbp.execPtraceFunc(func() { _, err = sys.PtracePeekData(t.ID, addr, data) })
|
||||
return
|
||||
}
|
||||
|
@ -21,14 +21,17 @@ const (
|
||||
maxArrayValues = 64 // Max value for reading large arrays.
|
||||
maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs
|
||||
|
||||
ChanRecv = "chan receive"
|
||||
ChanSend = "chan send"
|
||||
chanRecv = "chan receive"
|
||||
chanSend = "chan send"
|
||||
|
||||
hashTophashEmpty = 0 // used by map reading code, indicates an empty bucket
|
||||
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated
|
||||
)
|
||||
|
||||
// Represents a variable.
|
||||
// Variable represents a variable. It contains the address, name,
|
||||
// type and other information parsed from both the Dwarf information
|
||||
// and the memory of the debugged process.
|
||||
// If OnlyAddr is true, the variables value has not been loaded.
|
||||
type Variable struct {
|
||||
Addr uintptr
|
||||
OnlyAddr bool
|
||||
@ -60,7 +63,7 @@ type Variable struct {
|
||||
Unreadable error
|
||||
}
|
||||
|
||||
// Represents a runtime M (OS thread) structure.
|
||||
// M represents a runtime M (OS thread) structure.
|
||||
type M struct {
|
||||
procid int // Thread ID or port.
|
||||
spinning uint8 // Busy looping.
|
||||
@ -68,23 +71,23 @@ type M struct {
|
||||
curg uintptr // Current G running on this thread.
|
||||
}
|
||||
|
||||
// G status, from: src/runtime/runtime2.go
|
||||
const (
|
||||
// G status, from: src/runtime/runtime2.go
|
||||
Gidle uint64 = iota // 0
|
||||
Grunnable // 1 runnable and on a run queue
|
||||
Grunning // 2
|
||||
Gsyscall // 3
|
||||
Gwaiting // 4
|
||||
Gmoribund_unused // 5 currently unused, but hardcoded in gdb scripts
|
||||
Gdead // 6
|
||||
Genqueue // 7 Only the Gscanenqueue is used.
|
||||
Gcopystack // 8 in this state when newstack is moving the stack
|
||||
Gidle uint64 = iota // 0
|
||||
Grunnable // 1 runnable and on a run queue
|
||||
Grunning // 2
|
||||
Gsyscall // 3
|
||||
Gwaiting // 4
|
||||
GmoribundUnused // 5 currently unused, but hardcoded in gdb scripts
|
||||
Gdead // 6
|
||||
Genqueue // 7 Only the Gscanenqueue is used.
|
||||
Gcopystack // 8 in this state when newstack is moving the stack
|
||||
)
|
||||
|
||||
// Represents a runtime G (goroutine) structure (at least the
|
||||
// G represents a runtime G (goroutine) structure (at least the
|
||||
// fields that Delve is interested in).
|
||||
type G struct {
|
||||
Id int // Goroutine ID
|
||||
ID int // Goroutine ID
|
||||
PC uint64 // PC of goroutine when it was parked.
|
||||
SP uint64 // SP of goroutine when it was parked.
|
||||
GoPC uint64 // PC of 'go' statement that created this goroutine.
|
||||
@ -103,13 +106,15 @@ type G struct {
|
||||
dbp *Process
|
||||
}
|
||||
|
||||
// Scope for variable evaluation
|
||||
// EvalScope is the scope for variable evaluation. Contains the thread,
|
||||
// current location (PC), and canonical frame address.
|
||||
type EvalScope struct {
|
||||
Thread *Thread
|
||||
PC uint64
|
||||
CFA int64
|
||||
}
|
||||
|
||||
// IsNilErr is returned when a variable is nil.
|
||||
type IsNilErr struct {
|
||||
name string
|
||||
}
|
||||
@ -127,9 +132,8 @@ func ptrTypeKind(t *dwarf.PtrType) reflect.Kind {
|
||||
return reflect.Map
|
||||
} else if isvoid {
|
||||
return reflect.UnsafePointer
|
||||
} else {
|
||||
return reflect.Ptr
|
||||
}
|
||||
return reflect.Ptr
|
||||
}
|
||||
|
||||
func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) *Variable {
|
||||
@ -150,7 +154,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
|
||||
case t.StructName == "string":
|
||||
v.Kind = reflect.String
|
||||
v.stride = 1
|
||||
v.fieldType = &dwarf.UintType{dwarf.BasicType{dwarf.CommonType{1, "byte"}, 8, 0}}
|
||||
v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}}
|
||||
if v.Addr != 0 {
|
||||
v.base, v.Len, v.Unreadable = v.thread.readStringInfo(v.Addr)
|
||||
}
|
||||
@ -248,6 +252,8 @@ func (v *Variable) clone() *Variable {
|
||||
return &r
|
||||
}
|
||||
|
||||
// TypeString returns the string representation
|
||||
// of the type of this variable.
|
||||
func (v *Variable) TypeString() string {
|
||||
if v == nilVariable {
|
||||
return "nil"
|
||||
@ -278,22 +284,26 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
|
||||
return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread), nil
|
||||
}
|
||||
|
||||
// DwarfReader returns the DwarfReader containing the
|
||||
// Dwarf information for the target process.
|
||||
func (scope *EvalScope) DwarfReader() *reader.Reader {
|
||||
return scope.Thread.dbp.DwarfReader()
|
||||
}
|
||||
|
||||
// Type returns the Dwarf type entry at `offset`.
|
||||
func (scope *EvalScope) Type(offset dwarf.Offset) (dwarf.Type, error) {
|
||||
return scope.Thread.dbp.dwarf.Type(offset)
|
||||
}
|
||||
|
||||
// PtrSize returns the size of a pointer.
|
||||
func (scope *EvalScope) PtrSize() int {
|
||||
return scope.Thread.dbp.arch.PtrSize()
|
||||
}
|
||||
|
||||
// Returns whether the goroutine is blocked on
|
||||
// ChanRecvBlocked eturns whether the goroutine is blocked on
|
||||
// a channel read operation.
|
||||
func (g *G) ChanRecvBlocked() bool {
|
||||
return g.WaitReason == ChanRecv
|
||||
return g.WaitReason == chanRecv
|
||||
}
|
||||
|
||||
// chanRecvReturnAddr returns the address of the return from a channel read.
|
||||
@ -328,7 +338,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
initialInstructions = append([]byte{op.DW_OP_addr}, gaddrbytes...)
|
||||
gaddr = binary.LittleEndian.Uint64(gaddrbytes)
|
||||
if gaddr == 0 {
|
||||
return nil, NoGError{tid: thread.Id}
|
||||
return nil, NoGError{tid: thread.ID}
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,7 +435,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
|
||||
f, l, fn := thread.dbp.goSymTable.PCToLine(pc)
|
||||
g := &G{
|
||||
Id: int(goid),
|
||||
ID: int(goid),
|
||||
GoPC: gopc,
|
||||
PC: pc,
|
||||
SP: sp,
|
||||
@ -446,6 +456,8 @@ func isExportedRuntime(name string) bool {
|
||||
return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
|
||||
}
|
||||
|
||||
// UserCurrent returns the location the users code is at,
|
||||
// or was at before entering a runtime function.
|
||||
func (g *G) UserCurrent() Location {
|
||||
pc, sp := g.PC, g.SP
|
||||
if g.thread != nil {
|
||||
@ -466,17 +478,19 @@ func (g *G) UserCurrent() Location {
|
||||
return g.CurrentLoc
|
||||
}
|
||||
|
||||
// Go returns the location of the 'go' statement
|
||||
// that spawned this goroutine.
|
||||
func (g *G) Go() Location {
|
||||
f, l, fn := g.dbp.goSymTable.PCToLine(g.GoPC)
|
||||
return Location{PC: g.GoPC, File: f, Line: l, Fn: fn}
|
||||
}
|
||||
|
||||
// Returns the value of the given expression (backwards compatibility).
|
||||
// EvalVariable returns the value of the given expression (backwards compatibility).
|
||||
func (scope *EvalScope) EvalVariable(name string) (*Variable, error) {
|
||||
return scope.EvalExpression(name)
|
||||
}
|
||||
|
||||
// Sets the value of the named variable
|
||||
// SetVariable sets the value of the named variable
|
||||
func (scope *EvalScope) SetVariable(name, value string) error {
|
||||
t, err := parser.ParseExpr(name)
|
||||
if err != nil {
|
||||
@ -566,10 +580,9 @@ func (scope *EvalScope) FunctionArguments() ([]*Variable, error) {
|
||||
|
||||
// PackageVariables returns the name, value, and type of all package variables in the application.
|
||||
func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
|
||||
var vars []*Variable
|
||||
reader := scope.DwarfReader()
|
||||
|
||||
vars := make([]*Variable, 0)
|
||||
|
||||
for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -586,6 +599,8 @@ func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
// EvalPackageVariable will evaluate the package level variable
|
||||
// specified by 'name'.
|
||||
func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) {
|
||||
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
||||
|
||||
@ -669,9 +684,8 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
default:
|
||||
if v.Name == "" {
|
||||
return nil, fmt.Errorf("type %s is not a struct", structVar.TypeString())
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.TypeString())
|
||||
}
|
||||
return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.TypeString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1480,8 +1494,7 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vars := make([]*Variable, 0)
|
||||
|
||||
var vars []*Variable
|
||||
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"github.com/derekparker/delve/proc"
|
||||
)
|
||||
|
||||
// ConvertBreakpoint converts from a proc.Breakpoint to
|
||||
// an api.Breakpoint.
|
||||
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
b := &Breakpoint{
|
||||
ID: bp.ID,
|
||||
@ -31,6 +33,8 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
return b
|
||||
}
|
||||
|
||||
// ConvertThread converts a proc.Thread into an
|
||||
// api thread.
|
||||
func ConvertThread(th *proc.Thread) *Thread {
|
||||
var (
|
||||
function *Function
|
||||
@ -55,11 +59,11 @@ func ConvertThread(th *proc.Thread) *Thread {
|
||||
}
|
||||
|
||||
if g, _ := th.GetG(); g != nil {
|
||||
gid = g.Id
|
||||
gid = g.ID
|
||||
}
|
||||
|
||||
return &Thread{
|
||||
ID: th.Id,
|
||||
ID: th.ID,
|
||||
PC: pc,
|
||||
File: file,
|
||||
Line: line,
|
||||
@ -69,6 +73,7 @@ func ConvertThread(th *proc.Thread) *Thread {
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertVar converts from proc.Variable to api.Variable.
|
||||
func ConvertVar(v *proc.Variable) *Variable {
|
||||
r := Variable{
|
||||
Addr: v.Addr,
|
||||
@ -153,6 +158,8 @@ func ConvertVar(v *proc.Variable) *Variable {
|
||||
return &r
|
||||
}
|
||||
|
||||
// ConvertFunction converts from gosym.Func to
|
||||
// api.Function.
|
||||
func ConvertFunction(fn *gosym.Func) *Function {
|
||||
if fn == nil {
|
||||
return nil
|
||||
@ -166,15 +173,17 @@ func ConvertFunction(fn *gosym.Func) *Function {
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertGoroutine converts from proc.G to api.Goroutine.
|
||||
func ConvertGoroutine(g *proc.G) *Goroutine {
|
||||
return &Goroutine{
|
||||
ID: g.Id,
|
||||
ID: g.ID,
|
||||
CurrentLoc: ConvertLocation(g.CurrentLoc),
|
||||
UserCurrentLoc: ConvertLocation(g.UserCurrent()),
|
||||
GoStatementLoc: ConvertLocation(g.Go()),
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertLocation converts from proc.Location to api.Location.
|
||||
func ConvertLocation(loc proc.Location) Location {
|
||||
return Location{
|
||||
PC: loc.PC,
|
||||
|
@ -71,7 +71,7 @@ type Client interface {
|
||||
ListGoroutines() ([]*api.Goroutine, error)
|
||||
|
||||
// Returns stacktrace
|
||||
Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error)
|
||||
Stacktrace(int, int, bool) ([]api.Stackframe, error)
|
||||
|
||||
// Returns whether we attached to a running process or not
|
||||
AttachedToExistingProcess() bool
|
||||
|
@ -63,18 +63,24 @@ func New(config *Config) (*Debugger, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ProcessPid returns the PID of the process
|
||||
// the debugger is debugging.
|
||||
func (d *Debugger) ProcessPid() int {
|
||||
return d.process.Pid
|
||||
}
|
||||
|
||||
// Detach detaches from the target process.
|
||||
// If `kill` is true we will kill the process after
|
||||
// detaching.
|
||||
func (d *Debugger) Detach(kill bool) error {
|
||||
if d.config.AttachPid != 0 {
|
||||
return d.process.Detach(kill)
|
||||
} else {
|
||||
return d.process.Kill()
|
||||
}
|
||||
return d.process.Kill()
|
||||
}
|
||||
|
||||
// Restart will restart the target process, first killing
|
||||
// and then exec'ing it again.
|
||||
func (d *Debugger) Restart() error {
|
||||
if !d.process.Exited() {
|
||||
if d.process.Running() {
|
||||
@ -104,6 +110,7 @@ func (d *Debugger) Restart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// State returns the current state of the debugger.
|
||||
func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
if d.process.Exited() {
|
||||
return nil, proc.ProcessExitedError{Pid: d.ProcessPid()}
|
||||
@ -126,7 +133,7 @@ func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
for i := range d.process.Threads {
|
||||
th := api.ConvertThread(d.process.Threads[i])
|
||||
state.Threads = append(state.Threads, th)
|
||||
if i == d.process.CurrentThread.Id {
|
||||
if i == d.process.CurrentThread.ID {
|
||||
state.CurrentThread = th
|
||||
}
|
||||
}
|
||||
@ -134,6 +141,7 @@ func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// CreateBreakpoint creates a breakpoint.
|
||||
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
var (
|
||||
createdBp *api.Breakpoint
|
||||
@ -171,6 +179,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
return createdBp, nil
|
||||
}
|
||||
|
||||
// ClearBreakpoint clears a breakpoint.
|
||||
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
var clearedBp *api.Breakpoint
|
||||
bp, err := d.process.ClearBreakpoint(requestedBp.Addr)
|
||||
@ -182,6 +191,7 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
|
||||
return clearedBp, err
|
||||
}
|
||||
|
||||
// Breakpoints returns the list of current breakpoints.
|
||||
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||
bps := []*api.Breakpoint{}
|
||||
for _, bp := range d.process.Breakpoints {
|
||||
@ -193,6 +203,7 @@ func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||
return bps
|
||||
}
|
||||
|
||||
// FindBreakpoint returns the breakpoint specified by 'id'.
|
||||
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
for _, bp := range d.Breakpoints() {
|
||||
if bp.ID == id {
|
||||
@ -202,6 +213,7 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Threads returns the threads of the target process.
|
||||
func (d *Debugger) Threads() []*api.Thread {
|
||||
threads := []*api.Thread{}
|
||||
for _, th := range d.process.Threads {
|
||||
@ -210,6 +222,7 @@ func (d *Debugger) Threads() []*api.Thread {
|
||||
return threads
|
||||
}
|
||||
|
||||
// FindThread returns the thread for the given 'id'.
|
||||
func (d *Debugger) FindThread(id int) *api.Thread {
|
||||
for _, thread := range d.Threads() {
|
||||
if thread.ID == id {
|
||||
@ -324,6 +337,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sources returns a list of the source files for target binary.
|
||||
func (d *Debugger) Sources(filter string) ([]string, error) {
|
||||
regex, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
@ -339,6 +353,7 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Functions returns a list of functions in the target process.
|
||||
func (d *Debugger) Functions(filter string) ([]string, error) {
|
||||
return regexFilterFuncs(filter, d.process.Funcs())
|
||||
}
|
||||
@ -358,6 +373,8 @@ func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
// PackageVariables returns a list of package variables for the thread,
|
||||
// optionally regexp filtered using regexp described in 'filter'.
|
||||
func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable, error) {
|
||||
regex, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
@ -385,6 +402,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
|
||||
return vars, err
|
||||
}
|
||||
|
||||
// Registers returns string representation of the CPU registers.
|
||||
func (d *Debugger) Registers(threadID int) (string, error) {
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
@ -405,6 +423,7 @@ func convertVars(pv []*proc.Variable) []api.Variable {
|
||||
return vars
|
||||
}
|
||||
|
||||
// LocalVariables returns a list of the local variables.
|
||||
func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
@ -417,6 +436,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
|
||||
return convertVars(pv), err
|
||||
}
|
||||
|
||||
// FunctionArguments returns the arguments to the current function.
|
||||
func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
@ -429,6 +449,8 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error
|
||||
return convertVars(pv), nil
|
||||
}
|
||||
|
||||
// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
|
||||
// in the scope provided.
|
||||
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
@ -441,6 +463,8 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api
|
||||
return api.ConvertVar(v), err
|
||||
}
|
||||
|
||||
// SetVariableInScope will set the value of the variable represented by
|
||||
// 'symbol' to the value given, in the given scope.
|
||||
func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
@ -449,6 +473,7 @@ func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string)
|
||||
return s.SetVariable(symbol, value)
|
||||
}
|
||||
|
||||
// Goroutines will return a list of goroutines in the target process.
|
||||
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
||||
goroutines := []*api.Goroutine{}
|
||||
gs, err := d.process.GoroutinesInfo()
|
||||
@ -461,10 +486,13 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
||||
return goroutines, err
|
||||
}
|
||||
|
||||
func (d *Debugger) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) {
|
||||
// Stacktrace returns a list of Stackframes for the given goroutine. The
|
||||
// length of the returned list will be min(stack_len, depth).
|
||||
// If 'full' is true, then local vars, function args, etc will be returned as well.
|
||||
func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackframe, error) {
|
||||
var rawlocs []proc.Stackframe
|
||||
|
||||
g, err := d.process.FindGoroutine(goroutineId)
|
||||
g, err := d.process.FindGoroutine(goroutineID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -506,6 +534,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]ap
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
// FindLocation will find the location specified by 'locStr'.
|
||||
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
|
||||
loc, err := parseLocationSpec(locStr)
|
||||
if err != nil {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package service
|
||||
|
||||
// Server represents a server for a remote client
|
||||
// to connect to.
|
||||
type Server interface {
|
||||
Run() error
|
||||
Stop(bool) error
|
||||
|
@ -43,13 +43,14 @@ func (c command) match(cmdstr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Commands represents the commands for Delve terminal process.
|
||||
type Commands struct {
|
||||
cmds []command
|
||||
lastCmd cmdfunc
|
||||
client service.Client
|
||||
}
|
||||
|
||||
// Returns a Commands struct with default commands defined.
|
||||
// DebugCommands returns a Commands struct with default commands defined.
|
||||
func DebugCommands(client service.Client) *Commands {
|
||||
c := &Commands{client: client}
|
||||
|
||||
@ -130,12 +131,6 @@ func (c *Commands) Merge(allAliases map[string][]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func CommandFunc(fn func() error) cmdfunc {
|
||||
return func(t *Term, args string) error {
|
||||
return fn()
|
||||
}
|
||||
}
|
||||
|
||||
func noCmdAvailable(t *Term, args string) error {
|
||||
return fmt.Errorf("command not available")
|
||||
}
|
||||
@ -241,7 +236,7 @@ func goroutines(t *Term, argstr string) error {
|
||||
case "-g":
|
||||
fgl = fglGo
|
||||
default:
|
||||
fmt.Errorf("wrong argument: '%s'", args[0])
|
||||
return fmt.Errorf("wrong argument: '%s'", args[0])
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("too many arguments")
|
||||
@ -298,7 +293,7 @@ func frame(t *Term, args string) error {
|
||||
}
|
||||
|
||||
func scopePrefix(t *Term, cmdstr string) error {
|
||||
scope := api.EvalScope{-1, 0}
|
||||
scope := api.EvalScope{GoroutineID: -1, Frame: 0}
|
||||
lastcmd := ""
|
||||
rest := cmdstr
|
||||
|
||||
@ -507,7 +502,7 @@ func clearAll(t *Term, args string) error {
|
||||
|
||||
var locPCs map[uint64]struct{}
|
||||
if args != "" {
|
||||
locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args)
|
||||
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -533,18 +528,19 @@ func clearAll(t *Term, args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ById []*api.Breakpoint
|
||||
// ByID sorts breakpoints by ID.
|
||||
type ByID []*api.Breakpoint
|
||||
|
||||
func (a ById) Len() int { return len(a) }
|
||||
func (a ById) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ById) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||
func (a ByID) Len() int { return len(a) }
|
||||
func (a ByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||
|
||||
func breakpoints(t *Term, args string) error {
|
||||
breakPoints, err := t.client.ListBreakpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(ById(breakPoints))
|
||||
sort.Sort(ByID(breakPoints))
|
||||
for _, bp := range breakPoints {
|
||||
thing := "Breakpoint"
|
||||
if bp.Tracepoint {
|
||||
@ -594,7 +590,7 @@ func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
|
||||
}
|
||||
|
||||
requestedBp.Tracepoint = tracepoint
|
||||
locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args[0])
|
||||
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -625,13 +621,13 @@ func tracepoint(t *Term, args string) error {
|
||||
|
||||
func g0f0(fn scopedCmdfunc) cmdfunc {
|
||||
return func(t *Term, args string) error {
|
||||
return fn(t, api.EvalScope{-1, 0}, args)
|
||||
return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, args)
|
||||
}
|
||||
}
|
||||
|
||||
func g0f0filter(fn scopedFilteringFunc) filteringFunc {
|
||||
return func(t *Term, filter string) ([]string, error) {
|
||||
return fn(t, api.EvalScope{-1, 0}, filter)
|
||||
return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, filter)
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,7 +787,7 @@ func listCommand(t *Term, args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args)
|
||||
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -802,12 +798,12 @@ func listCommand(t *Term, args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmds *Commands) sourceCommand(t *Term, args string) error {
|
||||
func (c *Commands) sourceCommand(t *Term, args string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("wrong number of arguments: source <filename>")
|
||||
}
|
||||
|
||||
return cmds.executeFile(t, args)
|
||||
return c.executeFile(t, args)
|
||||
}
|
||||
|
||||
func digits(n int) int {
|
||||
@ -960,6 +956,8 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExitRequestError is returned when the user
|
||||
// exits Delve.
|
||||
type ExitRequestError struct{}
|
||||
|
||||
func (ere ExitRequestError) Error() string {
|
||||
@ -970,12 +968,14 @@ func exitCommand(t *Term, args string) error {
|
||||
return ExitRequestError{}
|
||||
}
|
||||
|
||||
// ShortenFilePath take a full file path and attempts to shorten
|
||||
// it by replacing the current directory to './'.
|
||||
func ShortenFilePath(fullPath string) string {
|
||||
workingDir, _ := os.Getwd()
|
||||
return strings.Replace(fullPath, workingDir, ".", 1)
|
||||
}
|
||||
|
||||
func (cmds *Commands) executeFile(t *Term, name string) error {
|
||||
func (c *Commands) executeFile(t *Term, name string) error {
|
||||
fh, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -993,7 +993,7 @@ func (cmds *Commands) executeFile(t *Term, name string) error {
|
||||
}
|
||||
|
||||
cmdstr, args := parseCommand(line)
|
||||
cmd := cmds.Find(cmdstr)
|
||||
cmd := c.Find(cmdstr)
|
||||
err := cmd(t, args)
|
||||
|
||||
if err != nil {
|
||||
|
@ -16,10 +16,11 @@ import (
|
||||
|
||||
const (
|
||||
historyFile string = ".dbg_history"
|
||||
TerminalBlueEscapeCode string = "\033[34m"
|
||||
TerminalResetEscapeCode string = "\033[0m"
|
||||
terminalBlueEscapeCode string = "\033[34m"
|
||||
terminalResetEscapeCode string = "\033[0m"
|
||||
)
|
||||
|
||||
// Term represents the terminal running dlv.
|
||||
type Term struct {
|
||||
client service.Client
|
||||
prompt string
|
||||
@ -29,6 +30,7 @@ type Term struct {
|
||||
InitFile string
|
||||
}
|
||||
|
||||
// New returns a new Term.
|
||||
func New(client service.Client, conf *config.Config) *Term {
|
||||
return &Term{
|
||||
prompt: "(dlv) ",
|
||||
@ -39,7 +41,8 @@ func New(client service.Client, conf *config.Config) *Term {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Term) Run() (error, int) {
|
||||
// Run begins running dlv in the terminal.
|
||||
func (t *Term) Run() (int, error) {
|
||||
defer t.line.Close()
|
||||
|
||||
// Send the debugger a halt command on SIGINT
|
||||
@ -49,7 +52,7 @@ func (t *Term) Run() (error, int) {
|
||||
for range ch {
|
||||
_, err := t.client.Halt()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@ -122,12 +125,13 @@ func (t *Term) Run() (error, int) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, status
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// Println prints a line to the terminal.
|
||||
func (t *Term) Println(prefix, str string) {
|
||||
if !t.dumb {
|
||||
prefix = fmt.Sprintf("%s%s%s", TerminalBlueEscapeCode, prefix, TerminalResetEscapeCode)
|
||||
prefix = fmt.Sprintf("%s%s%s", terminalBlueEscapeCode, prefix, terminalResetEscapeCode)
|
||||
}
|
||||
fmt.Printf("%s%s\n", prefix, str)
|
||||
}
|
||||
@ -146,7 +150,7 @@ func (t *Term) promptForInput() (string, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (t *Term) handleExit() (error, int) {
|
||||
func (t *Term) handleExit() (int, error) {
|
||||
fullHistoryFile, err := config.GetConfigFilePath(historyFile)
|
||||
if err != nil {
|
||||
fmt.Println("Error saving history file:", err)
|
||||
@ -162,24 +166,24 @@ func (t *Term) handleExit() (error, int) {
|
||||
|
||||
s, err := t.client.GetState()
|
||||
if err != nil {
|
||||
return err, 1
|
||||
return 1, err
|
||||
}
|
||||
if !s.Exited {
|
||||
kill := true
|
||||
if t.client.AttachedToExistingProcess() {
|
||||
answer, err := t.line.Prompt("Would you like to kill the process? [Y/n] ")
|
||||
if err != nil {
|
||||
return io.EOF, 2
|
||||
return 2, io.EOF
|
||||
}
|
||||
answer = strings.ToLower(strings.TrimSpace(answer))
|
||||
kill = (answer != "n" && answer != "no")
|
||||
}
|
||||
err = t.client.Detach(kill)
|
||||
if err != nil {
|
||||
return err, 1
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func parseCommand(cmdstr string) (string, string) {
|
||||
|
Loading…
Reference in New Issue
Block a user