diff --git a/cmd/dlv/main.go b/cmd/dlv/main.go index e64c166a..961de090 100644 --- a/cmd/dlv/main.go +++ b/cmd/dlv/main.go @@ -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) diff --git a/proc/arch.go b/proc/arch.go index fd716745..2cc4f087 100644 --- a/proc/arch.go +++ b/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 -} diff --git a/proc/breakpoints.go b/proc/breakpoints.go index f5650a69..70f49b9a 100644 --- a/proc/breakpoints.go +++ b/proc/breakpoints.go @@ -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 } diff --git a/proc/eval.go b/proc/eval.go index 5982e7a5..8a0cab32 100644 --- a/proc/eval.go +++ b/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 & @@ -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 } } diff --git a/proc/go_version.go b/proc/go_version.go index 895f5ac1..44232110 100644 --- a/proc/go_version.go +++ b/proc/go_version.go @@ -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 } diff --git a/proc/proc.go b/proc/proc.go index 5072d24d..8634aae5 100644 --- a/proc/proc.go +++ b/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 { diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index c361618e..f3cff4c1 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -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 } diff --git a/proc/proc_linux.go b/proc/proc_linux.go index 10916b09..b3e8a9b1 100644 --- a/proc/proc_linux.go +++ b/proc/proc_linux.go @@ -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 } diff --git a/proc/proc_test.go b/proc/proc_test.go index 986b32f5..d2099e72 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -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) diff --git a/proc/ptrace_darwin.go b/proc/ptrace_darwin.go index a6c3147d..6520ddb5 100644 --- a/proc/ptrace_darwin.go +++ b/proc/ptrace_darwin.go @@ -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) } diff --git a/proc/ptrace_linux.go b/proc/ptrace_linux.go index 4cd7b1b3..c4bda0ec 100644 --- a/proc/ptrace_linux.go +++ b/proc/ptrace_linux.go @@ -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) diff --git a/proc/registers.go b/proc/registers.go index add88988..d279493a 100644 --- a/proc/registers.go +++ b/proc/registers.go @@ -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 } diff --git a/proc/registers_darwin_amd64.go b/proc/registers_darwin_amd64.go index 7af84a88..91f99959 100644 --- a/proc/registers_darwin_amd64.go +++ b/proc/registers_darwin_amd64.go @@ -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") } diff --git a/proc/registers_linux_amd64.go b/proc/registers_linux_amd64.go index e36d59ec..5d09f55b 100644 --- a/proc/registers_linux_amd64.go +++ b/proc/registers_linux_amd64.go @@ -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 } diff --git a/proc/stack.go b/proc/stack.go index 65887182..0acf3ec8 100644 --- a/proc/stack.go +++ b/proc/stack.go @@ -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 } diff --git a/proc/threads.go b/proc/threads.go index b76db67d..1e0b7232 100644 --- a/proc/threads.go +++ b/proc/threads.go @@ -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 } diff --git a/proc/threads_darwin.go b/proc/threads_darwin.go index 4be0c108..6ab5d8ce 100644 --- a/proc/threads_darwin.go +++ b/proc/threads_darwin.go @@ -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") } diff --git a/proc/threads_linux.go b/proc/threads_linux.go index 4bca4485..f881a7f4 100644 --- a/proc/threads_linux.go +++ b/proc/threads_linux.go @@ -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 } diff --git a/proc/variables.go b/proc/variables.go index c386f454..19e8ba17 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -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 diff --git a/service/api/conversions.go b/service/api/conversions.go index fc9a8adc..8b67e0fa 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -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, diff --git a/service/client.go b/service/client.go index 28c937f1..3d97e4f3 100644 --- a/service/client.go +++ b/service/client.go @@ -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 diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index f576be7a..6978cb54 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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 { diff --git a/service/server.go b/service/server.go index 21f907bb..20a364bc 100644 --- a/service/server.go +++ b/service/server.go @@ -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 diff --git a/terminal/command.go b/terminal/command.go index 65e14922..9620ef4f 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -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 ") } - 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 { diff --git a/terminal/terminal.go b/terminal/terminal.go index 3f885ec7..f9831cc4 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -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) {