diff --git a/proc/proc.go b/proc/proc.go index 67886ee7..c059adea 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/line" @@ -24,8 +25,9 @@ import ( // Process represents all of the information the debugger // is holding onto regarding the process we are debugging. type Process struct { - Pid int // Process Pid - Process *os.Process // Pointer to process struct for the actual process we are debugging + Pid int // Process Pid + Process *os.Process // Pointer to process struct for the actual process we are debugging + LastModified time.Time // Time the executable of this process was last modified // Breakpoint table, holds information on breakpoints. // Maps instruction address to Breakpoint struct. @@ -157,10 +159,14 @@ func (dbp *Process) Running() bool { func (dbp *Process) LoadInformation(path string) error { var wg sync.WaitGroup - exe, err := dbp.findExecutable(path) + exe, path, err := dbp.findExecutable(path) if err != nil { return err } + fi, err := os.Stat(path) + if err == nil { + dbp.LastModified = fi.ModTime() + } wg.Add(5) go dbp.loadProcessInformation(&wg) diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index 927d7e4b..50b39685 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -336,22 +336,22 @@ func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) { var UnsupportedArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported") -func (dbp *Process) findExecutable(path string) (*macho.File, error) { +func (dbp *Process) findExecutable(path string) (*macho.File, string, error) { if path == "" { path = C.GoString(C.find_executable(C.int(dbp.Pid))) } exe, err := macho.Open(path) if err != nil { - return nil, err + return nil, path, err } if exe.Cpu != macho.CpuAmd64 { - return nil, UnsupportedArchErr + return nil, path, UnsupportedArchErr } dbp.dwarf, err = exe.DWARF() if err != nil { - return nil, err + return nil, path, err } - return exe, nil + return exe, path, nil } func (dbp *Process) trapWait(pid int) (*Thread, error) { diff --git a/proc/proc_linux.go b/proc/proc_linux.go index a886585b..7b9df75a 100644 --- a/proc/proc_linux.go +++ b/proc/proc_linux.go @@ -173,26 +173,26 @@ func (dbp *Process) updateThreadList() error { var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") -func (dbp *Process) findExecutable(path string) (*elf.File, error) { +func (dbp *Process) findExecutable(path string) (*elf.File, string, error) { if path == "" { path = fmt.Sprintf("/proc/%d/exe", dbp.Pid) } f, err := os.OpenFile(path, 0, os.ModePerm) if err != nil { - return nil, err + return nil, path, err } elfFile, err := elf.NewFile(f) if err != nil { - return nil, err + return nil, path, err } if elfFile.Machine != elf.EM_X86_64 { - return nil, UnsupportedArchErr + return nil, path, UnsupportedArchErr } dbp.dwarf, err = elfFile.DWARF() if err != nil { - return nil, err + return nil, path, err } - return elfFile, nil + return elfFile, path, nil } func (dbp *Process) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) { diff --git a/proc/proc_windows.go b/proc/proc_windows.go index 1323fcbf..aa5518e8 100644 --- a/proc/proc_windows.go +++ b/proc/proc_windows.go @@ -390,19 +390,19 @@ func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) { var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") -func (dbp *Process) findExecutable(path string) (*pe.File, error) { +func (dbp *Process) findExecutable(path string) (*pe.File, string, error) { peFile, err := openExecutablePath(path) if err != nil { - return nil, err + return nil, path, err } if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { - return nil, UnsupportedArchErr + return nil, path, UnsupportedArchErr } dbp.dwarf, err = dwarfFromPE(peFile) if err != nil { - return nil, err + return nil, path, err } - return peFile, nil + return peFile, path, nil } func openExecutablePath(path string) (*pe.File, error) { diff --git a/service/api/types.go b/service/api/types.go index 733cb37c..87ca4a3a 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -313,3 +313,8 @@ func (regs Registers) String() string { } return buf.String() } + +type DiscardedBreakpoint struct { + Breakpoint *Breakpoint + Reason error +} diff --git a/service/client.go b/service/client.go index e121fdca..fe06a944 100644 --- a/service/client.go +++ b/service/client.go @@ -1,6 +1,8 @@ package service import ( + "time" + "github.com/derekparker/delve/service/api" ) @@ -10,11 +12,14 @@ type Client interface { // Returns the pid of the process we are debugging. ProcessPid() int + // LastModified returns the time that the process' executable was modified. + LastModified() time.Time + // Detach detaches the debugger, optionally killing the process. Detach(killProcess bool) error // Restarts program. - Restart() error + Restart() ([]api.DiscardedBreakpoint, error) // GetState returns the current debugger state. GetState() (*api.DebuggerState, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 028bd05a..1992d6b7 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -11,6 +11,7 @@ import ( "runtime" "strings" "sync" + "time" "github.com/derekparker/delve/proc" "github.com/derekparker/delve/service/api" @@ -81,6 +82,12 @@ func (d *Debugger) ProcessPid() int { return d.process.Pid } +// LastModified returns the time that the process' executable was last +// modified. +func (d *Debugger) LastModified() time.Time { + return d.process.LastModified +} + // Detach detaches from the target process. // If `kill` is true we will kill the process after // detaching. @@ -100,7 +107,7 @@ func (d *Debugger) detach(kill bool) error { // Restart will restart the target process, first killing // and then exec'ing it again. -func (d *Debugger) Restart() error { +func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -110,30 +117,38 @@ func (d *Debugger) Restart() error { } // Ensure the process is in a PTRACE_STOP. if err := stopProcess(d.ProcessPid()); err != nil { - return err + return nil, err } if err := d.detach(true); err != nil { - return err + return nil, err } } p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { - return fmt.Errorf("could not launch process: %s", err) + return nil, fmt.Errorf("could not launch process: %s", err) } + discarded := []api.DiscardedBreakpoint{} for _, oldBp := range d.breakpoints() { if oldBp.ID < 0 { continue } + if len(oldBp.File) > 0 { + oldBp.Addr, err = d.process.FindFileLocation(oldBp.File, oldBp.Line) + if err != nil { + discarded = append(discarded, api.DiscardedBreakpoint{oldBp, err}) + continue + } + } newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil) if err != nil { - return err + return nil, err } if err := copyBreakpointInfo(newBp, oldBp); err != nil { - return err + return nil, err } } d.process = p - return nil + return discarded, nil } // State returns the current state of the debugger. diff --git a/service/rpc1/server.go b/service/rpc1/server.go index f2e497bf..af233beb 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -36,7 +36,8 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error { if s.config.AttachPid != 0 { return errors.New("cannot restart process Delve did not create") } - return s.debugger.Restart() + _, err := s.debugger.Restart() + return err } func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error { diff --git a/service/rpc2/client.go b/service/rpc2/client.go index b47fff56..1489044e 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -5,6 +5,7 @@ import ( "log" "net/rpc" "net/rpc/jsonrpc" + "time" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" @@ -37,14 +38,21 @@ func (c *RPCClient) ProcessPid() int { return out.Pid } +func (c *RPCClient) LastModified() time.Time { + out := new(LastModifiedOut) + c.call("LastModified", LastModifiedIn{}, out) + return out.Time +} + func (c *RPCClient) Detach(kill bool) error { out := new(DetachOut) return c.call("Detach", DetachIn{kill}, out) } -func (c *RPCClient) Restart() error { +func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) { out := new(RestartOut) - return c.call("Restart", RestartIn{}, out) + err := c.call("Restart", RestartIn{}, out) + return out.DiscardedBreakpoints, err } func (c *RPCClient) GetState() (*api.DebuggerState, error) { diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 841f4141..51c5954a 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -3,6 +3,7 @@ package rpc2 import ( "errors" "fmt" + "time" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" @@ -33,6 +34,18 @@ func (s *RPCServer) ProcessPid(arg ProcessPidIn, out *ProcessPidOut) error { return nil } +type LastModifiedIn struct { +} + +type LastModifiedOut struct { + Time time.Time +} + +func (s *RPCServer) LastModified(arg LastModifiedIn, out *LastModifiedOut) error { + out.Time = s.debugger.LastModified() + return nil +} + type DetachIn struct { Kill bool } @@ -49,6 +62,7 @@ type RestartIn struct { } type RestartOut struct { + DiscardedBreakpoints []api.DiscardedBreakpoint } // Restart restarts program. @@ -56,7 +70,9 @@ func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error { if s.config.AttachPid != 0 { return errors.New("cannot restart process Delve did not create") } - return s.debugger.Restart() + var err error + out.DiscardedBreakpoints, err = s.debugger.Restart() + return err } type StateIn struct { diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 4d287b00..00c26f46 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -71,7 +71,7 @@ func TestRestart_afterExit(t *testing.T) { if !state.Exited { t.Fatal("expected initial process to have exited") } - if err := c.Restart(); err != nil { + if _, err := c.Restart(); err != nil { t.Fatal(err) } if c.ProcessPid() == origPid { @@ -124,7 +124,7 @@ func TestRestart_duringStop(t *testing.T) { if state.CurrentThread.Breakpoint == nil { t.Fatal("did not hit breakpoint") } - if err := c.Restart(); err != nil { + if _, err := c.Restart(); err != nil { t.Fatal(err) } if c.ProcessPid() == origPid { diff --git a/terminal/command.go b/terminal/command.go index 342c671b..cb16e7d3 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -568,10 +568,14 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) { } func restart(t *Term, ctx callContext, args string) error { - if err := t.client.Restart(); err != nil { + discarded, err := t.client.Restart() + if err != nil { return err } fmt.Println("Process restarted with PID", t.client.ProcessPid()) + for i := range discarded { + fmt.Println("Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason) + } return nil } @@ -1266,6 +1270,12 @@ func printfile(t *Term, filename string, line int, showArrow bool) error { } defer file.Close() + fi, _ := file.Stat() + lastModExe := t.client.LastModified() + if fi.ModTime().After(lastModExe) { + fmt.Println("Warning: listing may not match stale executable") + } + buf := bufio.NewScanner(file) l := line for i := 1; i < l-5; i++ {