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")
if testBackend == "" {
testBackend = "native"
if runtime.GOOS == "darwin" {
testBackend = "lldb"
}
}
}
os.Exit(m.Run())
@ -85,8 +88,10 @@ func TestBuild(t *testing.T) {
scan := bufio.NewScanner(stderr)
// wait for the debugger to start
scan.Scan()
t.Log(scan.Text())
go func() {
for scan.Scan() {
t.Log(scan.Text())
// 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'.
// 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, entryPoint uint64, debugInfoDirs []string, wg *sync.WaitGroup) error {
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string) error {
fi, err := os.Stat(path)
if err == nil {
bi.lastModified = fi.ModTime()
}
var wg sync.WaitGroup
defer wg.Wait()
bi.Path = path
switch bi.GOOS {
case "linux":
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, wg)
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg)
case "windows":
return bi.LoadBinaryInfoPE(path, entryPoint, wg)
return bi.LoadBinaryInfoPE(path, entryPoint, &wg)
case "darwin":
return bi.LoadBinaryInfoMacho(path, entryPoint, wg)
return bi.LoadBinaryInfoMacho(path, entryPoint, &wg)
}
return errors.New("unsupported operating system")
}

@ -221,13 +221,16 @@ func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
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
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
// function, call proc.Process.SetBreakpoint instead, this function exists
// 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 {
// We can overlap one internal breakpoint with one user breakpoint, we
// 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.
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)
if err == nil {
bp.ID = id

@ -5,7 +5,6 @@ import (
"fmt"
"go/ast"
"io"
"sync"
"github.com/derekparker/delve/pkg/proc"
)
@ -193,26 +192,42 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error
return nil, err
}
var wg sync.WaitGroup
err = p.bi.LoadBinaryInfo(exePath, p.entryPoint, debugInfoDirs, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
}
if err != nil {
if err := p.initialize(exePath, debugInfoDirs); err != nil {
return nil, err
}
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
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.
func (p *Process) BinInfo() *proc.BinaryInfo {
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.
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{})
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")
t.Errorf("read core_pattern: %q, %v", pat, err)
apport, err := ioutil.ReadFile("/var/log/apport.log")

@ -75,7 +75,6 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/arch/x86/x86asm"
@ -83,7 +82,7 @@ import (
"github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty"
isatty "github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
)
@ -130,7 +129,7 @@ type Process struct {
common proc.CommonProcess
}
// Thread is a thread.
// Thread represents an operating system thread.
type Thread struct {
ID int
strID string
@ -142,8 +141,7 @@ type Thread struct {
}
// ErrBackendUnavailable is returned when the stub program can not be found.
type ErrBackendUnavailable struct {
}
type ErrBackendUnavailable struct{}
func (err *ErrBackendUnavailable) Error() string {
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.
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
p.conn.conn = conn
p.conn.pid = pid
err := p.conn.handshake()
if err != nil {
@ -270,55 +267,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
}
}
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 = 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()
if err := p.initialize(path, debugInfoDirs); err != nil {
return err
}
@ -335,36 +284,6 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
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
}
@ -523,7 +442,6 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error)
process.Stdout = os.Stdout
process.Stderr = os.Stderr
process.SysProcAttr = sysProcAttr(false)
err := process.Start()
@ -545,10 +463,80 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error)
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
// 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)
if err != nil {
return 0, "", err
@ -659,7 +647,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
// resume all threads
var threadID string
var sig uint8 = 0
var sig uint8
var tu = threadUpdater{p: p}
var err error
continueLoop:
@ -720,7 +708,7 @@ continueLoop:
for _, thread := range p.threads {
if thread.strID == threadID {
var err error = nil
var err error
switch sig {
case 0x91:
err = errors.New("bad access")
@ -742,6 +730,13 @@ continueLoop:
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.
func (p *Process) StepInstruction() error {
thread := p.currentThread
@ -1318,7 +1313,6 @@ func (t *Thread) Blocked() bool {
default:
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
}
return false
}
// 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.
func (thread *Thread) SetCurrentBreakpoint() error {
thread.clearBreakpointState()
regs, err := thread.Registers(false)
func (t *Thread) SetCurrentBreakpoint() error {
t.clearBreakpointState()
regs, err := t.Registers(false)
if err != nil {
return err
}
pc := regs.PC()
if bp, ok := thread.p.FindBreakpoint(pc); ok {
if thread.regs.PC() != bp.Addr {
if err := thread.SetPC(bp.Addr); err != nil {
if bp, ok := t.p.FindBreakpoint(pc); ok {
if t.regs.PC() != bp.Addr {
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

@ -68,6 +68,7 @@ type Info interface {
// ErrProcessExited or ProcessDetachedError).
Valid() (bool, error)
BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
// Common returns a struct with fields common to all backends
Common() *CommonProcess
@ -86,6 +87,7 @@ type ThreadInfo interface {
// GoroutineInfo is an interface for getting information on running goroutines.
type GoroutineInfo interface {
SelectedGoroutine() *G
SetSelectedGoroutine(*G)
}
// 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)
}
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)
}
@ -85,17 +87,17 @@ func (t *Thread) Blocked() bool {
}
// SetPC sets the value of the PC register.
func (thread *Thread) SetPC(pc uint64) error {
func (t *Thread) SetPC(pc uint64) error {
panic(ErrNativeBackendDisabled)
}
// SetSP sets the value of the SP register.
func (thread *Thread) SetSP(sp uint64) error {
func (t *Thread) SetSP(sp uint64) error {
panic(ErrNativeBackendDisabled)
}
// SetDX sets the value of the DX register.
func (thread *Thread) SetDX(dx uint64) error {
func (t *Thread) SetDX(dx uint64) error {
panic(ErrNativeBackendDisabled)
}
@ -126,3 +128,5 @@ func (t *Thread) restoreRegisters(sr proc.Registers) error {
func (t *Thread) Stopped() bool {
panic(ErrNativeBackendDisabled)
}
func initialize(dbp *Process) error { return nil }

@ -12,7 +12,8 @@ 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.
@ -184,32 +185,6 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
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
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
@ -379,26 +354,23 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
return nil, false
}
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string, debugInfoDirs []string) (*Process, error) {
err := dbp.LoadInformation(path, debugInfoDirs)
if err != nil {
return dbp, err
// initialize will ensure that all relevant information is loaded
// so the process is ready to be debugged.
func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
if err := initialize(dbp); err != nil {
return err
}
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
// because without calling SetGStructOffset we can not read the G struct of currentThread
// but without calling updateThreadList we can not examine memory to determine
// the offset of g struct inside TLS
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
return dbp, nil
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
dbp.selectedGoroutine = g
}
// ClearInternalBreakpoints will clear all non-user set breakpoints. These

@ -13,7 +13,6 @@ import (
"os"
"os/exec"
"path/filepath"
"sync"
"unsafe"
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, err = initializeDebugProcess(dbp, argv0Go, []string{})
err = dbp.initialize(argv0Go, []string{})
if err != nil {
return nil, err
}
@ -155,7 +154,7 @@ func Attach(pid int, _ []string) (*Process, error) {
return nil, err
}
dbp, err = initializeDebugProcess(dbp, "", []string{})
err = dbp.initialize("", []string{})
if err != nil {
dbp.Detach(false)
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) {
var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil)
@ -464,7 +459,9 @@ func (dbp *Process) detach(kill bool) error {
return PtraceDetach(dbp.pid, 0)
}
func (dbp *Process) entryPoint() (uint64, error) {
func (dbp *Process) EntryPoint() (uint64, error) {
//TODO(aarzilli): implement this
return 0, nil
}
func initialize(dbp *Process) error { return nil }

@ -12,7 +12,6 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"syscall"
"time"
@ -20,7 +19,8 @@ import (
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty"
isatty "github.com/mattn/go-isatty"
)
// Process statuses
@ -90,7 +90,10 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err != nil {
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
@ -110,7 +113,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
return nil, err
}
dbp, err = initializeDebugProcess(dbp, "", debugInfoDirs)
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
if err != nil {
dbp.Detach(false)
return nil, err
@ -118,6 +121,33 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
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.
func (dbp *Process) kill() (err error) {
if dbp.exited {
@ -299,35 +329,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
}
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
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 (dbp *Process) loadProcessInformation() {
}
func status(pid int, comm string) rune {
@ -483,7 +485,9 @@ func (dbp *Process) detach(kill bool) error {
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))
if err != nil {
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)

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

@ -41,6 +41,38 @@ func (pe ProcessDetachedError) Error() string {
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.
// Assumes that `file` is normalized to lower case and '/' on Windows.
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
}
// 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.
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) {
func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
}
if err == nil {
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint)
bp, err := p.Breakpoints().SetWithID(-1, panicpc, writeBreakpoint)
if err == nil {
bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}