diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 9fa579a1..da090ba2 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -533,7 +533,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile } if err := server.Run(); err != nil { - if err == api.NotExecutableErr { + if err == api.ErrNotExecutable { switch kind { case executingGeneratedFile: fmt.Fprintln(os.Stderr, "Can not debug non-main package") diff --git a/pkg/dwarf/frame/entries.go b/pkg/dwarf/frame/entries.go index acd791cd..1c26891a 100644 --- a/pkg/dwarf/frame/entries.go +++ b/pkg/dwarf/frame/entries.go @@ -56,11 +56,11 @@ func NewFrameIndex() FrameDescriptionEntries { return make(FrameDescriptionEntries, 0, 1000) } -type NoFDEForPCError struct { +type ErrNoFDEForPC struct { PC uint64 } -func (err *NoFDEForPCError) Error() string { +func (err *ErrNoFDEForPC) Error() string { return fmt.Sprintf("could not find FDE for PC %#v", err.PC) } @@ -76,7 +76,7 @@ func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, return true }) if idx == len(fdes) { - return nil, &NoFDEForPCError{pc} + return nil, &ErrNoFDEForPC{pc} } return fdes[idx], nil } diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index 73cfef60..e061a0bc 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -80,7 +80,7 @@ func (a *AMD64) BreakpointSize() int { return a.breakInstructionLen } -// If DerefTLS returns true the value of regs.TLS()+GStructOffset() is a +// DerefTLS returns true if the value of regs.TLS()+GStructOffset() is a // pointer to the G struct func (a *AMD64) DerefTLS() bool { return a.goos == "windows" diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 91c8677a..15ed8d63 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -25,6 +25,7 @@ import ( "github.com/derekparker/delve/pkg/goversion" ) +// BinaryInfo holds information on the binary being executed. type BinaryInfo struct { lastModified time.Time // Time the executable of this process was last modified @@ -72,9 +73,14 @@ type BinaryInfo struct { dwarfReader *dwarf.Reader } -var UnsupportedLinuxArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") -var UnsupportedWindowsArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") -var UnsupportedDarwinArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported") +// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture. +var ErrUnsupportedLinuxArch = errors.New("unsupported architecture - only linux/amd64 is supported") + +// ErrUnsupportedWindowsArch is returned when attempting to debug a binary compiled for an unsupported architecture. +var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") + +// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture. +var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported") const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231) @@ -260,16 +266,17 @@ func (e *loclistEntry) BaseAddressSelection() bool { return e.lowpc == ^uint64(0) } -type buildIdHeader struct { +type buildIDHeader struct { Namesz uint32 Descsz uint32 Type uint32 } -func NewBinaryInfo(goos, goarch string) BinaryInfo { - r := BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)} +// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct. +func NewBinaryInfo(goos, goarch string) *BinaryInfo { + r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)} - // TODO: find better way to determine proc arch (perhaps use executable file info) + // TODO: find better way to determine proc arch (perhaps use executable file info). switch goarch { case "amd64": r.Arch = AMD64Arch(goos) @@ -278,19 +285,22 @@ func NewBinaryInfo(goos, goarch string) BinaryInfo { return r } -func (bininfo *BinaryInfo) LoadBinaryInfo(path string, wg *sync.WaitGroup) error { +// LoadBinaryInfo will load and store the information from the binary at 'path'. +// It is expected this will be called in parallel with other initialization steps +// so a sync.WaitGroup must be provided. +func (bi *BinaryInfo) LoadBinaryInfo(path string, wg *sync.WaitGroup) error { fi, err := os.Stat(path) if err == nil { - bininfo.lastModified = fi.ModTime() + bi.lastModified = fi.ModTime() } - switch bininfo.GOOS { + switch bi.GOOS { case "linux": - return bininfo.LoadBinaryInfoElf(path, wg) + return bi.LoadBinaryInfoElf(path, wg) case "windows": - return bininfo.LoadBinaryInfoPE(path, wg) + return bi.LoadBinaryInfoPE(path, wg) case "darwin": - return bininfo.LoadBinaryInfoMacho(path, wg) + return bi.LoadBinaryInfoMacho(path, wg) } return errors.New("unsupported operating system") } @@ -301,6 +311,7 @@ func (bi *BinaryInfo) GStructOffset() uint64 { return bi.gStructOffset } +// LastModified returns the last modified time of the binary. func (bi *BinaryInfo) LastModified() time.Time { return bi.lastModified } @@ -381,6 +392,7 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function { return nil } +// Close closes all internal readers. func (bi *BinaryInfo) Close() error { if bi.sepDebugCloser != nil { bi.sepDebugCloser.Close() @@ -394,6 +406,7 @@ func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) { bi.loadErrMu.Unlock() } +// LoadError returns any internal load error. func (bi *BinaryInfo) LoadError() error { return bi.loadErr } @@ -506,6 +519,7 @@ func (bi *BinaryInfo) findCompileUnitForOffset(off dwarf.Offset) *compileUnit { return nil } +// Producer returns the value of DW_AT_producer. func (bi *BinaryInfo) Producer() string { for _, cu := range bi.compileUnits { if cu.isgo && cu.producer != "" { @@ -522,12 +536,12 @@ func (bi *BinaryInfo) Type(offset dwarf.Offset) (godwarf.Type, error) { // ELF /////////////////////////////////////////////////////////////// -// This error is used in openSeparateDebugInfo to signal there's no +// ErrNoBuildIDNote is used in openSeparateDebugInfo to signal there's no // build-id note on the binary, so LoadBinaryInfoElf will return // the error message coming from elfFile.DWARF() instead. -type NoBuildIdNoteError struct{} +type ErrNoBuildIDNote struct{} -func (e *NoBuildIdNoteError) Error() string { +func (e *ErrNoBuildIDNote) Error() string { return "can't find build-id note on binary" } @@ -539,11 +553,11 @@ func (e *NoBuildIdNoteError) Error() string { func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, error) { buildid := exe.Section(".note.gnu.build-id") if buildid == nil { - return nil, nil, &NoBuildIdNoteError{} + return nil, nil, &ErrNoBuildIDNote{} } br := buildid.Open() - bh := new(buildIdHeader) + bh := new(buildIDHeader) if err := binary.Read(br, binary.LittleEndian, bh); err != nil { return nil, nil, errors.New("can't read build-id header: " + err.Error()) } @@ -572,17 +586,18 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, elfFile, err := elf.NewFile(sepFile) if err != nil { sepFile.Close() - return nil, nil, errors.New(fmt.Sprintf("can't open separate debug file %q: %v", debugPath, err.Error())) + return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, err.Error()) } if elfFile.Machine != elf.EM_X86_64 { sepFile.Close() - return nil, nil, errors.New(fmt.Sprintf("can't open separate debug file %q: %v", debugPath, UnsupportedLinuxArchErr.Error())) + return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, ErrUnsupportedLinuxArch.Error()) } return sepFile, elfFile, nil } +// LoadBinaryInfoElf specifically loads information from an ELF binary. func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { exe, err := os.OpenFile(path, 0, os.ModePerm) if err != nil { @@ -594,7 +609,7 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { return err } if elfFile.Machine != elf.EM_X86_64 { - return UnsupportedLinuxArchErr + return ErrUnsupportedLinuxArch } dwarfFile := elfFile bi.dwarf, err = elfFile.DWARF() @@ -603,7 +618,7 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { var serr error sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile) if serr != nil { - if _, ok := serr.(*NoBuildIdNoteError); ok { + if _, ok := serr.(*ErrNoBuildIDNote); ok { return err } return serr @@ -688,6 +703,7 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { // PE //////////////////////////////////////////////////////////////// +// LoadBinaryInfoPE specifically loads information from a PE binary. func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error { peFile, closer, err := openExecutablePathPE(path) if err != nil { @@ -695,7 +711,7 @@ func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error { } bi.closer = closer if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { - return UnsupportedWindowsArchErr + return ErrUnsupportedWindowsArch } bi.dwarf, err = peFile.DWARF() if err != nil { @@ -772,6 +788,7 @@ func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { // MACH-O //////////////////////////////////////////////////////////// +// LoadBinaryInfoMacho specifically loads information from a Mach-O binary. func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, wg *sync.WaitGroup) error { exe, err := macho.Open(path) if err != nil { @@ -779,7 +796,7 @@ func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, wg *sync.WaitGroup) error } bi.closer = exe if exe.Cpu != macho.CpuAmd64 { - return UnsupportedDarwinArchErr + return ErrUnsupportedDarwinArch } bi.dwarf, err = exe.DWARF() if err != nil { diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index e3463b15..c6fe107a 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -59,7 +59,7 @@ type Breakpoint struct { returnInfo *returnBreakpointInfo } -// Breakpoint Kind determines the behavior of delve when the +// BreakpointKind determines the behavior of delve when the // breakpoint is reached. type BreakpointKind uint16 @@ -350,6 +350,7 @@ type BreakpointState struct { CondError error } +// Clear zeros the struct. func (bpstate *BreakpointState) Clear() { bpstate.Breakpoint = nil bpstate.Active = false diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 488785e1..f2d7d549 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -137,12 +137,14 @@ type OffsetReaderAt struct { offset uintptr } +// ReadMemory will read the memory at addr-offset. func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) { return r.reader.ReadAt(buf, int64(addr-r.offset)) } +// Process represents a core file. type Process struct { - bi proc.BinaryInfo + bi *proc.BinaryInfo core *Core breakpoints proc.BreakpointMap currentThread *Thread @@ -150,6 +152,7 @@ type Process struct { common proc.CommonProcess } +// Thread represents a thread in the core file being debugged. type Thread struct { th *LinuxPrStatus fpregs []proc.Register @@ -157,10 +160,17 @@ type Thread struct { common proc.CommonThread } -var ErrWriteCore = errors.New("can not to core process") +// ErrWriteCore is returned when attempting to write to the core +// process memory. +var ErrWriteCore = errors.New("can not write to core process") + +// ErrShortRead is returned on a short read. var ErrShortRead = errors.New("short read") + +// ErrContinueCore is returned when trying to continue execution of a core process. var ErrContinueCore = errors.New("can not continue execution of core process") +// OpenCore will open the core file and return a Process struct. func OpenCore(corePath, exePath string) (*Process, error) { core, err := readCore(corePath, exePath) if err != nil { @@ -194,43 +204,69 @@ func OpenCore(corePath, exePath string) (*Process, error) { return p, nil } +// BinInfo will return the binary info. func (p *Process) BinInfo() *proc.BinaryInfo { - return &p.bi + return p.bi } -func (p *Process) Recorded() (bool, string) { return true, "" } -func (p *Process) Restart(string) error { return ErrContinueCore } -func (p *Process) Direction(proc.Direction) error { return ErrContinueCore } -func (p *Process) When() (string, error) { return "", nil } -func (p *Process) Checkpoint(string) (int, error) { return -1, ErrContinueCore } -func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, nil } -func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not found") } +// Recorded returns whether this is a live or recorded process. Always returns true for core files. +func (p *Process) Recorded() (bool, string) { return true, "" } -func (thread *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { - n, err = thread.p.core.ReadMemory(data, addr) +// Restart will only return an error for core files, as they are not executing. +func (p *Process) Restart(string) error { return ErrContinueCore } + +// Direction will only return an error as you cannot continue a core process. +func (p *Process) Direction(proc.Direction) error { return ErrContinueCore } + +// When does not apply to core files, it is to support the Mozilla 'rr' backend. +func (p *Process) When() (string, error) { return "", nil } + +// Checkpoint for core files returns an error, there is no execution of a core file. +func (p *Process) Checkpoint(string) (int, error) { return -1, ErrContinueCore } + +// Checkpoints returns nil on core files, you cannot set checkpoints when debugging core files. +func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, nil } + +// ClearCheckpoint clears a checkpoint, but will only return an error for core files. +func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not found") } + +// ReadMemory will return memory from the core file at the specified location and put the +// read memory into `data`, returning the length read, and returning an error if +// the length read is shorter than the length of the `data` buffer. +func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { + n, err = t.p.core.ReadMemory(data, addr) if err == nil && n != len(data) { err = ErrShortRead } return n, err } -func (thread *Thread) WriteMemory(addr uintptr, data []byte) (int, error) { +// WriteMemory will only return an error for core files, you cannot write +// to the memory of a core process. +func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) { return 0, ErrWriteCore } +// Location returns the location of this thread based on +// the value of the instruction pointer register. func (t *Thread) Location() (*proc.Location, error) { f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip) return &proc.Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil } +// Breakpoint returns the current breakpoint this thread is stopped at. +// For core files this always returns an empty BreakpointState struct, as +// there are no breakpoints when debugging core files. func (t *Thread) Breakpoint() proc.BreakpointState { return proc.BreakpointState{} } +// ThreadID returns the ID for this thread. func (t *Thread) ThreadID() int { return int(t.th.Pid) } +// Registers returns the current value of the registers for this thread. func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { r := &Registers{&t.th.Reg, nil} if floatingPoint { @@ -239,105 +275,152 @@ func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { return r, nil } +// RestoreRegisters will only return an error for core files, +// you cannot change register values for core files. func (t *Thread) RestoreRegisters(proc.Registers) error { return errors.New("not supported") } +// Arch returns the architecture the target is built for and executing on. func (t *Thread) Arch() proc.Arch { return t.p.bi.Arch } +// BinInfo returns information about the binary. func (t *Thread) BinInfo() *proc.BinaryInfo { - return &t.p.bi + return t.p.bi } +// StepInstruction will only return an error for core files, +// you cannot execute a core file. func (t *Thread) StepInstruction() error { return ErrContinueCore } +// Blocked will return false always for core files as there is +// no execution. func (t *Thread) Blocked() bool { return false } +// SetCurrentBreakpoint will always just return nil +// for core files, as there are no breakpoints in core files. func (t *Thread) SetCurrentBreakpoint() error { return nil } +// Common returns a struct containing common information +// across thread implementations. func (t *Thread) Common() *proc.CommonThread { return &t.common } +// SetPC will always return an error, you cannot +// change register values when debugging core files. func (t *Thread) SetPC(uint64) error { return errors.New("not supported") } +// SetSP will always return an error, you cannot +// change register values when debugging core files. func (t *Thread) SetSP(uint64) error { return errors.New("not supported") } +// SetDX will always return an error, you cannot +// change register values when debugging core files. func (t *Thread) SetDX(uint64) error { return errors.New("not supported") } +// Breakpoints will return all breakpoints for the process. func (p *Process) Breakpoints() *proc.BreakpointMap { return &p.breakpoints } +// ClearBreakpoint will always return an error as you cannot set or clear +// breakpoints on core files. func (p *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) { return nil, proc.NoBreakpointError{Addr: addr} } +// ClearInternalBreakpoints will always return nil and have no +// effect since you cannot set breakpoints on core files. func (p *Process) ClearInternalBreakpoints() error { return nil } +// ContinueOnce will always return an error because you +// cannot control execution of a core file. func (p *Process) ContinueOnce() (proc.Thread, error) { return nil, ErrContinueCore } +// StepInstruction will always return an error +// as you cannot control execution of a core file. func (p *Process) StepInstruction() error { return ErrContinueCore } +// RequestManualStop will return nil and have no effect +// as you cannot control execution of a core file. func (p *Process) RequestManualStop() error { return nil } +// CheckAndClearManualStopRequest will always return false and +// have no effect since there are no manual stop requests as +// there is no controlling execution of a core file. func (p *Process) CheckAndClearManualStopRequest() bool { return false } +// CurrentThread returns the current active thread. func (p *Process) CurrentThread() proc.Thread { return p.currentThread } +// Detach will always return nil and have no +// effect as you cannot detach from a core file +// and have it continue execution or exit. func (p *Process) Detach(bool) error { return nil } +// Valid returns whether the process is active. Always returns true +// for core files as it cannot exit or be otherwise detached from. func (p *Process) Valid() (bool, error) { return true, nil } +// Common returns common information across Process +// implementations. func (p *Process) Common() *proc.CommonProcess { return &p.common } +// Pid returns the process ID of this process. func (p *Process) Pid() int { return p.core.Pid } +// ResumeNotify is a no-op on core files as we cannot +// control execution. func (p *Process) ResumeNotify(chan<- struct{}) { } +// SelectedGoroutine returns the current active and selected +// goroutine. func (p *Process) SelectedGoroutine() *proc.G { return p.selectedGoroutine } +// SetBreakpoint will always return an error for core files as you cannot write memory or control execution. func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) { return nil, ErrWriteCore } +// SwitchGoroutine will change the selected and active goroutine. func (p *Process) SwitchGoroutine(gid int) error { g, err := proc.FindGoroutine(p, gid) if err != nil { @@ -354,6 +437,7 @@ func (p *Process) SwitchGoroutine(gid int) error { return nil } +// SwitchThread will change the selected and active thread. func (p *Process) SwitchThread(tid int) error { if th, ok := p.core.Threads[tid]; ok { p.currentThread = th @@ -363,6 +447,7 @@ func (p *Process) SwitchThread(tid int) error { return fmt.Errorf("thread %d does not exist", tid) } +// ThreadList will return a list of all threads currently in the process. func (p *Process) ThreadList() []proc.Thread { r := make([]proc.Thread, 0, len(p.core.Threads)) for _, v := range p.core.Threads { @@ -371,16 +456,19 @@ func (p *Process) ThreadList() []proc.Thread { return r } +// FindThread will return the thread with the corresponding thread ID. func (p *Process) FindThread(threadID int) (proc.Thread, bool) { t, ok := p.core.Threads[threadID] return t, ok } +// Registers represents the CPU registers. type Registers struct { *LinuxCoreRegisters fpregs []proc.Register } +// Slice will return a slice containing all registers and their values. func (r *Registers) Slice() []proc.Register { var regs = []struct { k string @@ -426,6 +514,8 @@ func (r *Registers) Slice() []proc.Register { return out } +// Copy will return a copy of the registers that is guarenteed +// not to change. func (r *Registers) Copy() proc.Registers { return r } diff --git a/pkg/proc/core/linux_amd64_core.go b/pkg/proc/core/linux_amd64_core.go index bfe8c69b..5fcd1387 100644 --- a/pkg/proc/core/linux_amd64_core.go +++ b/pkg/proc/core/linux_amd64_core.go @@ -52,33 +52,47 @@ type LinuxCoreTimeval struct { Usec int64 } -const NT_FILE elf.NType = 0x46494c45 // "FILE". +// NT_FILE is file mapping information, e.g. program text mappings. Desc is a LinuxNTFile. +const NT_FILE elf.NType = 0x46494c45 // "FILE". + +// NT_X86_XSTATE is other registers, including AVX and such. const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area. +// PC returns the value of RIP. func (r *LinuxCoreRegisters) PC() uint64 { return r.Rip } +// SP returns the value of RSP. func (r *LinuxCoreRegisters) SP() uint64 { return r.Rsp } +// BP returns the value of RBP. func (r *LinuxCoreRegisters) BP() uint64 { return r.Rbp } +// CX returns the value of RCX. func (r *LinuxCoreRegisters) CX() uint64 { return r.Rcx } +// TLS returns the location of the thread local storate, +// which will be the value of Fs_base. func (r *LinuxCoreRegisters) TLS() uint64 { return r.Fs_base } +// GAddr returns the address of the G struct. Always returns 0 +// and false for core files. func (r *LinuxCoreRegisters) GAddr() (uint64, bool) { return 0, false } +// Get returns the value of the register requested via the +// register number, returning an error if that register +// could not be found. func (r *LinuxCoreRegisters) Get(n int) (uint64, error) { reg := x86asm.Reg(n) const ( @@ -233,7 +247,7 @@ func (r *LinuxCoreRegisters) Get(n int) (uint64, error) { return r.R15, nil } - return 0, proc.UnknownRegisterError + return 0, proc.ErrUnknownRegister } // readCore reads a core file from corePath corresponding to the executable at @@ -292,6 +306,7 @@ func readCore(corePath, exePath string) (*Core, error) { return core, nil } +// Core represents a core file. type Core struct { proc.MemoryReader Threads map[int]*Thread @@ -456,7 +471,7 @@ func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*Note) proc.Me return memory } -// Various structures from the ELF spec and the Linux kernel. +// LinuxPrPsInfo has various structures from the ELF spec and the Linux kernel. // AMD64 specific primarily because of unix.PtraceRegs, but also // because some of the fields are word sized. // See http://lxr.free-electrons.com/source/include/uapi/linux/elfcore.h @@ -473,6 +488,7 @@ type LinuxPrPsInfo struct { Args [80]uint8 } +// LinuxPrStatus is a copy of the prstatus kernel struct. type LinuxPrStatus struct { Siginfo LinuxSiginfo Cursig uint16 @@ -485,29 +501,35 @@ type LinuxPrStatus struct { Fpvalid int32 } +// LinuxSiginfo is a copy of the +// siginfo kernel struct. type LinuxSiginfo struct { Signo int32 Code int32 Errno int32 } +// LinuxNTFile contains information on mapped files. type LinuxNTFile struct { LinuxNTFileHdr entries []*LinuxNTFileEntry } +// LinuxNTFileHdr is a header struct for NTFile. type LinuxNTFileHdr struct { Count uint64 PageSize uint64 } +// LinuxNTFileEntry is an entry of an NT_FILE note. type LinuxNTFileEntry struct { Start uint64 End uint64 FileOfs uint64 } -// ELF Notes header. Same size on 64 and 32-bit machines. +// ELFNotesHdr is the ELF Notes header. +// Same size on 64 and 32-bit machines. type ELFNotesHdr struct { Namesz uint32 Descsz uint32 diff --git a/pkg/proc/disasm.go b/pkg/proc/disasm.go index 5372a678..8f013a81 100644 --- a/pkg/proc/disasm.go +++ b/pkg/proc/disasm.go @@ -2,20 +2,25 @@ package proc import "sort" +// AsmInstruction represents one assembly instruction. type AsmInstruction struct { Loc Location DestLoc *Location Bytes []byte Breakpoint bool AtPC bool - Inst *ArchInst + Inst *archInst } +// AssemblyFlavour is the assembly syntax to display. type AssemblyFlavour int const ( + // GNUFlavour will display GNU assembly syntax. GNUFlavour = AssemblyFlavour(iota) + // IntelFlavour will display Intel assembly syntax. IntelFlavour + // GoFlavour will display Go assembly syntax. GoFlavour ) diff --git a/pkg/proc/disasm_amd64.go b/pkg/proc/disasm_amd64.go index 2b3bd10c..1f33c6e9 100644 --- a/pkg/proc/disasm_amd64.go +++ b/pkg/proc/disasm_amd64.go @@ -8,19 +8,19 @@ import ( var maxInstructionLength uint64 = 15 -type ArchInst x86asm.Inst +type archInst x86asm.Inst -func asmDecode(mem []byte, pc uint64) (*ArchInst, error) { +func asmDecode(mem []byte, pc uint64) (*archInst, error) { inst, err := x86asm.Decode(mem, 64) if err != nil { return nil, err } patchPCRel(pc, &inst) - r := ArchInst(inst) + r := archInst(inst) return &r, nil } -func (inst *ArchInst) Size() int { +func (inst *archInst) Size() int { return inst.Len } @@ -34,6 +34,8 @@ func patchPCRel(pc uint64, inst *x86asm.Inst) { } } +// Text will return the assembly instructions in human readable format according to +// the flavour specified. func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string { if inst.Inst == nil { return "?" @@ -55,6 +57,7 @@ func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string return text } +// IsCall returns true if the instruction is a CALL or LCALL instruction. func (inst *AsmInstruction) IsCall() bool { if inst.Inst == nil { return false @@ -62,7 +65,7 @@ func (inst *AsmInstruction) IsCall() bool { return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL } -func resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location { +func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location { if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL { return nil } diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index c9a9f188..3fccd604 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -30,7 +30,7 @@ func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) *proc.BinaryInfo { bi := proc.NewBinaryInfo("linux", "amd64") bi.LoadFromData(dwdata, frame, line, loc) - return &bi + return bi } // fakeMemory implements proc.MemoryReadWriter by reading from a byte slice. diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index f734f4ee..bf218951 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -19,7 +19,7 @@ import ( "github.com/derekparker/delve/pkg/goversion" ) -var OperationOnSpecialFloatError = errors.New("operations on non-finite floats not implemented") +var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented") // EvalExpression returns the value of the given expression. func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) { @@ -853,7 +853,7 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { return nil, xv.Unreadable } if xv.FloatSpecial != 0 { - return nil, OperationOnSpecialFloatError + return nil, errOperationOnSpecialFloat } if xv.Value == nil { return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) @@ -971,7 +971,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { } if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 { - return nil, OperationOnSpecialFloatError + return nil, errOperationOnSpecialFloat } typ, err := negotiateType(node.Op, xv, yv) diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 85a4fffe..fba6daf2 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -39,17 +39,17 @@ const ( ) var ( - ErrFuncCallUnsupported = errors.New("function calls not supported by this version of Go") - ErrFuncCallUnsupportedBackend = errors.New("backend does not support function calls") - ErrFuncCallInProgress = errors.New("cannot call function while another function call is already in progress") - ErrNotACallExpr = errors.New("not a function call") - ErrNoGoroutine = errors.New("no goroutine selected") - ErrGoroutineNotRunning = errors.New("selected goroutine not running") - ErrNotEnoughStack = errors.New("not enough stack space") - ErrTooManyArguments = errors.New("too many arguments") - ErrNotEnoughArguments = errors.New("not enough arguments") - ErrNoAddrUnsupported = errors.New("arguments to a function call must have an address") - ErrNotAGoFunction = errors.New("not a Go function") + errFuncCallUnsupported = errors.New("function calls not supported by this version of Go") + errFuncCallUnsupportedBackend = errors.New("backend does not support function calls") + errFuncCallInProgress = errors.New("cannot call function while another function call is already in progress") + errNotACallExpr = errors.New("not a function call") + errNoGoroutine = errors.New("no goroutine selected") + errGoroutineNotRunning = errors.New("selected goroutine not running") + errNotEnoughStack = errors.New("not enough stack space") + errTooManyArguments = errors.New("too many arguments") + errNotEnoughArguments = errors.New("not enough arguments") + errNoAddrUnsupported = errors.New("arguments to a function call must have an address") + errNotAGoFunction = errors.New("not a Go function") ) type functionCallState struct { @@ -84,27 +84,27 @@ type functionCallState struct { func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error { bi := p.BinInfo() if !p.Common().fncallEnabled { - return ErrFuncCallUnsupportedBackend + return errFuncCallUnsupportedBackend } fncall := &p.Common().fncallState if fncall.inProgress { - return ErrFuncCallInProgress + return errFuncCallInProgress } *fncall = functionCallState{} dbgcallfn := bi.LookupFunc[debugCallFunctionName] if dbgcallfn == nil { - return ErrFuncCallUnsupported + return errFuncCallUnsupported } // check that the selected goroutine is running g := p.SelectedGoroutine() if g == nil { - return ErrNoGoroutine + return errNoGoroutine } if g.Status != Grunning || g.Thread == nil { - return ErrGoroutineNotRunning + return errGoroutineNotRunning } // check that there are at least 256 bytes free on the stack @@ -114,11 +114,11 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error { } regs = regs.Copy() if regs.SP()-256 <= g.stacklo { - return ErrNotEnoughStack + return errNotEnoughStack } _, err = regs.Get(int(x86asm.RAX)) if err != nil { - return ErrFuncCallUnsupportedBackend + return errFuncCallUnsupportedBackend } fn, closureAddr, argvars, err := funcCallEvalExpr(p, expr) @@ -208,7 +208,7 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, closureAddr uint64, } callexpr, iscall := t.(*ast.CallExpr) if !iscall { - return nil, 0, nil, ErrNotACallExpr + return nil, 0, nil, errNotACallExpr } fnvar, err := scope.evalAST(callexpr.Fun) @@ -230,7 +230,7 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, closureAddr uint64, return nil, 0, nil, fmt.Errorf("could not find DIE for function %q", exprToString(callexpr.Fun)) } if !fn.cu.isgo { - return nil, 0, nil, ErrNotAGoFunction + return nil, 0, nil, errNotAGoFunction } argvars = make([]*Variable, 0, len(callexpr.Args)+1) @@ -265,10 +265,10 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo return nil, err } if len(actualArgs) > len(formalArgs) { - return nil, ErrTooManyArguments + return nil, errTooManyArguments } if len(actualArgs) < len(formalArgs) { - return nil, ErrNotEnoughArguments + return nil, errNotEnoughArguments } // constructs arguments frame diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 2d659fc7..3ada3474 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -96,12 +96,14 @@ const ( const heartbeatInterval = 10 * time.Second +// ErrDirChange is returned when trying to change execution direction +// while there are still internal breakpoints set. var ErrDirChange = errors.New("direction change with internal breakpoints") // Process implements proc.Process using a connection to a debugger stub // that understands Gdb Remote Serial Protocol. type Process struct { - bi proc.BinaryInfo + bi *proc.BinaryInfo conn gdbConn threads map[int]*Thread @@ -374,7 +376,8 @@ func unusedPort() string { const debugserverExecutable = "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/debugserver" -var ErrUnsupportedOS = errors.New("lldb backend not supported on windows") +// ErrUnsupportedOS is returned when trying to use the lldb backend on Windows. +var ErrUnsupportedOS = errors.New("lldb backend not supported on Windows") func getLdEnvVars() []string { var result []string @@ -400,7 +403,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) { default: // check that the argument to Launch is an executable file if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { - return nil, proc.NotExecutableErr + return nil, proc.ErrNotExecutable } } @@ -548,37 +551,47 @@ func (p *Process) loadProcessInfo(pid int) (int, string, error) { return pid, pi["name"], nil } +// BinInfo returns information on the binary. func (p *Process) BinInfo() *proc.BinaryInfo { - return &p.bi + return p.bi } +// Recorded returns whether or not we are debugging +// a recorded "traced" program. func (p *Process) Recorded() (bool, string) { return p.tracedir != "", p.tracedir } +// Pid returns the process ID. func (p *Process) Pid() int { return int(p.conn.pid) } +// Valid returns true if we are not detached +// and the process has not exited. func (p *Process) Valid() (bool, error) { if p.detached { return false, &proc.ProcessDetachedError{} } if p.exited { - return false, &proc.ProcessExitedError{Pid: p.Pid()} + return false, &proc.ErrProcessExited{Pid: p.Pid()} } return true, nil } +// ResumeNotify specifies a channel that will be closed the next time +// ContinueOnce finishes resuming the target. func (p *Process) ResumeNotify(ch chan<- struct{}) { p.conn.resumeChan = ch } +// FindThread returns the thread with the given ID. func (p *Process) FindThread(threadID int) (proc.Thread, bool) { thread, ok := p.threads[threadID] return thread, ok } +// ThreadList returns all threads in the process. func (p *Process) ThreadList() []proc.Thread { r := make([]proc.Thread, 0, len(p.threads)) for _, thread := range p.threads { @@ -587,14 +600,18 @@ func (p *Process) ThreadList() []proc.Thread { return r } +// CurrentThread returns the current active +// selected thread. func (p *Process) CurrentThread() proc.Thread { return p.currentThread } +// Common returns common information across Process implementations. func (p *Process) Common() *proc.CommonProcess { return &p.common } +// SelectedGoroutine returns the current actuve selected goroutine. func (p *Process) SelectedGoroutine() *proc.G { return p.selectedGoroutine } @@ -606,9 +623,11 @@ const ( stopSignal = 0x13 ) +// ContinueOnce will continue execution of the process until +// a breakpoint is hit or signal is received. func (p *Process) ContinueOnce() (proc.Thread, error) { if p.exited { - return nil, &proc.ProcessExitedError{Pid: p.conn.pid} + return nil, &proc.ErrProcessExited{Pid: p.conn.pid} } if p.conn.direction == proc.Forward { @@ -639,7 +658,7 @@ continueLoop: tu.Reset() threadID, sig, err = p.conn.resume(sig, &tu) if err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { p.exited = true } return nil, err @@ -706,6 +725,7 @@ continueLoop: return nil, fmt.Errorf("could not find thread %s", threadID) } +// StepInstruction will step exactly one CPU instruction. func (p *Process) StepInstruction() error { thread := p.currentThread if p.selectedGoroutine != nil { @@ -719,7 +739,7 @@ func (p *Process) StepInstruction() error { } p.common.ClearAllGCache() if p.exited { - return &proc.ProcessExitedError{Pid: p.conn.pid} + return &proc.ErrProcessExited{Pid: p.conn.pid} } thread.clearBreakpointState() err := thread.StepInstruction() @@ -736,9 +756,10 @@ func (p *Process) StepInstruction() error { return nil } +// SwitchThread will change the internal selected thread. func (p *Process) SwitchThread(tid int) error { if p.exited { - return proc.ProcessExitedError{Pid: p.conn.pid} + return proc.ErrProcessExited{Pid: p.conn.pid} } if th, ok := p.threads[tid]; ok { p.currentThread = th @@ -748,6 +769,7 @@ func (p *Process) SwitchThread(tid int) error { return fmt.Errorf("thread %d does not exist", tid) } +// SwitchGoroutine will change the internal selected goroutine. func (p *Process) SwitchGoroutine(gid int) error { g, err := proc.FindGoroutine(p, gid) if err != nil { @@ -764,6 +786,8 @@ func (p *Process) SwitchGoroutine(gid int) error { return nil } +// RequestManualStop will attempt to stop the process +// without a breakpoint or signal having been recieved. func (p *Process) RequestManualStop() error { p.conn.manualStopMutex.Lock() p.manualStopRequested = true @@ -776,6 +800,8 @@ func (p *Process) RequestManualStop() error { return p.conn.sendCtrlC() } +// CheckAndClearManualStopRequest will check for a manual +// stop and then clear that state. func (p *Process) CheckAndClearManualStopRequest() bool { p.conn.manualStopMutex.Lock() msr := p.manualStopRequested @@ -796,11 +822,13 @@ func (p *Process) getCtrlC() bool { return p.ctrlC } +// Detach will detach from the target process, +// if 'kill' is true it will also kill the process. func (p *Process) Detach(kill bool) error { if kill && !p.exited { err := p.conn.kill() if err != nil { - if _, exited := err.(proc.ProcessExitedError); !exited { + if _, exited := err.(proc.ErrProcessExited); !exited { return err } p.exited = true @@ -820,9 +848,10 @@ func (p *Process) Detach(kill bool) error { return p.bi.Close() } +// Restart will restart the process from the given position. func (p *Process) Restart(pos string) error { if p.tracedir == "" { - return proc.NotRecordedErr + return proc.ErrNotRecorded } p.exited = false @@ -859,9 +888,11 @@ func (p *Process) Restart(pos string) error { return p.setCurrentBreakpoints() } +// When executes the 'when' command for the Mozilla RR backend. +// This command will return rr's internal event number. func (p *Process) When() (string, error) { if p.tracedir == "" { - return "", proc.NotRecordedErr + return "", proc.ErrNotRecorded } event, err := p.conn.qRRCmd("when") if err != nil { @@ -874,9 +905,10 @@ const ( checkpointPrefix = "Checkpoint " ) +// Checkpoint creates a checkpoint from which you can restart the program. func (p *Process) Checkpoint(where string) (int, error) { if p.tracedir == "" { - return -1, proc.NotRecordedErr + return -1, proc.ErrNotRecorded } resp, err := p.conn.qRRCmd("checkpoint", where) if err != nil { @@ -901,9 +933,10 @@ func (p *Process) Checkpoint(where string) (int, error) { return cpid, nil } +// Checkpoints returns a list of all checkpoints set. func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { if p.tracedir == "" { - return nil, proc.NotRecordedErr + return nil, proc.ErrNotRecorded } resp, err := p.conn.qRRCmd("info checkpoints") if err != nil { @@ -930,9 +963,10 @@ func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { const deleteCheckpointPrefix = "Deleted checkpoint " +// ClearCheckpoint clears the checkpoint for the given ID. func (p *Process) ClearCheckpoint(id int) error { if p.tracedir == "" { - return proc.NotRecordedErr + return proc.ErrNotRecorded } resp, err := p.conn.qRRCmd("delete checkpoint", strconv.Itoa(id)) if err != nil { @@ -944,12 +978,13 @@ func (p *Process) ClearCheckpoint(id int) error { return nil } +// Direction sets whether to run the program forwards or in reverse execution. func (p *Process) Direction(dir proc.Direction) error { if p.tracedir == "" { - return proc.NotRecordedErr + return proc.ErrNotRecorded } if p.conn.conn == nil { - return proc.ProcessExitedError{Pid: p.conn.pid} + return proc.ErrProcessExited{Pid: p.conn.pid} } if p.conn.direction == dir { return nil @@ -961,10 +996,12 @@ func (p *Process) Direction(dir proc.Direction) error { return nil } +// Breakpoints returns the list of breakpoints currently set. func (p *Process) Breakpoints() *proc.BreakpointMap { return &p.breakpoints } +// FindBreakpoint returns the breakpoint at the given address. func (p *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) { // Check to see if address is past the breakpoint, (i.e. breakpoint was hit). if bp, ok := p.breakpoints.M[pc-uint64(p.bi.Arch.BreakpointSize())]; ok { @@ -990,22 +1027,25 @@ func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []b return f, l, fn, nil, nil } +// SetBreakpoint creates a new breakpoint. func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) { if p.exited { - return nil, &proc.ProcessExitedError{Pid: p.conn.pid} + return nil, &proc.ErrProcessExited{Pid: p.conn.pid} } return p.breakpoints.Set(addr, kind, cond, p.writeBreakpoint) } +// ClearBreakpoint clears a breakpoint at the given address. func (p *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) { if p.exited { - return nil, &proc.ProcessExitedError{Pid: p.conn.pid} + return nil, &proc.ErrProcessExited{Pid: p.conn.pid} } return p.breakpoints.Clear(addr, func(bp *proc.Breakpoint) error { return p.conn.clearBreakpoint(bp.Addr) }) } +// ClearInternalBreakpoints clear all internal use breakpoints like those set by 'next'. func (p *Process) ClearInternalBreakpoints() error { return p.breakpoints.ClearInternalBreakpoints(func(bp *proc.Breakpoint) error { if err := p.conn.clearBreakpoint(bp.Addr); err != nil { @@ -1155,6 +1195,7 @@ func (p *Process) setCurrentBreakpoints() error { return nil } +// ReadMemory will read into 'data' memory at the address provided. func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { err = t.p.conn.readMemory(data, addr) if err != nil { @@ -1163,10 +1204,12 @@ func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { return len(data), nil } +// WriteMemory will write into the memory at 'addr' the data provided. func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) { return t.p.conn.writeMemory(addr, data) } +// Location returns the current location of this thread. func (t *Thread) Location() (*proc.Location, error) { regs, err := t.Registers(false) if err != nil { @@ -1177,31 +1220,38 @@ func (t *Thread) Location() (*proc.Location, error) { return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil } +// Breakpoint returns the current active breakpoint for this thread. func (t *Thread) Breakpoint() proc.BreakpointState { return t.CurrentBreakpoint } +// ThreadID returns this threads ID. func (t *Thread) ThreadID() int { return t.ID } +// Registers returns the CPU registers for this thread. func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { return &t.regs, nil } +// RestoreRegisters will set the CPU registers the value of those provided. func (t *Thread) RestoreRegisters(savedRegs proc.Registers) error { copy(t.regs.buf, savedRegs.(*gdbRegisters).buf) return t.writeRegisters() } +// Arch will return the CPU architecture for the target. func (t *Thread) Arch() proc.Arch { return t.p.bi.Arch } +// BinInfo will return information on the binary being debugged. func (t *Thread) BinInfo() *proc.BinaryInfo { - return &t.p.bi + return t.p.bi } +// Common returns common information across Process implementations. func (t *Thread) Common() *proc.CommonThread { return &t.common } @@ -1219,6 +1269,7 @@ func (t *Thread) stepInstruction(tu *threadUpdater) error { return err } +// StepInstruction will step exactly 1 CPU instruction. func (t *Thread) StepInstruction() error { if err := t.stepInstruction(&threadUpdater{p: t.p}); err != nil { return err @@ -1226,6 +1277,7 @@ func (t *Thread) StepInstruction() error { return t.reloadRegisters() } +// Blocked returns true if the thread is blocked in runtime or kernel code. func (t *Thread) Blocked() bool { regs, err := t.Registers(false) if err != nil { @@ -1507,6 +1559,7 @@ func (t *Thread) clearBreakpointState() { t.CurrentBreakpoint.Clear() } +// SetCurrentBreakpoint will find and set the threads current breakpoint. func (thread *Thread) SetCurrentBreakpoint() error { thread.clearBreakpointState() regs, err := thread.Registers(false) @@ -1732,9 +1785,10 @@ func (regs *gdbRegisters) Get(n int) (uint64, error) { return regs.byName("r15"), nil } - return 0, proc.UnknownRegisterError + return 0, proc.ErrUnknownRegister } +// SetPC will set the value of the PC register to the given value. func (t *Thread) SetPC(pc uint64) error { t.regs.setPC(pc) if t.p.gcmdok { @@ -1744,6 +1798,7 @@ func (t *Thread) SetPC(pc uint64) error { return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value) } +// SetSP will set the value of the SP register to the given value. func (t *Thread) SetSP(sp uint64) error { t.regs.setSP(sp) if t.p.gcmdok { @@ -1753,6 +1808,7 @@ func (t *Thread) SetSP(sp uint64) error { return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value) } +// SetDX will set the value of the DX register to the given value. func (t *Thread) SetDX(dx uint64) error { t.regs.setDX(dx) if t.p.gcmdok { diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index cf5f0b74..ec0633c6 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -426,7 +426,7 @@ func (conn *gdbConn) kill() error { // kill. This is not an error. conn.conn.Close() conn.conn = nil - return proc.ProcessExitedError{Pid: conn.pid} + return proc.ErrProcessExited{Pid: conn.pid} } if err != nil { return err @@ -684,7 +684,7 @@ func (conn *gdbConn) parseStopPacket(resp []byte, threadID string, tu *threadUpd semicolon = len(resp) } status, _ := strconv.ParseUint(string(resp[1:semicolon]), 16, 8) - return false, stopPacket{}, proc.ProcessExitedError{Pid: conn.pid, Status: int(status)} + return false, stopPacket{}, proc.ErrProcessExited{Pid: conn.pid, Status: int(status)} case 'N': // we were singlestepping the thread and the thread exited diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index d012aba1..4de8d5bf 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -64,7 +64,7 @@ func TestRestartAfterExit(t *testing.T) { loc, err := p.CurrentThread().Location() assertNoError(err, t, "CurrentThread().Location()") err = proc.Continue(p) - if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited { + if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited { t.Fatalf("program did not exit: %v", err) } @@ -77,7 +77,7 @@ func TestRestartAfterExit(t *testing.T) { t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line) } err = proc.Continue(p) - if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited { + if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited { t.Fatalf("program did not exit (after exit): %v", err) } }) @@ -100,7 +100,7 @@ func TestRestartDuringStop(t *testing.T) { t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line) } err = proc.Continue(p) - if _, isexited := err.(proc.ProcessExitedError); err == nil || !isexited { + if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited { t.Fatalf("program did not exit (after exit): %v", err) } }) diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index a69c685a..7b7f13e5 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -40,10 +40,13 @@ type RecordingManipulation interface { ClearCheckpoint(id int) error } +// Direction is the direction of execution for the target process. type Direction int8 const ( - Forward Direction = 0 + // Forward direction executes the target normally. + Forward Direction = 0 + // Backward direction executes the target in reverse. Backward Direction = 1 ) @@ -62,7 +65,7 @@ type Info interface { ResumeNotify(chan<- struct{}) // Valid returns true if this Process can be used. When it returns false it // also returns an error describing why the Process is invalid (either - // ProcessExitedError or ProcessDetachedError). + // ErrProcessExited or ProcessDetachedError). Valid() (bool, error) BinInfo() *BinaryInfo // Common returns a struct with fields common to all backends @@ -114,6 +117,8 @@ type CommonProcess struct { fncallEnabled bool } +// NewCommonProcess returns a struct with fields common across +// all process implementations. func NewCommonProcess(fncallEnabled bool) CommonProcess { return CommonProcess{fncallEnabled: fncallEnabled} } diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go index bd11475c..a5aa469b 100644 --- a/pkg/proc/mem.go +++ b/pkg/proc/mem.go @@ -17,6 +17,9 @@ type MemoryReader interface { ReadMemory(buf []byte, addr uintptr) (n int, err error) } +// MemoryReadWriter is an interface for reading or writing to +// the targets memory. This allows us to read from the actual +// target memory or possibly a cache. type MemoryReadWriter interface { MemoryReader WriteMemory(addr uintptr, data []byte) (written int, err error) diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 64e6739f..631d254d 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -12,7 +12,7 @@ import ( // Process represents all of the information the debugger // is holding onto regarding the process we are debugging. type Process struct { - bi proc.BinaryInfo + bi *proc.BinaryInfo pid int // Process Pid // Breakpoint table, holds information on breakpoints. @@ -61,17 +61,36 @@ func New(pid int) *Process { return dbp } +// BinInfo will return the binary info struct associated with this process. func (dbp *Process) BinInfo() *proc.BinaryInfo { - return &dbp.bi + return dbp.bi } -func (dbp *Process) Recorded() (bool, string) { return false, "" } -func (dbp *Process) Restart(string) error { return proc.NotRecordedErr } -func (dbp *Process) Direction(proc.Direction) error { return proc.NotRecordedErr } -func (dbp *Process) When() (string, error) { return "", nil } -func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.NotRecordedErr } -func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.NotRecordedErr } -func (dbp *Process) ClearCheckpoint(int) error { return proc.NotRecordedErr } +// Recorded always returns false for the native proc backend. +func (dbp *Process) Recorded() (bool, string) { return false, "" } + +// Restart will always return an error in the native proc backend, only for +// recorded traces. +func (dbp *Process) Restart(string) error { return proc.ErrNotRecorded } + +// Direction will always return an error in the native proc backend, only for +// recorded traces. +func (dbp *Process) Direction(proc.Direction) error { return proc.ErrNotRecorded } + +// When will always return an empty string and nil, not supported on native proc backend. +func (dbp *Process) When() (string, error) { return "", nil } + +// Checkpoint will always return an error on the native proc backend, +// only supported for recorded traces. +func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.ErrNotRecorded } + +// Checkpoints will always return an error on the native proc backend, +// only supported for recorded traces. +func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.ErrNotRecorded } + +// ClearCheckpoint will always return an error on the native proc backend, +// only supported in recorded traces. +func (dbp *Process) ClearCheckpoint(int) error { return proc.ErrNotRecorded } // Detach from the process being debugged, optionally killing it. func (dbp *Process) Detach(kill bool) (err error) { @@ -111,28 +130,36 @@ func (dbp *Process) Detach(kill bool) (err error) { return } +// Valid returns whether the process is still attached to and +// has not exited. func (dbp *Process) Valid() (bool, error) { if dbp.detached { return false, &proc.ProcessDetachedError{} } if dbp.exited { - return false, &proc.ProcessExitedError{Pid: dbp.Pid()} + return false, &proc.ErrProcessExited{Pid: dbp.Pid()} } return true, nil } +// ResumeNotify specifies a channel that will be closed the next time +// ContinueOnce finishes resuming the target. func (dbp *Process) ResumeNotify(ch chan<- struct{}) { dbp.resumeChan = ch } +// Pid returns the process ID. func (dbp *Process) Pid() int { return dbp.pid } +// SelectedGoroutine returns the current selected, +// active goroutine. func (dbp *Process) SelectedGoroutine() *proc.G { return dbp.selectedGoroutine } +// ThreadList returns a list of threads in the process. func (dbp *Process) ThreadList() []proc.Thread { r := make([]proc.Thread, 0, len(dbp.threads)) for _, v := range dbp.threads { @@ -141,15 +168,18 @@ func (dbp *Process) ThreadList() []proc.Thread { return r } +// FindThread attempts to find the thread with the specified ID. func (dbp *Process) FindThread(threadID int) (proc.Thread, bool) { th, ok := dbp.threads[threadID] return th, ok } +// CurrentThread returns the current selected, active thread. func (dbp *Process) CurrentThread() proc.Thread { return dbp.currentThread } +// Breakpoints returns a list of breakpoints currently set. func (dbp *Process) Breakpoints() *proc.BreakpointMap { return &dbp.breakpoints } @@ -179,7 +209,7 @@ func (dbp *Process) LoadInformation(path string) error { // sends SIGSTOP to all threads. func (dbp *Process) RequestManualStop() error { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } dbp.stopMu.Lock() defer dbp.stopMu.Unlock() @@ -187,11 +217,15 @@ func (dbp *Process) RequestManualStop() error { return dbp.requestManualStop() } +// CheckAndClearManualStopRequest checks if a manual stop has +// been requested, and then clears that state. func (dbp *Process) CheckAndClearManualStopRequest() bool { dbp.stopMu.Lock() + defer dbp.stopMu.Unlock() + msr := dbp.manualStopRequested dbp.manualStopRequested = false - dbp.stopMu.Unlock() + return msr } @@ -222,14 +256,16 @@ func (dbp *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond as // ClearBreakpoint clears the breakpoint at addr. func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) { if dbp.exited { - return nil, &proc.ProcessExitedError{Pid: dbp.Pid()} + return nil, &proc.ErrProcessExited{Pid: dbp.Pid()} } return dbp.breakpoints.Clear(addr, dbp.currentThread.ClearBreakpoint) } +// ContinueOnce will continue the target until it stops. +// This could be the result of a breakpoint or signal. func (dbp *Process) ContinueOnce() (proc.Thread, error) { if dbp.exited { - return nil, &proc.ProcessExitedError{Pid: dbp.Pid()} + return nil, &proc.ErrProcessExited{Pid: dbp.Pid()} } if err := dbp.resume(); err != nil { @@ -274,7 +310,7 @@ func (dbp *Process) StepInstruction() (err error) { } dbp.common.ClearAllGCache() if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } thread.CurrentBreakpoint.Clear() err = thread.StepInstruction() @@ -294,7 +330,7 @@ func (dbp *Process) StepInstruction() (err error) { // SwitchThread changes from current thread to the thread specified by `tid`. func (dbp *Process) SwitchThread(tid int) error { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } if th, ok := dbp.threads[tid]; ok { dbp.currentThread = th @@ -308,7 +344,7 @@ func (dbp *Process) SwitchThread(tid int) error { // running the specified goroutine. func (dbp *Process) SwitchGoroutine(gid int) error { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } g, err := proc.FindGoroutine(dbp, gid) if err != nil { @@ -360,6 +396,8 @@ func initializeDebugProcess(dbp *Process, path string) (*Process, error) { return dbp, nil } +// ClearInternalBreakpoints will clear all non-user set breakpoints. These +// breakpoints are set for internal operations such as 'next'. func (dbp *Process) ClearInternalBreakpoints() error { return dbp.breakpoints.ClearInternalBreakpoints(func(bp *proc.Breakpoint) error { if err := dbp.currentThread.ClearBreakpoint(bp); err != nil { @@ -403,6 +441,8 @@ func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error { return err } +// Common returns common information across Process +// implementations func (dbp *Process) Common() *proc.CommonProcess { return &dbp.common } diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index 661659ea..bb8cd10d 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -39,7 +39,7 @@ type OSProcessDetails struct { func Launch(cmd []string, wd string, foreground bool) (*Process, error) { // check that the argument to Launch is an executable file if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { - return nil, proc.NotExecutableErr + return nil, proc.ErrNotExecutable } argv0Go, err := filepath.Abs(cmd[0]) if err != nil { @@ -305,7 +305,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { return nil, err } dbp.postExit() - return nil, proc.ProcessExitedError{Pid: dbp.pid, Status: status.ExitStatus()} + return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} case C.MACH_RCV_INTERRUPTED: dbp.stopMu.Lock() @@ -402,7 +402,7 @@ func (dbp *Process) exitGuard(err error) error { _, status, werr := dbp.wait(dbp.pid, sys.WNOHANG) if werr == nil && status.Exited() { dbp.postExit() - return proc.ProcessExitedError{Pid: dbp.pid, Status: status.ExitStatus()} + return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} } return err } @@ -429,7 +429,7 @@ func (dbp *Process) resume() error { // stop stops all running threads and sets breakpoints func (dbp *Process) stop(trapthread *Thread) (err error) { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } for _, th := range dbp.threads { if !th.Stopped() { diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index e932e718..f4e1bb64 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -52,7 +52,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) { ) // check that the argument to Launch is an executable file if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { - return nil, proc.NotExecutableErr + return nil, proc.ErrNotExecutable } if !isatty.IsTerminal(os.Stdin.Fd()) { @@ -228,7 +228,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) { if status.Exited() { if wpid == dbp.pid { dbp.postExit() - return nil, proc.ProcessExitedError{Pid: wpid, Status: status.ExitStatus()} + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} } delete(dbp.threads, wpid) continue @@ -286,7 +286,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) { // TODO(dp) alert user about unexpected signals here. if err := th.resumeWithSig(int(status.StopSignal())); err != nil { if err == sys.ESRCH { - return nil, proc.ProcessExitedError{Pid: dbp.pid} + return nil, proc.ErrProcessExited{Pid: dbp.pid} } return nil, err } @@ -418,7 +418,7 @@ func (dbp *Process) resume() error { // stop stops all running threads threads and sets breakpoints func (dbp *Process) stop(trapthread *Thread) (err error) { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } for _, th := range dbp.threads { if !th.Stopped() { diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index b6f8a2ca..4208fb82 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -51,7 +51,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) { _, closer, err := openExecutablePathPE(argv0Go) if err != nil { - return nil, proc.NotExecutableErr + return nil, proc.ErrNotExecutable } closer.Close() @@ -96,7 +96,7 @@ func newDebugProcess(dbp *Process, exepath string) (*Process, error) { } if tid == 0 { dbp.postExit() - return nil, proc.ProcessExitedError{Pid: dbp.pid, Status: exitCode} + return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} } // Suspend all threads so that the call to _ContinueDebugEvent will // not resume the target. @@ -378,7 +378,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { } if tid == 0 { dbp.postExit() - return nil, proc.ProcessExitedError{Pid: dbp.pid, Status: exitCode} + return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} } th := dbp.threads[tid] return th, nil @@ -419,7 +419,7 @@ func (dbp *Process) resume() error { // stop stops all running threads threads and sets breakpoints func (dbp *Process) stop(trapthread *Thread) (err error) { if dbp.exited { - return &proc.ProcessExitedError{Pid: dbp.Pid()} + return &proc.ErrProcessExited{Pid: dbp.Pid()} } // While the debug event that stopped the target was being propagated diff --git a/pkg/proc/native/registers_darwin_amd64.go b/pkg/proc/native/registers_darwin_amd64.go index 2f8f179e..64d84ca8 100644 --- a/pkg/proc/native/registers_darwin_amd64.go +++ b/pkg/proc/native/registers_darwin_amd64.go @@ -284,7 +284,7 @@ func (r *Regs) Get(n int) (uint64, error) { return r.r15, nil } - return 0, proc.UnknownRegisterError + return 0, proc.ErrUnknownRegister } func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { diff --git a/pkg/proc/native/registers_linux_amd64.go b/pkg/proc/native/registers_linux_amd64.go index 6a3014f7..eb3715a2 100644 --- a/pkg/proc/native/registers_linux_amd64.go +++ b/pkg/proc/native/registers_linux_amd64.go @@ -281,7 +281,7 @@ func (r *Regs) Get(n int) (uint64, error) { return r.regs.R15, nil } - return 0, proc.UnknownRegisterError + return 0, proc.ErrUnknownRegister } func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { @@ -324,6 +324,8 @@ func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs proc.LinuxX86X return } +// Copy returns a copy of these registers that is +// guarenteed not to change. func (r *Regs) Copy() proc.Registers { var rr Regs rr.regs = &sys.PtraceRegs{} diff --git a/pkg/proc/native/registers_windows_amd64.go b/pkg/proc/native/registers_windows_amd64.go index 099a1829..febf758c 100644 --- a/pkg/proc/native/registers_windows_amd64.go +++ b/pkg/proc/native/registers_windows_amd64.go @@ -329,7 +329,7 @@ func (r *Regs) Get(n int) (uint64, error) { return r.r15, nil } - return 0, proc.UnknownRegisterError + return 0, proc.ErrUnknownRegister } func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go index 4e2a15bc..07c835ea 100644 --- a/pkg/proc/native/threads.go +++ b/pkg/proc/native/threads.go @@ -27,19 +27,19 @@ type Thread struct { // If we are currently at a breakpoint, we'll clear it // first and then resume execution. Thread will continue until // it hits a breakpoint or is signaled. -func (thread *Thread) Continue() error { - pc, err := thread.PC() +func (t *Thread) Continue() error { + pc, err := t.PC() if err != nil { return err } // Check whether we are stopped at a breakpoint, and // if so, single step over it before continuing. - if _, ok := thread.dbp.FindBreakpoint(pc); ok { - if err := thread.StepInstruction(); err != nil { + if _, ok := t.dbp.FindBreakpoint(pc); ok { + if err := t.StepInstruction(); err != nil { return err } } - return thread.resume() + return t.resume() } // StepInstruction steps a single instruction. @@ -48,33 +48,33 @@ func (thread *Thread) Continue() error { // If the thread is at a breakpoint, we first clear it, // execute the instruction, and then replace the breakpoint. // Otherwise we simply execute the next instruction. -func (thread *Thread) StepInstruction() (err error) { - thread.singleStepping = true +func (t *Thread) StepInstruction() (err error) { + t.singleStepping = true defer func() { - thread.singleStepping = false + t.singleStepping = false }() - pc, err := thread.PC() + pc, err := t.PC() if err != nil { return err } - bp, ok := thread.dbp.FindBreakpoint(pc) + bp, ok := t.dbp.FindBreakpoint(pc) if ok { // Clear the breakpoint so that we can continue execution. - err = thread.ClearBreakpoint(bp) + err = t.ClearBreakpoint(bp) if err != nil { return err } // Restore breakpoint now that we have passed it. defer func() { - err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr) + err = t.dbp.writeSoftwareBreakpoint(t, bp.Addr) }() } - err = thread.singleStep() + err = t.singleStep() if err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { return err } return fmt.Errorf("step failed: %s", err.Error()) @@ -85,61 +85,69 @@ func (thread *Thread) StepInstruction() (err error) { // 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() (*proc.Location, error) { - pc, err := thread.PC() +func (t *Thread) Location() (*proc.Location, error) { + pc, err := t.PC() if err != nil { return nil, err } - f, l, fn := thread.dbp.bi.PCToLine(pc) + f, l, fn := t.dbp.bi.PCToLine(pc) return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil } -func (thread *Thread) Arch() proc.Arch { - return thread.dbp.bi.Arch +// Arch returns the architecture the binary is +// compiled for and executing on. +func (t *Thread) Arch() proc.Arch { + return t.dbp.bi.Arch } -func (thread *Thread) BinInfo() *proc.BinaryInfo { - return &thread.dbp.bi +// BinInfo returns information on the binary. +func (t *Thread) BinInfo() *proc.BinaryInfo { + return t.dbp.bi } -func (thread *Thread) Common() *proc.CommonThread { - return &thread.common +// Common returns information common across Process +// implementations. +func (t *Thread) Common() *proc.CommonThread { + return &t.common } // SetCurrentBreakpoint sets the current breakpoint that this // thread is stopped at as CurrentBreakpoint on the thread struct. -func (thread *Thread) SetCurrentBreakpoint() error { - thread.CurrentBreakpoint.Clear() - pc, err := thread.PC() +func (t *Thread) SetCurrentBreakpoint() error { + t.CurrentBreakpoint.Clear() + pc, err := t.PC() if err != nil { return err } - if bp, ok := thread.dbp.FindBreakpoint(pc); ok { - if err = thread.SetPC(bp.Addr); err != nil { + if bp, ok := t.dbp.FindBreakpoint(pc); ok { + if err = t.SetPC(bp.Addr); err != nil { return err } - thread.CurrentBreakpoint = bp.CheckCondition(thread) - if thread.CurrentBreakpoint.Breakpoint != nil && thread.CurrentBreakpoint.Active { - if g, err := proc.GetG(thread); err == nil { - thread.CurrentBreakpoint.HitCount[g.ID]++ + t.CurrentBreakpoint = bp.CheckCondition(t) + if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active { + if g, err := proc.GetG(t); err == nil { + t.CurrentBreakpoint.HitCount[g.ID]++ } - thread.CurrentBreakpoint.TotalHitCount++ + t.CurrentBreakpoint.TotalHitCount++ } } return nil } -func (th *Thread) Breakpoint() proc.BreakpointState { - return th.CurrentBreakpoint +// Breakpoint returns the current breakpoint that is active +// on this thread. +func (t *Thread) Breakpoint() proc.BreakpointState { + return t.CurrentBreakpoint } -func (th *Thread) ThreadID() int { - return th.ID +// ThreadID returns the ID of this thread. +func (t *Thread) ThreadID() int { + return t.ID } // ClearBreakpoint clears the specified breakpoint. -func (thread *Thread) ClearBreakpoint(bp *proc.Breakpoint) error { - if _, err := thread.WriteMemory(uintptr(bp.Addr), bp.OriginalData); err != nil { +func (t *Thread) ClearBreakpoint(bp *proc.Breakpoint) error { + if _, err := t.WriteMemory(uintptr(bp.Addr), bp.OriginalData); err != nil { return fmt.Errorf("could not clear breakpoint %s", err) } return nil @@ -150,10 +158,13 @@ func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { return registers(t, floatingPoint) } +// RestoreRegisters will set the value of the CPU registers to those +// passed in via 'savedRegs'. func (t *Thread) RestoreRegisters(savedRegs proc.Registers) error { return t.restoreRegisters(savedRegs) } +// PC returns the current program counter value for this thread. func (t *Thread) PC() (uint64, error) { regs, err := t.Registers(false) if err != nil { diff --git a/pkg/proc/native/threads_darwin.go b/pkg/proc/native/threads_darwin.go index 298ac9b5..0cf28beb 100644 --- a/pkg/proc/native/threads_darwin.go +++ b/pkg/proc/native/threads_darwin.go @@ -110,7 +110,7 @@ func (t *Thread) Stopped() bool { func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(data) == 0 { return 0, nil @@ -128,7 +128,7 @@ func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) { func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(buf) == 0 { return 0, nil diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go index 18781280..6e0da76f 100644 --- a/pkg/proc/native/threads_linux.go +++ b/pkg/proc/native/threads_linux.go @@ -61,7 +61,7 @@ func (t *Thread) singleStep() (err error) { if status != nil { rs = status.ExitStatus() } - return proc.ProcessExitedError{Pid: t.dbp.pid, Status: rs} + return proc.ErrProcessExited{Pid: t.dbp.pid, Status: rs} } if wpid == t.ID && status.StopSignal() == sys.SIGTRAP { return nil @@ -108,7 +108,7 @@ func (t *Thread) restoreRegisters(savedRegs proc.Registers) error { func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(data) == 0 { return @@ -119,7 +119,7 @@ func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(data) == 0 { return diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index 3420aafb..8a43dbc3 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -50,7 +50,7 @@ func (t *Thread) singleStep() error { } if tid == 0 { t.dbp.postExit() - return proc.ProcessExitedError{Pid: t.dbp.pid, Status: exitCode} + return proc.ErrProcessExited{Pid: t.dbp.pid, Status: exitCode} } if t.dbp.os.breakThread == t.ID { @@ -123,7 +123,7 @@ func (t *Thread) Stopped() bool { func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(data) == 0 { return 0, nil @@ -140,7 +140,7 @@ var ErrShortRead = errors.New("short read") func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) { if t.dbp.exited { - return 0, proc.ProcessExitedError{Pid: t.dbp.pid} + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} } if len(buf) == 0 { return 0, nil diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index c886740d..3e889434 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -11,19 +11,25 @@ import ( "strings" ) -var NotExecutableErr = errors.New("not an executable file") -var NotRecordedErr = errors.New("not a recording") +// ErrNotExecutable is returned after attempting to execute a non-executable file +// to begin a debug session. +var ErrNotExecutable = errors.New("not an executable file") +// ErrNotRecorded is returned when an action is requested that is +// only possible on recorded (traced) programs. +var ErrNotRecorded = errors.New("not a recording") + +// UnrecoveredPanic is the name given to the unrecovered panic breakpoint. const UnrecoveredPanic = "unrecovered-panic" -// ProcessExitedError indicates that the process has exited and contains both +// ErrProcessExited indicates that the process has exited and contains both // process id and exit status. -type ProcessExitedError struct { +type ErrProcessExited struct { Pid int Status int } -func (pe ProcessExitedError) Error() string { +func (pe ErrProcessExited) Error() string { return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status) } @@ -48,11 +54,13 @@ func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) { return pc, nil } -type FunctionNotFoundError struct { +// ErrFunctionNotFound is returned when failing to find the +// function named 'FuncName' within the binary. +type ErrFunctionNotFound struct { FuncName string } -func (err *FunctionNotFoundError) Error() string { +func (err *ErrFunctionNotFound) Error() string { return fmt.Sprintf("Could not find function %s\n", err.FuncName) } @@ -66,7 +74,7 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset bi := p.BinInfo() origfn := bi.LookupFunc[funcName] if origfn == nil { - return 0, &FunctionNotFoundError{funcName} + return 0, &ErrFunctionNotFound{funcName} } if firstLine { @@ -290,7 +298,7 @@ func Step(dbp Process) (err error) { if err = next(dbp, true, false); err != nil { switch err.(type) { - case ThreadBlockedError: // Noop + case ErrThreadBlocked: // Noop default: dbp.ClearInternalBreakpoints() return @@ -378,7 +386,7 @@ func StepOut(dbp Process) error { sameGCond := SameGoroutineCondition(selg) retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset()) - var deferpc uint64 = 0 + var deferpc uint64 if filepath.Ext(topframe.Current.File) == ".go" { if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 { deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC) @@ -593,11 +601,11 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack return s } -// CreateUnrecoverablePanicBreakpoint creates the unrecoverable-panic breakpoint. +// CreateUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint. // This function is meant to be called by implementations of the Process interface. func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) { panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0) - if _, isFnNotFound := err.(*FunctionNotFoundError); isFnNotFound { + if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound { panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0) } if err == nil { @@ -619,11 +627,10 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error if ok { if !sameline { return pc, nil - } else { - _, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry) - if entryLine == line { - return pc, nil - } + } + _, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry) + if entryLine == line { + return pc, nil } } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index d66d6f01..f01d4553 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -135,7 +135,7 @@ func TestExit(t *testing.T) { protest.AllowRecording(t) withTestProcess("continuetestprog", t, func(p proc.Process, fixture protest.Fixture) { err := proc.Continue(p) - pe, ok := err.(proc.ProcessExitedError) + pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", err) } @@ -155,7 +155,7 @@ func TestExitAfterContinue(t *testing.T) { assertNoError(err, t, "setFunctionBreakpoint()") assertNoError(proc.Continue(p), t, "First Continue()") err = proc.Continue(p) - pe, ok := err.(proc.ProcessExitedError) + pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", pe) } @@ -1068,7 +1068,7 @@ func TestProcessReceivesSIGCHLD(t *testing.T) { protest.AllowRecording(t) withTestProcess("sigchldprog", t, func(p proc.Process, fixture protest.Fixture) { err := proc.Continue(p) - _, ok := err.(proc.ProcessExitedError) + _, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %v", err) } @@ -1384,7 +1384,7 @@ func TestBreakpointCounts(t *testing.T) { for { if err := proc.Continue(p); err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { break } assertNoError(err, t, "Continue()") @@ -1436,7 +1436,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) { for { if err := proc.Continue(p); err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { break } assertNoError(err, t, "Continue()") @@ -1536,7 +1536,7 @@ func TestIssue262(t *testing.T) { if err == nil { t.Fatalf("No error on second continue") } - _, exited := err.(proc.ProcessExitedError) + _, exited := err.(proc.ErrProcessExited) if !exited { t.Fatalf("Process did not exit after second continue: %v", err) } @@ -1645,7 +1645,7 @@ func TestCondBreakpointError(t *testing.T) { err = proc.Continue(p) if err != nil { - if _, exited := err.(proc.ProcessExitedError); !exited { + if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on second Continue(): %v", err) } } else { @@ -1785,7 +1785,7 @@ func TestIssue332_Part2(t *testing.T) { assertNoError(proc.Next(p), t, "second Next()") assertNoError(proc.Next(p), t, "third Next()") err = proc.Continue(p) - if _, exited := err.(proc.ProcessExitedError); !exited { + if _, exited := err.(proc.ErrProcessExited); !exited { assertNoError(err, t, "final Continue()") } }) @@ -1810,7 +1810,7 @@ func TestIssue414(t *testing.T) { for { err := proc.Step(p) if err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { break } } @@ -1871,7 +1871,7 @@ func TestCmdLineArgs(t *testing.T) { if bp.Breakpoint != nil && bp.Name == proc.UnrecoveredPanic { t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp) } - exit, exited := err.(proc.ProcessExitedError) + exit, exited := err.(proc.ErrProcessExited) if !exited { t.Fatalf("Process did not exit: %v", err) } else { @@ -1940,7 +1940,7 @@ func TestNextParked(t *testing.T) { var parkedg *proc.G for parkedg == nil { err := proc.Continue(p) - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return } @@ -1992,7 +1992,7 @@ func TestStepParked(t *testing.T) { LookForParkedG: for { err := proc.Continue(p) - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return } @@ -2040,8 +2040,8 @@ func TestIssue509(t *testing.T) { if err == nil { t.Fatalf("expected error but none was generated") } - if err != proc.NotExecutableErr { - t.Fatalf("expected error \"%v\" got \"%v\"", proc.NotExecutableErr, err) + if err != proc.ErrNotExecutable { + t.Fatalf("expected error \"%v\" got \"%v\"", proc.ErrNotExecutable, err) } os.Remove(exepath) } @@ -2072,7 +2072,7 @@ func TestUnsupportedArch(t *testing.T) { p, err := native.Launch([]string{outfile}, ".", false) switch err { - case proc.UnsupportedLinuxArchErr, proc.UnsupportedWindowsArchErr, proc.UnsupportedDarwinArchErr: + case proc.ErrUnsupportedLinuxArch, proc.ErrUnsupportedWindowsArch, proc.ErrUnsupportedDarwinArch: // all good case nil: p.Detach(true) @@ -2370,7 +2370,7 @@ func TestStepConcurrentPtr(t *testing.T) { count := 0 for { err := proc.Continue(p) - _, exited := err.(proc.ProcessExitedError) + _, exited := err.(proc.ErrProcessExited) if exited { break } @@ -2698,7 +2698,7 @@ func TestStacktraceWithBarriers(t *testing.T) { stackBarrierGoids := []int{} for len(stackBarrierGoids) == 0 { err := proc.Continue(p) - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { t.Logf("Could not run test") return } @@ -2957,16 +2957,16 @@ func TestIssue893(t *testing.T) { if err == nil { return } - if _, ok := err.(*frame.NoFDEForPCError); ok { + if _, ok := err.(*frame.ErrNoFDEForPC); ok { return } - if _, ok := err.(proc.ThreadBlockedError); ok { + if _, ok := err.(proc.ErrThreadBlocked); ok { return } - if _, ok := err.(*proc.NoSourceForPCError); ok { + if _, ok := err.(*proc.ErrNoSourceForPC); ok { return } - if _, ok := err.(proc.ProcessExitedError); ok { + if _, ok := err.(proc.ErrProcessExited); ok { return } assertNoError(err, t, "Next") @@ -3519,7 +3519,7 @@ func TestIssue1101(t *testing.T) { lastCmd = "final Continue()" exitErr = proc.Continue(p) } - if pexit, exited := exitErr.(proc.ProcessExitedError); exited { + if pexit, exited := exitErr.(proc.ErrProcessExited); exited { if pexit.Status != 2 && testBackend != "lldb" { // looks like there's a bug with debugserver on macOS that sometimes // will report exit status 0 instead of the proper exit status. diff --git a/pkg/proc/proc_unix_test.go b/pkg/proc/proc_unix_test.go index ccb6be71..e9d9602e 100644 --- a/pkg/proc/proc_unix_test.go +++ b/pkg/proc/proc_unix_test.go @@ -65,7 +65,7 @@ func TestIssue419(t *testing.T) { continue } - if _, exited := err.(proc.ProcessExitedError); !exited { + if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error after Continue(): %v\n", err) } } diff --git a/pkg/proc/registers.go b/pkg/proc/registers.go index 4c3f8fe1..1826762e 100644 --- a/pkg/proc/registers.go +++ b/pkg/proc/registers.go @@ -29,6 +29,7 @@ type Registers interface { Copy() Registers } +// Register represents a CPU register. type Register struct { Name string Bytes []byte @@ -169,7 +170,9 @@ func AppendSSEReg(regs []Register, name string, xmm []byte) []Register { return append(regs, Register{name, xmm, out.String()}) } -var UnknownRegisterError = errors.New("unknown register") +// ErrUnknownRegister is returned when the value of an unknown +// register is requested. +var ErrUnknownRegister = errors.New("unknown register") type flagRegisterDescr []flagDescr type flagDescr struct { @@ -248,7 +251,7 @@ func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string { return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " ")) } -// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h +// PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h type PtraceFpRegs struct { Cwd uint16 Swd uint16 diff --git a/pkg/proc/registers_amd64.go b/pkg/proc/registers_amd64.go index dc7e1db5..fac817a1 100644 --- a/pkg/proc/registers_amd64.go +++ b/pkg/proc/registers_amd64.go @@ -66,7 +66,7 @@ var dwarfToName = map[int]string{ 66: "SW", } -// getDwarfRegister maps between DWARF register numbers and architecture +// GetDwarfRegister maps between DWARF register numbers and architecture // registers. // The mapping is specified in the System V ABI AMD64 Architecture Processor // Supplement page 57, figure 3.36 diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go index 42411309..b01ffc66 100644 --- a/pkg/proc/scope_test.go +++ b/pkg/proc/scope_test.go @@ -80,7 +80,7 @@ func TestScope(t *testing.T) { for { if err := proc.Continue(p); err != nil { - if _, exited := err.(proc.ProcessExitedError); exited { + if _, exited := err.(proc.ErrProcessExited); exited { break } assertNoError(err, t, "Continue()") diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 5c7b3f17..c63009df 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -489,7 +489,7 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uint64, retaddr uint64) { fde, err := it.bi.frameEntries.FDEForPC(it.pc) var framectx *frame.FrameContext - if _, nofde := err.(*frame.NoFDEForPCError); nofde { + if _, nofde := err.(*frame.ErrNoFDEForPC); nofde { framectx = it.bi.Arch.FixFrameUnwindContext(nil, it.pc, it.bi) } else { framectx = it.bi.Arch.FixFrameUnwindContext(fde.EstablishFrame(it.pc), it.pc, it.bi) @@ -573,10 +573,9 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c } if curReg.Uint64Val <= uint64(cfa) { return it.readRegisterAt(regnum, curReg.Uint64Val) - } else { - newReg := *curReg - return &newReg, nil } + newReg := *curReg + return &newReg, nil } } @@ -668,12 +667,12 @@ func (d *Defer) load() { } } -// spDecreasedErr is used when (*Defer).Next detects a corrupted linked +// errSPDecreased is used when (*Defer).Next detects a corrupted linked // list, specifically when after followin a link pointer the value of SP // decreases rather than increasing or staying the same (the defer list is a // FIFO list, nodes further down the list have been added by function calls // further down the call stack and therefore the SP should always increase). -var spDecreasedErr = errors.New("corrupted defer list: SP decreased") +var errSPDecreased = errors.New("corrupted defer list: SP decreased") // Next returns the next defer in the linked list func (d *Defer) Next() *Defer { @@ -682,7 +681,7 @@ func (d *Defer) Next() *Defer { } d.link.load() if d.link.SP < d.SP { - d.link.Unreadable = spDecreasedErr + d.link.Unreadable = errSPDecreased } return d.link } diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index 53b8c36a..3bc5c5e1 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -16,6 +16,7 @@ import ( "github.com/derekparker/delve/pkg/goversion" ) +// EnableRace allows to configure whether the race detector is enabled on target process. var EnableRace = flag.Bool("racetarget", false, "Enables race detector on inferior process") var runningWithFixtures bool @@ -30,14 +31,17 @@ type Fixture struct { Source string } +// FixtureKey holds the name and builds flags used for a test fixture. type FixtureKey struct { Name string Flags BuildFlags } // Fixtures is a map of fixtureKey{ Fixture.Name, buildFlags } to Fixture. -var Fixtures map[FixtureKey]Fixture = make(map[FixtureKey]Fixture) +var Fixtures = make(map[FixtureKey]Fixture) +// FindFixturesDir will search for the directory holding all test fixtures +// beginning with the current directory and searching up 10 directories. func FindFixturesDir() string { parent := ".." fixturesDir := "_fixtures" @@ -50,16 +54,23 @@ func FindFixturesDir() string { return fixturesDir } +// BuildFlags used to build fixture. type BuildFlags uint32 const ( + // LinkStrip enables '-ldflas="-s"'. LinkStrip BuildFlags = 1 << iota + // EnableCGOOptimization will build CGO code with optimizations. EnableCGOOptimization + // EnableInlining will build a binary with inline optimizations turned on. EnableInlining + // EnableOptimization will build a binary with default optimizations. EnableOptimization + // EnableDWZCompression will enable DWZ compression of DWARF sections. EnableDWZCompression ) +// BuildFixture will compile the fixture 'name' using the provided build flags. func BuildFixture(name string, flags BuildFlags) Fixture { if !runningWithFixtures { panic("RunTestsWithFixtures not called") diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index b1927739..3a9139e0 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -58,11 +58,11 @@ type Location struct { Fn *Function } -// ThreadBlockedError is returned when the thread +// ErrThreadBlocked is returned when the thread // is blocked in the scheduler. -type ThreadBlockedError struct{} +type ErrThreadBlocked struct{} -func (tbe ThreadBlockedError) Error() string { +func (tbe ErrThreadBlocked) Error() string { return "thread blocked" } @@ -72,6 +72,8 @@ type CommonThread struct { returnValues []*Variable } +// ReturnValues reads the return values from the function executing on +// this thread using the provided LoadConfig. func (t *CommonThread) ReturnValues(cfg LoadConfig) []*Variable { loadValues(t.returnValues, cfg) return t.returnValues @@ -84,7 +86,7 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) { if g == nil { if thread.Blocked() { - return Stackframe{}, Stackframe{}, ThreadBlockedError{} + return Stackframe{}, Stackframe{}, ErrThreadBlocked{} } frames, err = ThreadStacktrace(thread, 1) } else { @@ -103,12 +105,14 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) { } } -type NoSourceForPCError struct { +// ErrNoSourceForPC is returned when the given address +// does not correspond with a source file location. +type ErrNoSourceForPC struct { pc uint64 } -func (err *NoSourceForPCError) Error() string { - return fmt.Sprintf("no source for pc %#x", err.pc) +func (err *ErrNoSourceForPC) Error() string { + return fmt.Sprintf("no source for PC %#x", err.pc) } // Set breakpoints at every line, and the return address. Also look for @@ -147,7 +151,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error { } if topframe.Current.Fn == nil { - return &NoSourceForPCError{topframe.Current.PC} + return &ErrNoSourceForPC{topframe.Current.PC} } // sanity check @@ -232,7 +236,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error { } // Set breakpoint on the most recently deferred function (if any) - var deferpc uint64 = 0 + var deferpc uint64 if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 { deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC) var err error @@ -430,7 +434,15 @@ func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) { name := "" if deref { - typ = &godwarf.PtrType{godwarf.CommonType{int64(thread.Arch().PtrSize()), "", reflect.Ptr, 0}, typ} + typ = &godwarf.PtrType{ + CommonType: godwarf.CommonType{ + ByteSize: int64(thread.Arch().PtrSize()), + Name: "", + ReflectKind: reflect.Ptr, + Offset: 0, + }, + Type: typ, + } } else { name = "runtime.curg" } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index ba2b530b..559501b3 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -65,7 +65,15 @@ func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) { } func pointerTo(typ godwarf.Type, arch Arch) godwarf.Type { - return &godwarf.PtrType{godwarf.CommonType{int64(arch.PtrSize()), "*" + typ.Common().Name, reflect.Ptr, 0}, typ} + return &godwarf.PtrType{ + CommonType: godwarf.CommonType{ + ByteSize: int64(arch.PtrSize()), + Name: "*" + typ.Common().Name, + ReflectKind: reflect.Ptr, + Offset: 0, + }, + Type: typ, + } } func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (godwarf.Type, error) { @@ -185,6 +193,12 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou if wg != nil { defer wg.Done() } + + var cu *compileUnit + var pu *partialUnit + var partialUnits = make(map[dwarf.Offset]*partialUnit) + var lastOffset dwarf.Offset + bi.types = make(map[string]dwarf.Offset) bi.packageVars = []packageVar{} bi.Functions = []Function{} @@ -193,11 +207,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou bi.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE) reader := bi.DwarfReader() ardr := bi.DwarfReader() - var cu *compileUnit = nil - var pu *partialUnit = nil - var partialUnits = make(map[dwarf.Offset]*partialUnit) abstractOriginNameTable := make(map[dwarf.Offset]string) - var lastOffset dwarf.Offset outer: for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { @@ -649,14 +659,10 @@ func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) if tflag&tflagNamed != 0 { typename, err = nameOfNamedRuntimeType(_type, kind, tflag) return typename, kind, err - } else { - typename, err = nameOfUnnamedRuntimeType(_type, kind, tflag) - return typename, kind, err } - _type.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind} - - return typename, kind, nil + typename, err = nameOfUnnamedRuntimeType(_type, kind, tflag) + return typename, kind, err } // The layout of a runtime._type struct is as follows: @@ -878,9 +884,8 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err if len(methods.Children) == 0 { buf.WriteString("}") return buf.String(), nil - } else { - buf.WriteString(" ") } + buf.WriteString(" ") for i, im := range methods.Children { var methodname, methodtype string @@ -940,9 +945,8 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) if len(fields.Children) == 0 { buf.WriteString("}") return buf.String(), nil - } else { - buf.WriteString(" ") } + buf.WriteString(" ") for i, field := range fields.Children { var fieldname, fieldtypename string @@ -1086,11 +1090,16 @@ func constructTypeForKind(kind int64, bi *BinaryInfo) (*godwarf.StructType, erro return nil, err } - uint32typ := &godwarf.UintType{godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 4, Name: "uint32"}}} - uint16typ := &godwarf.UintType{godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 2, Name: "uint16"}}} + uint32typ := &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 4, Name: "uint32"}}} + uint16typ := &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 2, Name: "uint16"}}} newStructType := func(name string, sz uintptr) *godwarf.StructType { - return &godwarf.StructType{godwarf.CommonType{Name: name, ByteSize: int64(sz)}, name, "struct", nil, false} + return &godwarf.StructType{ + CommonType: godwarf.CommonType{Name: name, ByteSize: int64(sz)}, + StructName: name, + Kind: "struct", + Field: nil, Incomplete: false, + } } appendField := func(typ *godwarf.StructType, name string, fieldtype godwarf.Type, off uintptr) { diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index f69fed65..fed56e2d 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -34,16 +34,20 @@ const ( maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame ) -type FloatSpecial uint8 +type floatSpecial uint8 const ( - FloatIsNormal FloatSpecial = iota + // FloatIsNormal means the value is a normal float. + FloatIsNormal floatSpecial = iota + // FloatIsNaN means the float is a special NaN value. FloatIsNaN + // FloatIsPosInf means the float is a special positive inifitiy value. FloatIsPosInf + // FloatIsNegInf means the float is a special negative infinity value. FloatIsNegInf ) -type VariableFlags uint16 +type variableFlags uint16 const ( // VariableEscaped is set for local variables that escaped to the heap @@ -52,7 +56,7 @@ const ( // that may outlive the stack frame are allocated on the heap instead and // only the address is recorded on the stack. These variables will be // marked with this flag. - VariableEscaped VariableFlags = (1 << iota) + VariableEscaped variableFlags = (1 << iota) // VariableShadowed is set for local variables that are shadowed by a // variable with the same name in another scope VariableShadowed @@ -79,12 +83,12 @@ type Variable struct { bi *BinaryInfo Value constant.Value - FloatSpecial FloatSpecial + FloatSpecial floatSpecial Len int64 Cap int64 - Flags VariableFlags + Flags variableFlags // Base address of arrays, Base address of the backing array for slices (0 for nil slices) // Base address of the backing byte array for strings @@ -106,6 +110,7 @@ type Variable struct { DeclLine int64 // line number of this variable's declaration } +// LoadConfig controls how variables are loaded from the targets memory. type LoadConfig struct { // FollowPointers requests pointers to be automatically dereferenced. FollowPointers bool @@ -405,13 +410,13 @@ func (ng NoGError) Error() string { return fmt.Sprintf("no G executing on thread %d", ng.tid) } -func (gvar *Variable) parseG() (*G, error) { - mem := gvar.mem - gaddr := uint64(gvar.Addr) - _, deref := gvar.RealType.(*godwarf.PtrType) +func (v *Variable) parseG() (*G, error) { + mem := v.mem + gaddr := uint64(v.Addr) + _, deref := v.RealType.(*godwarf.PtrType) if deref { - gaddrbytes := make([]byte, gvar.bi.Arch.PtrSize()) + gaddrbytes := make([]byte, v.bi.Arch.PtrSize()) _, err := mem.ReadMemory(gaddrbytes, uintptr(gaddr)) if err != nil { return nil, fmt.Errorf("error derefing *G %s", err) @@ -426,27 +431,27 @@ func (gvar *Variable) parseG() (*G, error) { return nil, NoGError{tid: id} } for { - if _, isptr := gvar.RealType.(*godwarf.PtrType); !isptr { + if _, isptr := v.RealType.(*godwarf.PtrType); !isptr { break } - gvar = gvar.maybeDereference() + v = v.maybeDereference() } - gvar.loadValue(LoadConfig{false, 2, 64, 0, -1}) - if gvar.Unreadable != nil { - return nil, gvar.Unreadable + v.loadValue(LoadConfig{false, 2, 64, 0, -1}) + if v.Unreadable != nil { + return nil, v.Unreadable } - schedVar := gvar.fieldVariable("sched") + schedVar := v.fieldVariable("sched") pc, _ := constant.Int64Val(schedVar.fieldVariable("pc").Value) sp, _ := constant.Int64Val(schedVar.fieldVariable("sp").Value) var bp int64 if bpvar := schedVar.fieldVariable("bp"); bpvar != nil && bpvar.Value != nil { bp, _ = constant.Int64Val(bpvar.Value) } - id, _ := constant.Int64Val(gvar.fieldVariable("goid").Value) - gopc, _ := constant.Int64Val(gvar.fieldVariable("gopc").Value) - startpc, _ := constant.Int64Val(gvar.fieldVariable("startpc").Value) + id, _ := constant.Int64Val(v.fieldVariable("goid").Value) + gopc, _ := constant.Int64Val(v.fieldVariable("gopc").Value) + startpc, _ := constant.Int64Val(v.fieldVariable("startpc").Value) waitReason := "" - if wrvar := gvar.fieldVariable("waitreason"); wrvar.Value != nil { + if wrvar := v.fieldVariable("waitreason"); wrvar.Value != nil { switch wrvar.Kind { case reflect.String: waitReason = constant.StringVal(wrvar.Value) @@ -456,7 +461,7 @@ func (gvar *Variable) parseG() (*G, error) { } var stackhi, stacklo uint64 - if stackVar := gvar.fieldVariable("stack"); stackVar != nil { + if stackVar := v.fieldVariable("stack"); stackVar != nil { if stackhiVar := stackVar.fieldVariable("hi"); stackhiVar != nil { stackhi, _ = constant.Uint64Val(stackhiVar.Value) } @@ -465,15 +470,15 @@ func (gvar *Variable) parseG() (*G, error) { } } - stkbarVar, _ := gvar.structMember("stkbar") - stkbarVarPosFld := gvar.fieldVariable("stkbarPos") + stkbarVar, _ := v.structMember("stkbar") + stkbarVarPosFld := v.fieldVariable("stkbarPos") var stkbarPos int64 if stkbarVarPosFld != nil { // stack barriers were removed in Go 1.9 stkbarPos, _ = constant.Int64Val(stkbarVarPosFld.Value) } - status, _ := constant.Int64Val(gvar.fieldVariable("atomicstatus").Value) - f, l, fn := gvar.bi.PCToLine(uint64(pc)) + status, _ := constant.Int64Val(v.fieldVariable("atomicstatus").Value) + f, l, fn := v.bi.PCToLine(uint64(pc)) g := &G{ ID: int(id), GoPC: uint64(gopc), @@ -484,7 +489,7 @@ func (gvar *Variable) parseG() (*G, error) { WaitReason: waitReason, Status: uint64(status), CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn}, - variable: gvar, + variable: v, stkbarVar: stkbarVar, stkbarPos: int(stkbarPos), stackhi: stackhi, @@ -563,7 +568,7 @@ func (g *G) Go() Location { // Backup to CALL instruction. // Mimics runtime/traceback.go:677. if g.GoPC > fn.Entry { - pc -= 1 + pc-- } } f, l, fn := g.variable.bi.PCToLine(pc) @@ -583,7 +588,7 @@ func (g *G) stkbar() ([]savedLR, error) { } g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3}) if g.stkbarVar.Unreadable != nil { - return nil, fmt.Errorf("unreadable stkbar: %v\n", g.stkbarVar.Unreadable) + return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable) } r := make([]savedLR, len(g.stkbarVar.Children)) for i, child := range g.stkbarVar.Children { @@ -1026,13 +1031,13 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { // is performed. // * If srcv and dstv have the same type and are both addressable then the // contents of srcv are copied byte-by-byte into dstv -func (dstv *Variable) setValue(srcv *Variable, srcExpr string) error { +func (v *Variable) setValue(srcv *Variable, srcExpr string) error { srcv.loadValue(loadSingleValue) - typerr := srcv.isType(dstv.RealType, dstv.Kind) + typerr := srcv.isType(v.RealType, v.Kind) if _, isTypeConvErr := typerr.(*typeConvErr); isTypeConvErr { // attempt iface -> eface and ptr-shaped -> eface conversions. - return convertToEface(srcv, dstv) + return convertToEface(srcv, v) } if typerr != nil { return typerr @@ -1043,51 +1048,51 @@ func (dstv *Variable) setValue(srcv *Variable, srcExpr string) error { } // Numerical types - switch dstv.Kind { + switch v.Kind { case reflect.Float32, reflect.Float64: f, _ := constant.Float64Val(srcv.Value) - return dstv.writeFloatRaw(f, dstv.RealType.Size()) + return v.writeFloatRaw(f, v.RealType.Size()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n, _ := constant.Int64Val(srcv.Value) - return dstv.writeUint(uint64(n), dstv.RealType.Size()) + return v.writeUint(uint64(n), v.RealType.Size()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: n, _ := constant.Uint64Val(srcv.Value) - return dstv.writeUint(n, dstv.RealType.Size()) + return v.writeUint(n, v.RealType.Size()) case reflect.Bool: - return dstv.writeBool(constant.BoolVal(srcv.Value)) + return v.writeBool(constant.BoolVal(srcv.Value)) case reflect.Complex64, reflect.Complex128: real, _ := constant.Float64Val(constant.Real(srcv.Value)) imag, _ := constant.Float64Val(constant.Imag(srcv.Value)) - return dstv.writeComplex(real, imag, dstv.RealType.Size()) + return v.writeComplex(real, imag, v.RealType.Size()) } // nilling nillable variables if srcv == nilVariable { - return dstv.writeZero() + return v.writeZero() } // set a string to "" if srcv.Kind == reflect.String && srcv.Len == 0 { - return dstv.writeZero() + return v.writeZero() } // slice assignment (this is not handled by the writeCopy below so that // results of a reslice operation can be used here). if srcv.Kind == reflect.Slice { - return dstv.writeSlice(srcv.Len, srcv.Cap, srcv.Base) + return v.writeSlice(srcv.Len, srcv.Cap, srcv.Base) } // allow any integer to be converted to any pointer - if t, isptr := dstv.RealType.(*godwarf.PtrType); isptr { - return dstv.writeUint(uint64(srcv.Children[0].Addr), int64(t.ByteSize)) + if t, isptr := v.RealType.(*godwarf.PtrType); isptr { + return v.writeUint(uint64(srcv.Children[0].Addr), int64(t.ByteSize)) } // byte-by-byte copying for everything else, but the source must be addressable if srcv.Addr != 0 { - return dstv.writeCopy(srcv) + return v.writeCopy(srcv) } - return fmt.Errorf("can not set variables of type %s (not implemented)", dstv.Kind.String()) + return fmt.Errorf("can not set variables of type %s (not implemented)", v.Kind.String()) } // convertToEface converts srcv into an "interface {}" and writes it to @@ -1504,13 +1509,13 @@ func (v *Variable) writeSlice(len, cap int64, base uintptr) error { return nil } -func (dstv *Variable) writeCopy(srcv *Variable) error { +func (v *Variable) writeCopy(srcv *Variable) error { buf := make([]byte, srcv.RealType.Size()) _, err := srcv.mem.ReadMemory(buf, srcv.Addr) if err != nil { return err } - _, err = dstv.mem.WriteMemory(dstv.Addr, buf) + _, err = v.mem.WriteMemory(v.Addr, buf) return err } @@ -1656,16 +1661,16 @@ func (v *Variable) mapIterator() *mapIterator { } if it.buckets.Kind != reflect.Struct || it.oldbuckets.Kind != reflect.Struct { - v.Unreadable = mapBucketsNotStructErr + v.Unreadable = errMapBucketsNotStruct return nil } return it } -var mapBucketContentsNotArrayErr = errors.New("malformed map type: keys, values or tophash of a bucket is not an array") -var mapBucketContentsInconsistentLenErr = errors.New("malformed map type: inconsistent array length in bucket") -var mapBucketsNotStructErr = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct") +var errMapBucketContentsNotArray = errors.New("malformed map type: keys, values or tophash of a bucket is not an array") +var errMapBucketContentsInconsistentLen = errors.New("malformed map type: inconsistent array length in bucket") +var errMapBucketsNotStruct = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct") func (it *mapIterator) nextBucket() bool { if it.overflow != nil && it.overflow.Addr > 0 { @@ -1755,24 +1760,24 @@ func (it *mapIterator) nextBucket() bool { } if it.tophashes.Kind != reflect.Array || it.keys.Kind != reflect.Array || it.values.Kind != reflect.Array { - it.v.Unreadable = mapBucketContentsNotArrayErr + it.v.Unreadable = errMapBucketContentsNotArray return false } if it.tophashes.Len != it.keys.Len { - it.v.Unreadable = mapBucketContentsInconsistentLenErr + it.v.Unreadable = errMapBucketContentsInconsistentLen return false } if it.values.fieldType.Size() > 0 && it.tophashes.Len != it.values.Len { // if the type of the value is zero-sized (i.e. struct{}) then the values // array's length is zero. - it.v.Unreadable = mapBucketContentsInconsistentLenErr + it.v.Unreadable = errMapBucketContentsInconsistentLen return false } if it.overflow.Kind != reflect.Struct { - it.v.Unreadable = mapBucketsNotStructErr + it.v.Unreadable = errMapBucketsNotStruct return false } @@ -2031,7 +2036,7 @@ func (v *variablesByDepth) Swap(i int, j int) { v.vars[i], v.vars[j] = v.vars[j], v.vars[i] } -// Fetches all variables of a specific type in the current function scope +// Locals fetches all variables of a specific type in the current function scope. func (scope *EvalScope) Locals() ([]*Variable, error) { if scope.Fn == nil { return nil, errors.New("unable to find function context") diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 7e6cd94b..2ec95409 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -81,10 +81,18 @@ type Commands struct { } var ( - LongLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} + // LongLoadConfig loads more information: + // * Follows pointers + // * Loads more array values + // * Does not limit struct fields + LongLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} + // ShortLoadConfig loads less information, not following pointers + // and limiting struct fields loaded to 3. ShortLoadConfig = api.LoadConfig{false, 0, 64, 0, 3} ) +// ByFirstAlias will sort by the first +// alias of a command. type ByFirstAlias []command func (a ByFirstAlias) Len() int { return len(a) } @@ -405,6 +413,7 @@ func (c *Commands) Find(cmdstr string, prefix cmdPrefix) cmdfunc { return noCmdAvailable } +// CallWithContext takes a command and a context that command should be executed in. func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) error { vals := strings.SplitN(strings.TrimSpace(cmdstr), " ", 2) cmdname := vals[0] @@ -415,6 +424,7 @@ func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) erro return c.Find(cmdname, ctx.Prefix)(t, ctx, args) } +// Call takes a command to execute. func (c *Commands) Call(cmdstr string, t *Term) error { ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame}} return c.CallWithContext(cmdstr, t, ctx) diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 56b4dd49..a7af7e2d 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -47,7 +47,7 @@ type Term struct { func New(client service.Client, conf *config.Config) *Term { if client != nil && client.IsMulticlient() { state, _ := client.GetStateNonBlocking() - // The error return of GetState will usually be the ProcessExitedError, + // The error return of GetState will usually be the ErrProcessExited, // which we don't care about. If there are other errors they will show up // later, here we are only concerned about stopping a running target so // that we can initialize our connection. diff --git a/service/api/conversions.go b/service/api/conversions.go index 94a086d7..8e88817c 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -250,6 +250,7 @@ func ConvertLocation(loc proc.Location) Location { } } +// ConvertAsmInstruction converts from proc.AsmInstruction to api.AsmInstruction. func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction { var destloc *Location if inst.DestLoc != nil { @@ -266,6 +267,7 @@ func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction } } +// LoadConfigToProc converts an api.LoadConfig to proc.LoadConfig. func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig { if cfg == nil { return nil @@ -279,6 +281,7 @@ func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig { } } +// LoadConfigFromProc converts a proc.LoadConfig to api.LoadConfig. func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig { if cfg == nil { return nil @@ -292,6 +295,7 @@ func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig { } } +// ConvertRegisters converts proc.Register to api.Register for a slice. func ConvertRegisters(in []proc.Register) (out []Register) { out = make([]Register, len(in)) for i := range in { @@ -300,6 +304,7 @@ func ConvertRegisters(in []proc.Register) (out []Register) { return } +// ConvertCheckpoint converts proc.Chekcpoint to api.Checkpoint. func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) { return Checkpoint(in) } diff --git a/service/api/types.go b/service/api/types.go index df047d59..118841e0 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -11,7 +11,9 @@ import ( "github.com/derekparker/delve/pkg/proc" ) -var NotExecutableErr = proc.NotExecutableErr +// ErrNotExecutable is an error returned when trying +// to debug a non-executable file. +var ErrNotExecutable = proc.ErrNotExecutable // DebuggerState represents the current context of the debugger. type DebuggerState struct { @@ -75,6 +77,10 @@ type Breakpoint struct { TotalHitCount uint64 `json:"totalHitCount"` } +// ValidBreakpointName returns an error if +// the name to be chosen for a breakpoint is invalid. +// The name can not be just a number, and must contain a series +// of letters or numbers. func ValidBreakpointName(name string) error { if _, err := strconv.Atoi(name); err == nil { return errors.New("breakpoint name can not be a number") @@ -114,6 +120,7 @@ type Thread struct { ReturnValues []Variable } +// Location holds program location information. type Location struct { PC uint64 `json:"pc"` File string `json:"file"` @@ -121,6 +128,7 @@ type Location struct { Function *Function `json:"function,omitempty"` } +// Stackframe describes one frame in a stack trace. type Stackframe struct { Location Locals []Variable @@ -136,6 +144,7 @@ type Stackframe struct { Err string } +// Defer describes a deferred function. type Defer struct { DeferredLoc Location // deferred function DeferLoc Location // location of the defer statement @@ -143,6 +152,8 @@ type Defer struct { Unreadable string } +// Var will return the variable described by 'name' within +// this stack frame. func (frame *Stackframe) Var(name string) *Variable { for i := range frame.Locals { if frame.Locals[i].Name == name { @@ -168,6 +179,7 @@ type Function struct { Optimized bool `json:"optimized"` } +// Name will return the function name. func (fn *Function) Name() string { if fn == nil { return "???" @@ -297,7 +309,7 @@ type DebuggerCommand struct { Expr string `json:"expr,omitempty"` } -// Informations about the current breakpoint +// BreakpointInfo contains informations about the current breakpoint type BreakpointInfo struct { Stacktrace []Stackframe `json:"stacktrace,omitempty"` Goroutine *Goroutine `json:"goroutine,omitempty"` @@ -306,6 +318,8 @@ type BreakpointInfo struct { Locals []Variable `json:"locals,omitempty"` } +// EvalScope is the scope a command should +// be evaluated in. Describes the goroutine and frame number. type EvalScope struct { GoroutineID int Frame int @@ -320,7 +334,7 @@ const ( Step = "step" // StepOut continues to the return address of the current function StepOut = "stepOut" - // SingleStep continues for exactly 1 cpu instruction. + // StepInstruction continues for exactly 1 cpu instruction. StepInstruction = "stepInstruction" // Next continues to the next source line, not entering function calls. Next = "next" @@ -334,10 +348,14 @@ const ( Call = "call" ) +// AssemblyFlavour describes the output +// of disassembled code. type AssemblyFlavour int const ( - GNUFlavour = AssemblyFlavour(proc.GNUFlavour) + // GNUFlavour will disassemble using GNU assembly syntax. + GNUFlavour = AssemblyFlavour(proc.GNUFlavour) + // IntelFlavour will disassemble using Intel assembly syntax. IntelFlavour = AssemblyFlavour(proc.IntelFlavour) ) @@ -357,28 +375,35 @@ type AsmInstruction struct { AtPC bool } +// AsmInstructions is a slice of single instructions. type AsmInstructions []AsmInstruction +// GetVersionIn is the argument for GetVersion. type GetVersionIn struct { } +// GetVersionOut is the result of GetVersion. type GetVersionOut struct { DelveVersion string APIVersion int } +// SetAPIVersionIn is the input for SetAPIVersion. type SetAPIVersionIn struct { APIVersion int } +// SetAPIVersionOut is the output for SetAPIVersion. type SetAPIVersionOut struct { } +// Register holds information on a CPU register. type Register struct { Name string Value string } +// Registers is a list of CPU registers. type Registers []Register func (regs Registers) String() string { @@ -396,11 +421,15 @@ func (regs Registers) String() string { return buf.String() } +// DiscardedBreakpoint is a breakpoint that is not +// reinstated during a restart. type DiscardedBreakpoint struct { Breakpoint *Breakpoint Reason string } +// Checkpoint is a point in the program that +// can be returned to in certain execution modes. type Checkpoint struct { ID int When string diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 96df5c5b..746684f0 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -117,7 +117,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) { d.log.Infof("launching process with args: %v", d.processArgs) p, err := d.Launch(d.processArgs, d.config.WorkingDir) if err != nil { - if err != proc.NotExecutableErr && err != proc.UnsupportedLinuxArchErr && err != proc.UnsupportedWindowsArchErr && err != proc.UnsupportedDarwinArchErr { + if err != proc.ErrNotExecutable && err != proc.ErrUnsupportedLinuxArch && err != proc.ErrUnsupportedWindowsArch && err != proc.ErrUnsupportedDarwinArch { err = go11DecodeErrorCheck(err) err = fmt.Errorf("could not launch process: %s", err) } @@ -128,6 +128,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) { return d, nil } +// Launch will start a process with the given args and working directory. func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) { switch d.config.Backend { case "native": @@ -152,6 +153,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) // the target's executable. var ErrNoAttachPath = errors.New("must specify executable path on macOS") +// Attach will attach to the process specified by 'pid'. func (d *Debugger) Attach(pid int, path string) (proc.Process, error) { switch d.config.Backend { case "native": @@ -168,7 +170,7 @@ func (d *Debugger) Attach(pid int, path string) (proc.Process, error) { } } -var macOSBackendUnavailableErr = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server") +var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server") func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) { if runtime.GOOS != "darwin" { @@ -178,7 +180,7 @@ func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) return p, err } - return p, macOSBackendUnavailableErr + return p, errMacOSBackendUnavailable } // ProcessPid returns the PID of the process @@ -224,7 +226,7 @@ func (d *Debugger) Restart(pos string, resetArgs bool, newArgs []string) ([]api. } if pos != "" { - return nil, proc.NotRecordedErr + return nil, proc.ErrNotRecorded } if valid, _ := d.target.Valid(); valid { @@ -252,7 +254,7 @@ func (d *Debugger) Restart(pos string, resetArgs bool, newArgs []string) ([]api. var err error oldBp.Addr, err = proc.FindFileLocation(p, oldBp.File, oldBp.Line) if err != nil { - discarded = append(discarded, api.DiscardedBreakpoint{oldBp, err.Error()}) + discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()}) continue } } @@ -295,7 +297,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error exited := false if _, err := d.target.Valid(); err != nil { - _, exited = err.(*proc.ProcessExitedError) + _, exited = err.(*proc.ErrProcessExited) } state = &api.DebuggerState{ @@ -388,6 +390,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin return createdBp, nil } +// AmendBreakpoint will update the breakpoint with the matching ID. func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -402,6 +405,8 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error { return copyBreakpointInfo(original, amend) } +// CancelNext will clear internal breakpoints, thus cancelling the 'next', +// 'step' or 'stepout' operation. func (d *Debugger) CancelNext() error { return d.target.ClearInternalBreakpoints() } @@ -596,7 +601,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er } if err != nil { - if exitedErr, exited := err.(proc.ProcessExitedError); command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && exited { + if exitedErr, exited := err.(proc.ErrProcessExited); command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && exited { state := &api.DebuggerState{} state.Exited = true state.ExitStatus = exitedErr.Status @@ -667,7 +672,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error bpi.Variables = make([]api.Variable, len(bp.Variables)) } for i := range bp.Variables { - v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{true, 1, 64, 64, -1}) + v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}) if err != nil { bpi.Variables[i] = api.Variable{Name: bp.Variables[i], Unreadable: fmt.Sprintf("eval error: %v", err)} } else { @@ -716,6 +721,7 @@ func (d *Debugger) Functions(filter string) ([]string, error) { return regexFilterFuncs(filter, d.target.BinInfo().Functions) } +// Types returns all type information in the binary. func (d *Debugger) Types(filter string) ([]string, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -1059,12 +1065,14 @@ func (d *Debugger) Recorded() (recorded bool, tracedir string) { return d.target.Recorded() } +// Checkpoint will set a checkpoint specified by the locspec. func (d *Debugger) Checkpoint(where string) (int, error) { d.processMutex.Lock() defer d.processMutex.Unlock() return d.target.Checkpoint(where) } +// Checkpoints will return a list of checkpoints. func (d *Debugger) Checkpoints() ([]api.Checkpoint, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -1079,6 +1087,7 @@ func (d *Debugger) Checkpoints() ([]api.Checkpoint, error) { return r, nil } +// ClearCheckpoint will clear the checkpoint of the given ID. func (d *Debugger) ClearCheckpoint(id int) error { d.processMutex.Lock() defer d.processMutex.Unlock()