From d61cd1c0d7cda9d96995df0dca1b6559df78857c Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Mon, 12 Nov 2018 14:52:13 -0800 Subject: [PATCH] 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). --- cmd/dlv/dlv_test.go | 5 + pkg/proc/bininfo.go | 10 +- pkg/proc/breakpoints.go | 9 +- pkg/proc/core/core.go | 35 +++-- pkg/proc/core/core_test.go | 2 +- pkg/proc/gdbserial/gdbserver.go | 198 ++++++++++++++--------------- pkg/proc/interface.go | 2 + pkg/proc/native/nonative_darwin.go | 12 +- pkg/proc/native/proc.go | 58 +++------ pkg/proc/native/proc_darwin.go | 13 +- pkg/proc/native/proc_linux.go | 72 ++++++----- pkg/proc/native/proc_windows.go | 36 ++---- pkg/proc/proc.go | 38 +++++- 13 files changed, 256 insertions(+), 234 deletions(-) diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 67fee926..7dc83b85 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -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 } }() diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 9b8dea1d..d6c9e316 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -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") } diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index ca64d2a2..3eb09bbc 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -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 diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 1446fe4c..85ef19d7 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -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, "" } diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index d1a4dce4..0ecbd0ab 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -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") diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 2bbd0f55..becbd582 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -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 diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 7b7f13e5..9a4245a5 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -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. diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go index b01a2d93..51926596 100644 --- a/pkg/proc/native/nonative_darwin.go +++ b/pkg/proc/native/nonative_darwin.go @@ -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 } diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index e56fd01b..1a0ea012 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -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 diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index d90b87b6..80859e49 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -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 } diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 93a874ed..7928edbe 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -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) diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 222bd307..901bcc2b 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -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 } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index e9dd7a95..d809ba8f 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -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"}