misc: cleanup and documentation

This commit is contained in:
Derek Parker 2016-01-10 00:57:52 -08:00
parent 6d50aba71d
commit 0188dc2c8b
25 changed files with 486 additions and 346 deletions

@ -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)

@ -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
}

@ -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
}

@ -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, &regs) })
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, &regs) })
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) {