pkg/proc: Refactor process post initialization

This patch is a slight refactor to share more code used for genericprocess initialization. There will always be OS/backend specificinitialization, but as much as can be shared should be to preventduplicating of any logic (setting internal breakpoints, loading bininfo,etc).
This commit is contained in:
Derek Parker 2018-11-12 14:52:13 -08:00 committed by Alessandro Arzilli
parent 91953dd49c
commit d61cd1c0d7
13 changed files with 256 additions and 234 deletions

@ -30,6 +30,9 @@ func TestMain(m *testing.M) {
testBackend = os.Getenv("PROCTEST") testBackend = os.Getenv("PROCTEST")
if testBackend == "" { if testBackend == "" {
testBackend = "native" testBackend = "native"
if runtime.GOOS == "darwin" {
testBackend = "lldb"
}
} }
} }
os.Exit(m.Run()) os.Exit(m.Run())
@ -85,8 +88,10 @@ func TestBuild(t *testing.T) {
scan := bufio.NewScanner(stderr) scan := bufio.NewScanner(stderr)
// wait for the debugger to start // wait for the debugger to start
scan.Scan() scan.Scan()
t.Log(scan.Text())
go func() { go func() {
for scan.Scan() { for scan.Scan() {
t.Log(scan.Text())
// keep pipe empty // keep pipe empty
} }
}() }()

@ -304,20 +304,22 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
// LoadBinaryInfo will load and store the information from the binary at 'path'. // 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 // It is expected this will be called in parallel with other initialization steps
// so a sync.WaitGroup must be provided. // so a sync.WaitGroup must be provided.
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string, wg *sync.WaitGroup) error { func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string) error {
fi, err := os.Stat(path) fi, err := os.Stat(path)
if err == nil { if err == nil {
bi.lastModified = fi.ModTime() bi.lastModified = fi.ModTime()
} }
var wg sync.WaitGroup
defer wg.Wait()
bi.Path = path bi.Path = path
switch bi.GOOS { switch bi.GOOS {
case "linux": case "linux":
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, wg) return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg)
case "windows": case "windows":
return bi.LoadBinaryInfoPE(path, entryPoint, wg) return bi.LoadBinaryInfoPE(path, entryPoint, &wg)
case "darwin": case "darwin":
return bi.LoadBinaryInfoMacho(path, entryPoint, wg) return bi.LoadBinaryInfoMacho(path, entryPoint, &wg)
} }
return errors.New("unsupported operating system") return errors.New("unsupported operating system")
} }

@ -221,13 +221,16 @@ func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
bpmap.breakpointIDCounter = 0 bpmap.breakpointIDCounter = 0
} }
type writeBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error) // WriteBreakpointFn is a type that represents a function to be used for
// writting breakpoings into the target.
type WriteBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error)
type clearBreakpointFn func(*Breakpoint) error type clearBreakpointFn func(*Breakpoint) error
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this // Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
// function, call proc.Process.SetBreakpoint instead, this function exists // function, call proc.Process.SetBreakpoint instead, this function exists
// to implement proc.Process.SetBreakpoint. // to implement proc.Process.SetBreakpoint.
func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) { func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
if bp, ok := bpmap.M[addr]; ok { if bp, ok := bpmap.M[addr]; ok {
// We can overlap one internal breakpoint with one user breakpoint, we // We can overlap one internal breakpoint with one user breakpoint, we
// need to support this otherwise a conditional breakpoint can mask a // need to support this otherwise a conditional breakpoint can mask a
@ -275,7 +278,7 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
} }
// SetWithID creates a breakpoint at addr, with the specified ID. // SetWithID creates a breakpoint at addr, with the specified ID.
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) { func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint) bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
if err == nil { if err == nil {
bp.ID = id bp.ID = id

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"io" "io"
"sync"
"github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc"
) )
@ -193,26 +192,42 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error
return nil, err return nil, err
} }
var wg sync.WaitGroup if err := p.initialize(exePath, debugInfoDirs); err != nil {
err = p.bi.LoadBinaryInfo(exePath, p.entryPoint, debugInfoDirs, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
}
if err != nil {
return nil, err return nil, err
} }
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return p, nil return p, nil
} }
// initialize for core files doesn't do much
// aside from call the post initialization setup.
func (p *Process) initialize(path string, debugInfoDirs []string) error {
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
}
// BinInfo will return the binary info. // BinInfo will return the binary info.
func (p *Process) BinInfo() *proc.BinaryInfo { func (p *Process) BinInfo() *proc.BinaryInfo {
return p.bi return p.bi
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// EntryPoint will return the entry point address for this core file.
func (p *Process) EntryPoint() (uint64, error) {
return p.entryPoint, nil
}
// writeBreakpoint is a noop function since you
// cannot write breakpoints into core files.
func (p *Process) writeBreakpoint(addr uint64) (file string, line int, fn *proc.Function, originalData []byte, err error) {
return "", 0, nil, nil, errors.New("cannot write a breakpoint to a core file")
}
// Recorded returns whether this is a live or recorded process. Always returns true for core files. // 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 (p *Process) Recorded() (bool, string) { return true, "" }

@ -174,7 +174,7 @@ func withCoreFile(t *testing.T, name, args string) *Process {
p, err := OpenCore(corePath, fix.Path, []string{}) p, err := OpenCore(corePath, fix.Path, []string{})
if err != nil { if err != nil {
t.Errorf("ReadCore(%q) failed: %v", corePath, err) t.Errorf("OpenCore(%q) failed: %v", corePath, err)
pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern") pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern")
t.Errorf("read core_pattern: %q, %v", pat, err) t.Errorf("read core_pattern: %q, %v", pat, err)
apport, err := ioutil.ReadFile("/var/log/apport.log") apport, err := ioutil.ReadFile("/var/log/apport.log")

@ -75,7 +75,6 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
@ -83,7 +82,7 @@ import (
"github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil" "github.com/derekparker/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -130,7 +129,7 @@ type Process struct {
common proc.CommonProcess common proc.CommonProcess
} }
// Thread is a thread. // Thread represents an operating system thread.
type Thread struct { type Thread struct {
ID int ID int
strID string strID string
@ -142,8 +141,7 @@ type Thread struct {
} }
// ErrBackendUnavailable is returned when the stub program can not be found. // ErrBackendUnavailable is returned when the stub program can not be found.
type ErrBackendUnavailable struct { type ErrBackendUnavailable struct{}
}
func (err *ErrBackendUnavailable) Error() string { func (err *ErrBackendUnavailable) Error() string {
return "backend unavailable" return "backend unavailable"
@ -250,7 +248,6 @@ func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string
// and Connect will be unable to function without knowing them. // and Connect will be unable to function without knowing them.
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error { func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
p.conn.conn = conn p.conn.conn = conn
p.conn.pid = pid p.conn.pid = pid
err := p.conn.handshake() err := p.conn.handshake()
if err != nil { if err != nil {
@ -270,55 +267,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
} }
} }
if path == "" { if err := p.initialize(path, debugInfoDirs); err != nil {
// If we are attaching to a running process and the user didn't specify
// the executable file manually we must ask the stub for it.
// We support both qXfer:exec-file:read:: (the gdb way) and calling
// qProcessInfo (the lldb way).
// Unfortunately debugserver on macOS supports neither.
path, err = p.conn.readExecFile()
if err != nil {
if isProtocolErrorUnsupported(err) {
_, path, err = p.loadProcessInfo(pid)
if err != nil {
conn.Close()
return err
}
} else {
conn.Close()
return fmt.Errorf("could not determine executable path: %v", err)
}
}
}
if path == "" {
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
// this supported on debugserver (but only on macOS >= 12.10)
images, _ := p.conn.getLoadedDynamicLibraries()
for _, image := range images {
if image.MachHeader.FileType == macho.TypeExec {
path = image.Pathname
break
}
}
}
var entryPoint uint64
if auxv, err := p.conn.readAuxv(); err == nil {
// If we can't read the auxiliary vector it just means it's not supported
// by the OS or by the stub. If we are debugging a PIE and the entry point
// is needed proc.LoadBinaryInfo will complain about it.
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
}
var wg sync.WaitGroup
err = p.bi.LoadBinaryInfo(path, entryPoint, debugInfoDirs, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
}
if err != nil {
conn.Close()
return err return err
} }
@ -335,36 +284,6 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
p.loadGInstrAddr = addr p.loadGInstrAddr = addr
} }
} }
err = p.updateThreadList(&threadUpdater{p: p})
if err != nil {
conn.Close()
p.bi.Close()
return err
}
if p.conn.pid <= 0 {
p.conn.pid, _, err = p.loadProcessInfo(0)
if err != nil && !isProtocolErrorUnsupported(err) {
conn.Close()
p.bi.Close()
return err
}
}
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
proc.CreateUnrecoveredPanicBreakpoint(p, p.writeBreakpoint, &p.breakpoints)
panicpc, err := proc.FindFunctionLocation(p, "runtime.startpanic", true, 0)
if err == nil {
bp, err := p.breakpoints.SetWithID(-1, panicpc, p.writeBreakpoint)
if err == nil {
bp.Name = proc.UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
return nil return nil
} }
@ -523,7 +442,6 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error)
process.Stdout = os.Stdout process.Stdout = os.Stdout
process.Stderr = os.Stderr process.Stderr = os.Stderr
process.SysProcAttr = sysProcAttr(false) process.SysProcAttr = sysProcAttr(false)
err := process.Start() err := process.Start()
@ -545,10 +463,80 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error)
return p, nil return p, nil
} }
// loadProcessInfo uses qProcessInfo to load the inferior's PID and // EntryPoint will return the process entry point address, useful for
// debugging PIEs.
func (p *Process) EntryPoint() (uint64, error) {
var entryPoint uint64
if auxv, err := p.conn.readAuxv(); err == nil {
// If we can't read the auxiliary vector it just means it's not supported
// by the OS or by the stub. If we are debugging a PIE and the entry point
// is needed proc.LoadBinaryInfo will complain about it.
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
}
return entryPoint, nil
}
// initialize uses qProcessInfo to load the inferior's PID and
// executable path. This command is not supported by all stubs and not all // executable path. This command is not supported by all stubs and not all
// stubs will report both the PID and executable path. // stubs will report both the PID and executable path.
func (p *Process) loadProcessInfo(pid int) (int, string, error) { func (p *Process) initialize(path string, debugInfoDirs []string) error {
var err error
if path == "" {
// If we are attaching to a running process and the user didn't specify
// the executable file manually we must ask the stub for it.
// We support both qXfer:exec-file:read:: (the gdb way) and calling
// qProcessInfo (the lldb way).
// Unfortunately debugserver on macOS supports neither.
path, err = p.conn.readExecFile()
if err != nil {
if isProtocolErrorUnsupported(err) {
_, path, err = queryProcessInfo(p, p.Pid())
if err != nil {
p.conn.conn.Close()
return err
}
} else {
p.conn.conn.Close()
return fmt.Errorf("could not determine executable path: %v", err)
}
}
}
if path == "" {
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
// this supported on debugserver (but only on macOS >= 12.10)
images, _ := p.conn.getLoadedDynamicLibraries()
for _, image := range images {
if image.MachHeader.FileType == macho.TypeExec {
path = image.Pathname
break
}
}
}
err = p.updateThreadList(&threadUpdater{p: p})
if err != nil {
p.conn.conn.Close()
p.bi.Close()
return err
}
if p.conn.pid <= 0 {
p.conn.pid, _, err = queryProcessInfo(p, 0)
if err != nil && !isProtocolErrorUnsupported(err) {
p.conn.conn.Close()
p.bi.Close()
return err
}
}
if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil {
p.conn.conn.Close()
return err
}
return nil
}
func queryProcessInfo(p *Process, pid int) (int, string, error) {
pi, err := p.conn.queryProcessInfo(pid) pi, err := p.conn.queryProcessInfo(pid)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@ -659,7 +647,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
// resume all threads // resume all threads
var threadID string var threadID string
var sig uint8 = 0 var sig uint8
var tu = threadUpdater{p: p} var tu = threadUpdater{p: p}
var err error var err error
continueLoop: continueLoop:
@ -720,7 +708,7 @@ continueLoop:
for _, thread := range p.threads { for _, thread := range p.threads {
if thread.strID == threadID { if thread.strID == threadID {
var err error = nil var err error
switch sig { switch sig {
case 0x91: case 0x91:
err = errors.New("bad access") err = errors.New("bad access")
@ -742,6 +730,13 @@ continueLoop:
return nil, fmt.Errorf("could not find thread %s", threadID) return nil, fmt.Errorf("could not find thread %s", threadID)
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// StepInstruction will step exactly one CPU instruction. // StepInstruction will step exactly one CPU instruction.
func (p *Process) StepInstruction() error { func (p *Process) StepInstruction() error {
thread := p.currentThread thread := p.currentThread
@ -1318,7 +1313,6 @@ func (t *Thread) Blocked() bool {
default: default:
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall") return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
} }
return false
} }
// loadGInstr returns the correct MOV instruction for the current // loadGInstr returns the correct MOV instruction for the current
@ -1577,25 +1571,25 @@ func (t *Thread) clearBreakpointState() {
} }
// SetCurrentBreakpoint will find and set the threads current breakpoint. // SetCurrentBreakpoint will find and set the threads current breakpoint.
func (thread *Thread) SetCurrentBreakpoint() error { func (t *Thread) SetCurrentBreakpoint() error {
thread.clearBreakpointState() t.clearBreakpointState()
regs, err := thread.Registers(false) regs, err := t.Registers(false)
if err != nil { if err != nil {
return err return err
} }
pc := regs.PC() pc := regs.PC()
if bp, ok := thread.p.FindBreakpoint(pc); ok { if bp, ok := t.p.FindBreakpoint(pc); ok {
if thread.regs.PC() != bp.Addr { if t.regs.PC() != bp.Addr {
if err := thread.SetPC(bp.Addr); err != nil { if err := t.SetPC(bp.Addr); err != nil {
return err return err
} }
} }
thread.CurrentBreakpoint = bp.CheckCondition(thread) t.CurrentBreakpoint = bp.CheckCondition(t)
if thread.CurrentBreakpoint.Breakpoint != nil && thread.CurrentBreakpoint.Active { if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
if g, err := proc.GetG(thread); err == nil { if g, err := proc.GetG(t); err == nil {
thread.CurrentBreakpoint.HitCount[g.ID]++ t.CurrentBreakpoint.HitCount[g.ID]++
} }
thread.CurrentBreakpoint.TotalHitCount++ t.CurrentBreakpoint.TotalHitCount++
} }
} }
return nil return nil

@ -68,6 +68,7 @@ type Info interface {
// ErrProcessExited or ProcessDetachedError). // ErrProcessExited or ProcessDetachedError).
Valid() (bool, error) Valid() (bool, error)
BinInfo() *BinaryInfo BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
// Common returns a struct with fields common to all backends // Common returns a struct with fields common to all backends
Common() *CommonProcess Common() *CommonProcess
@ -86,6 +87,7 @@ type ThreadInfo interface {
// GoroutineInfo is an interface for getting information on running goroutines. // GoroutineInfo is an interface for getting information on running goroutines.
type GoroutineInfo interface { type GoroutineInfo interface {
SelectedGoroutine() *G SelectedGoroutine() *G
SetSelectedGoroutine(*G)
} }
// ProcessManipulation is an interface for changing the execution state of a process. // ProcessManipulation is an interface for changing the execution state of a process.

@ -75,7 +75,9 @@ func (dbp *Process) detach(kill bool) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func (dbp *Process) entryPoint() (uint64, error) { // EntryPoint returns the entry point for the process,
// useful for PIEs.
func (dbp *Process) EntryPoint() (uint64, error) {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
@ -85,17 +87,17 @@ func (t *Thread) Blocked() bool {
} }
// SetPC sets the value of the PC register. // SetPC sets the value of the PC register.
func (thread *Thread) SetPC(pc uint64) error { func (t *Thread) SetPC(pc uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
// SetSP sets the value of the SP register. // SetSP sets the value of the SP register.
func (thread *Thread) SetSP(sp uint64) error { func (t *Thread) SetSP(sp uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
// SetDX sets the value of the DX register. // SetDX sets the value of the DX register.
func (thread *Thread) SetDX(dx uint64) error { func (t *Thread) SetDX(dx uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
@ -126,3 +128,5 @@ func (t *Thread) restoreRegisters(sr proc.Registers) error {
func (t *Thread) Stopped() bool { func (t *Thread) Stopped() bool {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func initialize(dbp *Process) error { return nil }

@ -13,6 +13,7 @@ import (
// is holding onto regarding the process we are debugging. // is holding onto regarding the process we are debugging.
type Process struct { type Process struct {
bi *proc.BinaryInfo bi *proc.BinaryInfo
pid int // Process Pid pid int // Process Pid
// Breakpoint table, holds information on breakpoints. // Breakpoint table, holds information on breakpoints.
@ -184,32 +185,6 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
return &dbp.breakpoints return &dbp.breakpoints
} }
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string, debugInfoDirs []string) error {
var wg sync.WaitGroup
path = findExecutable(path, dbp.pid)
entryPoint, err := dbp.entryPoint()
if err != nil {
return err
}
wg.Add(1)
go dbp.loadProcessInformation(&wg)
err = dbp.bi.LoadBinaryInfo(path, entryPoint, debugInfoDirs, &wg)
wg.Wait()
if err == nil {
err = dbp.bi.LoadError()
}
return err
}
// RequestManualStop sets the `halt` flag and // RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads. // sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error { func (dbp *Process) RequestManualStop() error {
@ -379,26 +354,23 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
return nil, false return nil, false
} }
// Returns a new Process struct. // initialize will ensure that all relevant information is loaded
func initializeDebugProcess(dbp *Process, path string, debugInfoDirs []string) (*Process, error) { // so the process is ready to be debugged.
err := dbp.LoadInformation(path, debugInfoDirs) func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
if err != nil { if err := initialize(dbp); err != nil {
return dbp, err return err
} }
if err := dbp.updateThreadList(); err != nil { if err := dbp.updateThreadList(); err != nil {
return dbp, err return err
}
return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint)
} }
// selectedGoroutine can not be set correctly by the call to updateThreadList // SetSelectedGoroutine will set internally the goroutine that should be
// because without calling SetGStructOffset we can not read the G struct of currentThread // the default for any command executed, the goroutine being actively
// but without calling updateThreadList we can not examine memory to determine // followed.
// the offset of g struct inside TLS func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread) dbp.selectedGoroutine = g
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
return dbp, nil
} }
// ClearInternalBreakpoints will clear all non-user set breakpoints. These // ClearInternalBreakpoints will clear all non-user set breakpoints. These

@ -13,7 +13,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sync"
"unsafe" "unsafe"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
@ -119,7 +118,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
} }
dbp.os.initialized = true dbp.os.initialized = true
dbp, err = initializeDebugProcess(dbp, argv0Go, []string{}) err = dbp.initialize(argv0Go, []string{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -155,7 +154,7 @@ func Attach(pid int, _ []string) (*Process, error) {
return nil, err return nil, err
} }
dbp, err = initializeDebugProcess(dbp, "", []string{}) err = dbp.initialize("", []string{})
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
@ -383,10 +382,6 @@ func (dbp *Process) waitForStop() ([]int, error) {
} }
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
var status sys.WaitStatus var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil) wpid, err := sys.Wait4(pid, &status, options, nil)
@ -464,7 +459,9 @@ func (dbp *Process) detach(kill bool) error {
return PtraceDetach(dbp.pid, 0) return PtraceDetach(dbp.pid, 0)
} }
func (dbp *Process) entryPoint() (uint64, error) { func (dbp *Process) EntryPoint() (uint64, error) {
//TODO(aarzilli): implement this //TODO(aarzilli): implement this
return 0, nil return 0, nil
} }
func initialize(dbp *Process) error { return nil }

@ -12,7 +12,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
@ -20,7 +19,8 @@ import (
"github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil" "github.com/derekparker/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty"
isatty "github.com/mattn/go-isatty"
) )
// Process statuses // Process statuses
@ -90,7 +90,10 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
return initializeDebugProcess(dbp, process.Path, debugInfoDirs) if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err
}
return dbp, nil
} }
// Attach to an existing process with the given PID. Once attached, if // Attach to an existing process with the given PID. Once attached, if
@ -110,7 +113,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
return nil, err return nil, err
} }
dbp, err = initializeDebugProcess(dbp, "", debugInfoDirs) err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
@ -118,6 +121,33 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
return dbp, nil return dbp, nil
} }
func initialize(dbp *Process) error {
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
if err == nil {
// removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n"))
}
if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
if err != nil {
return fmt.Errorf("could not read proc stat: %v", err)
}
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
rexp, err := regexp.Compile(expr)
if err != nil {
return fmt.Errorf("regexp compile error: %v", err)
}
match := rexp.FindSubmatch(stat)
if match == nil {
return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
}
comm = match[1]
}
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
return nil
}
// kill kills the target process. // kill kills the target process.
func (dbp *Process) kill() (err error) { func (dbp *Process) kill() (err error) {
if dbp.exited { if dbp.exited {
@ -299,35 +329,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
} }
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { func (dbp *Process) loadProcessInformation() {
defer wg.Done()
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
if err == nil {
// removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n"))
}
if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
if err != nil {
fmt.Printf("Could not read proc stat: %v\n", err)
os.Exit(1)
}
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
rexp, err := regexp.Compile(expr)
if err != nil {
fmt.Printf("Regexp compile error: %v\n", err)
os.Exit(1)
}
match := rexp.FindSubmatch(stat)
if match == nil {
fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.pid)
os.Exit(1)
}
comm = match[1]
}
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
} }
func status(pid int, comm string) rune { func status(pid int, comm string) rune {
@ -483,7 +485,9 @@ func (dbp *Process) detach(kill bool) error {
return nil return nil
} }
func (dbp *Process) entryPoint() (uint64, error) { // EntryPoint will return the process entry point address, useful for
// debugging PIEs.
func (dbp *Process) EntryPoint() (uint64, error) {
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid)) auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
if err != nil { if err != nil {
return 0, fmt.Errorf("could not read auxiliary vector: %v", err) return 0, fmt.Errorf("could not read auxiliary vector: %v", err)

@ -7,7 +7,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
@ -77,11 +76,14 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
dbp.pid = p.Pid dbp.pid = p.Pid
dbp.childProcess = true dbp.childProcess = true
return newDebugProcess(dbp, argv0Go) if err = dbp.initialize(argv0Go, []string{}); err != nil {
dbp.Detach(true)
return nil, err
}
return dbp, nil
} }
// newDebugProcess prepares process pid for debugging. func initialize(dbp *Process) error {
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the // It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows // call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event // will always fire a CREATE_PROCESS_DEBUG_EVENT event
@ -93,29 +95,25 @@ func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
}) })
if err != nil { if err != nil {
return nil, err return err
} }
if tid == 0 { if tid == 0 {
dbp.postExit() dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} return proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
} }
// Suspend all threads so that the call to _ContinueDebugEvent will // Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target. // not resume the target.
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
_, err := _SuspendThread(thread.os.hThread) _, err := _SuspendThread(thread.os.hThread)
if err != nil { if err != nil {
return nil, err return err
} }
} }
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
}) })
if err != nil { return err
return nil, err
}
return initializeDebugProcess(dbp, exepath, []string{})
} }
// findExePath searches for process pid, and returns its executable path. // findExePath searches for process pid, and returns its executable path.
@ -163,11 +161,9 @@ func Attach(pid int, _ []string) (*Process, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbp, err := newDebugProcess(New(pid), exepath) dbp := New(pid)
if err != nil { if err = dbp.initialize(exepath, []string{}); err != nil {
if dbp != nil { dbp.Detach(true)
dbp.Detach(false)
}
return nil, err return nil, err
} }
return dbp, nil return dbp, nil
@ -392,10 +388,6 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
return th, nil return th, nil
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
return 0, nil, fmt.Errorf("not implemented: wait") return 0, nil, fmt.Errorf("not implemented: wait")
} }
@ -488,7 +480,7 @@ func (dbp *Process) detach(kill bool) error {
return _DebugActiveProcessStop(uint32(dbp.pid)) return _DebugActiveProcessStop(uint32(dbp.pid))
} }
func (dbp *Process) entryPoint() (uint64, error) { func (dbp *Process) EntryPoint() (uint64, error) {
return dbp.os.entryPoint, nil return dbp.os.entryPoint, nil
} }

@ -41,6 +41,38 @@ func (pe ProcessDetachedError) Error() string {
return "detached from the process" return "detached from the process"
} }
// PostInitializationSetup handles all of the initialization procedures
// that must happen after Delve creates or attaches to a process.
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
entryPoint, err := p.EntryPoint()
if err != nil {
return err
}
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
if err == nil {
err = p.BinInfo().LoadError()
}
if err != nil {
return err
}
g, _ := GetG(p.CurrentThread())
p.SetSelectedGoroutine(g)
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
if err == nil {
bp, err := p.Breakpoints().SetWithID(-1, panicpc, writeBreakpoint)
if err == nil {
bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
return nil
}
// FindFileLocation returns the PC for a given file:line. // FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normalized to lower case and '/' on Windows. // Assumes that `file` is normalized to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) { func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
@ -648,15 +680,15 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
return s return s
} }
// CreateUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint. // createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
// This function is meant to be called by implementations of the Process interface. // This function is meant to be called by implementations of the Process interface.
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) { func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0) panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound { if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0) panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
} }
if err == nil { if err == nil {
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint) bp, err := p.Breakpoints().SetWithID(-1, panicpc, writeBreakpoint)
if err == nil { if err == nil {
bp.Name = UnrecoveredPanic bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"} bp.Variables = []string{"runtime.curg._panic.arg"}