Skip to content

Commit

Permalink
pkg/proc: Clean up proc.go
Browse files Browse the repository at this point in the history
This patch moves out unrelated types, variables and functions from
proc.go into a place where they make more sense.
  • Loading branch information
derekparker authored and aarzilli committed Mar 24, 2020
1 parent 697310f commit c4fd80f
Show file tree
Hide file tree
Showing 12 changed files with 1,002 additions and 998 deletions.
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5 h1:rIXlvz2IWiupMFlC45cZCXZFvKX/ExBcSLrDy2G0Lp8=
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
Expand Down
156 changes: 128 additions & 28 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ import (
"github.com/sirupsen/logrus"
)

const (
dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
dwarfTreeCacheSize = 512 // size of the dwarfTree cache of each image
)

// BinaryInfo holds information on the binaries being executed (this
// includes both the executable and also any loaded libraries).
type BinaryInfo struct {
Expand Down Expand Up @@ -102,24 +107,137 @@ type BinaryInfo struct {
logger *logrus.Entry
}

// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
// position independant executable.
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
var (
// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
// position independant executable.
ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")

// ErrNoDebugInfoFound is returned when Delve cannot open the debug_info
// section or find an external debug info file.
var ErrNoDebugInfoFound = errors.New("could not open debug info")
// ErrNoDebugInfoFound is returned when Delve cannot open the debug_info
// section or find an external debug info file.
ErrNoDebugInfoFound = errors.New("could not open debug info")
)

// ErrUnsupportedArch is returned when attempting to debug a binary compiled for an unsupported architecture.
type ErrUnsupportedArch struct {
os string
cpuArch CpuArch
var (
supportedLinuxArch = map[elf.Machine]bool{
elf.EM_X86_64: true,
elf.EM_AARCH64: true,
elf.EM_386: true,
}

supportedWindowsArch = map[PEMachine]bool{
IMAGE_FILE_MACHINE_AMD64: true,
}

supportedDarwinArch = map[macho.Cpu]bool{
macho.CpuAmd64: true,
}
)

// ErrFunctionNotFound is returned when failing to find the
// function named 'FuncName' within the binary.
type ErrFunctionNotFound struct {
FuncName string
}

func (err *ErrFunctionNotFound) Error() string {
return fmt.Sprintf("could not find function %s\n", err.FuncName)
}

// FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normalized to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) {
pcs, err := p.BinInfo().LineToPC(fileName, lineno)
if err != nil {
return nil, err
}
var fn *Function
for i := range pcs {
if fn == nil || pcs[i] < fn.Entry || pcs[i] >= fn.End {
fn = p.BinInfo().PCToFunc(pcs[i])
}
if fn != nil && fn.Entry == pcs[i] {
pcs[i], _ = FirstPCAfterPrologue(p, fn, true)
}
}
return pcs, nil
}

// FindFunctionLocation finds address of a function's line
// If lineOffset is passed FindFunctionLocation will return the address of that line
func FindFunctionLocation(p Process, funcName string, lineOffset int) ([]uint64, error) {
bi := p.BinInfo()
origfn := bi.LookupFunc[funcName]
if origfn == nil {
return nil, &ErrFunctionNotFound{funcName}
}

if lineOffset <= 0 {
r := make([]uint64, 0, len(origfn.InlinedCalls)+1)
if origfn.Entry > 0 {
// add concrete implementation of the function
pc, err := FirstPCAfterPrologue(p, origfn, false)
if err != nil {
return nil, err
}
r = append(r, pc)
}
// add inlined calls to the function
for _, call := range origfn.InlinedCalls {
r = append(r, call.LowPC)
}
if len(r) == 0 {
return nil, &ErrFunctionNotFound{funcName}
}
return r, nil
}
filename, lineno := origfn.cu.lineInfo.PCToLine(origfn.Entry, origfn.Entry)
return bi.LineToPC(filename, lineno+lineOffset)
}

// FirstPCAfterPrologue returns the address of the first
// instruction after the prologue for function fn.
// If sameline is set FirstPCAfterPrologue will always return an
// address associated with the same line as fn.Entry.
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
if ok {
if !sameline {
return pc, nil
}
_, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
if entryLine == line {
return pc, nil
}
}

pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline)
if err != nil {
return fn.Entry, err
}

if pc == fn.Entry {
// Look for the first instruction with the stmt flag set, so that setting a
// breakpoint with file:line and with the function name always result on
// the same instruction being selected.
if pc2, _, _, ok := fn.cu.lineInfo.FirstStmtForLine(fn.Entry, fn.End); ok {
return pc2, nil
}
}

return pc, nil
}

// CpuArch is a stringer interface representing CPU architectures.
type CpuArch interface {
String() string
}

// ErrUnsupportedArch is returned when attempting to debug a binary compiled for an unsupported architecture.
type ErrUnsupportedArch struct {
os string
cpuArch CpuArch
}

func (e *ErrUnsupportedArch) Error() string {
var supportArchs []CpuArch
switch e.os {
Expand Down Expand Up @@ -151,24 +269,6 @@ func (e *ErrUnsupportedArch) Error() string {
return errStr
}

var supportedLinuxArch = map[elf.Machine]bool{
elf.EM_X86_64: true,
elf.EM_AARCH64: true,
elf.EM_386: true,
}

var supportedWindowsArch = map[PEMachine]bool{
IMAGE_FILE_MACHINE_AMD64: true,
}

var supportedDarwinArch = map[macho.Cpu]bool{
macho.CpuAmd64: true,
}

const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)

const dwarfTreeCacheSize = 512 // size of the dwarfTree cache of each image

type compileUnit struct {
name string // univocal name for non-go compile units
lowPC uint64
Expand Down
12 changes: 12 additions & 0 deletions pkg/proc/breakpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import (
"reflect"
)

const (
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
UnrecoveredPanic = "unrecovered-panic"

// FatalThrow is the name given to the breakpoint triggered when the target
// process dies because of a fatal runtime error.
FatalThrow = "runtime-fatal-throw"

unrecoveredPanicID = -1
fatalThrowID = -2
)

// Breakpoint represents a physical breakpoint. Stores information on the break
// point including the byte of data that originally was stored at that
// address.
Expand Down
106 changes: 106 additions & 0 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,112 @@ type EvalScope struct {
callCtx *callContext
}

// ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame.
// If deferCall is > 0 the eval scope will be relative to the specified deferred call.
func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error) {
if _, err := dbp.Valid(); err != nil {
return nil, err
}
ct := dbp.CurrentThread()
g, err := FindGoroutine(dbp, gid)
if err != nil {
return nil, err
}
if g == nil {
return ThreadScope(ct)
}

var thread MemoryReadWriter
if g.Thread == nil {
thread = ct
} else {
thread = g.Thread
}

var opts StacktraceOptions
if deferCall > 0 {
opts = StacktraceReadDefers
}

locs, err := g.Stacktrace(frame+1, opts)
if err != nil {
return nil, err
}

if frame >= len(locs) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
}

if deferCall > 0 {
if deferCall-1 >= len(locs[frame].Defers) {
return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
}

d := locs[frame].Defers[deferCall-1]
if d.Unreadable != nil {
return nil, d.Unreadable
}

return d.EvalScope(ct)
}

return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
}

// FrameToScope returns a new EvalScope for frames[0].
// If frames has at least two elements all memory between
// frames[0].Regs.SP() and frames[1].Regs.CFA will be cached.
// Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA
// will be cached.
func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope {
// Creates a cacheMem that will preload the entire stack frame the first
// time any local variable is read.
// Remember that the stack grows downward in memory.
minaddr := frames[0].Regs.SP()
var maxaddr uint64
if len(frames) > 1 && frames[0].SystemStack == frames[1].SystemStack {
maxaddr = uint64(frames[1].Regs.CFA)
} else {
maxaddr = uint64(frames[0].Regs.CFA)
}
if maxaddr > minaddr && maxaddr-minaddr < maxFramePrefetchSize {
thread = cacheMemory(thread, uintptr(minaddr), int(maxaddr-minaddr))
}

s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: bi, frameOffset: frames[0].FrameOffset()}
s.PC = frames[0].lastpc
return s
}

// ThreadScope returns an EvalScope for the given thread.
func ThreadScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 1)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
return FrameToScope(thread.BinInfo(), thread, nil, locations...), nil
}

// GoroutineScope returns an EvalScope for the goroutine running on the given thread.
func GoroutineScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 1)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
g, err := GetG(thread)
if err != nil {
return nil, err
}
return FrameToScope(thread.BinInfo(), thread, g, locations...), nil
}

// EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
if scope.callCtx != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/gdbserial/gdbserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ func (p *Process) Pid() int {
// and the process has not exited.
func (p *Process) Valid() (bool, error) {
if p.detached {
return false, &proc.ProcessDetachedError{}
return false, proc.ErrProcessDetached
}
if p.exited {
return false, &proc.ErrProcessExited{Pid: p.Pid()}
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,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
// ErrProcessExited or ProcessDetachedError).
// ErrProcessExited or ErrProcessDetached).
Valid() (bool, error)
BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/native/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (dbp *Process) Detach(kill bool) (err error) {
// has not exited.
func (dbp *Process) Valid() (bool, error) {
if dbp.detached {
return false, &proc.ProcessDetachedError{}
return false, proc.ErrProcessDetached
}
if dbp.exited {
return false, &proc.ErrProcessExited{Pid: dbp.Pid()}
Expand Down
Loading

0 comments on commit c4fd80f

Please sign in to comment.