mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 04:35:19 +08:00
Two bugfixes regarding stale executable files, and executables changing between restarts (#689)
* service/debugger: Restore breakpoints using file:line on restart Restoring by address can cause the breakpoint to be inserted in the middle of an instruction if the executable file has changed. * terminal: Warn of stale executable when printing source
This commit is contained in:
committed by
Derek Parker
parent
8f0646e426
commit
f4aaffbbf3
12
proc/proc.go
12
proc/proc.go
@ -14,6 +14,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/dwarf/frame"
|
"github.com/derekparker/delve/dwarf/frame"
|
||||||
"github.com/derekparker/delve/dwarf/line"
|
"github.com/derekparker/delve/dwarf/line"
|
||||||
@ -24,8 +25,9 @@ import (
|
|||||||
// Process represents all of the information the debugger
|
// Process represents all of the information the debugger
|
||||||
// is holding onto regarding the process we are debugging.
|
// is holding onto regarding the process we are debugging.
|
||||||
type Process struct {
|
type Process struct {
|
||||||
Pid int // Process Pid
|
Pid int // Process Pid
|
||||||
Process *os.Process // Pointer to process struct for the actual process we are debugging
|
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.
|
// Breakpoint table, holds information on breakpoints.
|
||||||
// Maps instruction address to Breakpoint struct.
|
// Maps instruction address to Breakpoint struct.
|
||||||
@ -157,10 +159,14 @@ func (dbp *Process) Running() bool {
|
|||||||
func (dbp *Process) LoadInformation(path string) error {
|
func (dbp *Process) LoadInformation(path string) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
exe, err := dbp.findExecutable(path)
|
exe, path, err := dbp.findExecutable(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
dbp.LastModified = fi.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
wg.Add(5)
|
wg.Add(5)
|
||||||
go dbp.loadProcessInformation(&wg)
|
go dbp.loadProcessInformation(&wg)
|
||||||
|
|||||||
@ -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")
|
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 == "" {
|
if path == "" {
|
||||||
path = C.GoString(C.find_executable(C.int(dbp.Pid)))
|
path = C.GoString(C.find_executable(C.int(dbp.Pid)))
|
||||||
}
|
}
|
||||||
exe, err := macho.Open(path)
|
exe, err := macho.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, path, err
|
||||||
}
|
}
|
||||||
if exe.Cpu != macho.CpuAmd64 {
|
if exe.Cpu != macho.CpuAmd64 {
|
||||||
return nil, UnsupportedArchErr
|
return nil, path, UnsupportedArchErr
|
||||||
}
|
}
|
||||||
dbp.dwarf, err = exe.DWARF()
|
dbp.dwarf, err = exe.DWARF()
|
||||||
if err != nil {
|
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) {
|
func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||||
|
|||||||
@ -173,26 +173,26 @@ func (dbp *Process) updateThreadList() error {
|
|||||||
|
|
||||||
var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported")
|
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 == "" {
|
if path == "" {
|
||||||
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
||||||
}
|
}
|
||||||
f, err := os.OpenFile(path, 0, os.ModePerm)
|
f, err := os.OpenFile(path, 0, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, path, err
|
||||||
}
|
}
|
||||||
elfFile, err := elf.NewFile(f)
|
elfFile, err := elf.NewFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, path, err
|
||||||
}
|
}
|
||||||
if elfFile.Machine != elf.EM_X86_64 {
|
if elfFile.Machine != elf.EM_X86_64 {
|
||||||
return nil, UnsupportedArchErr
|
return nil, path, UnsupportedArchErr
|
||||||
}
|
}
|
||||||
dbp.dwarf, err = elfFile.DWARF()
|
dbp.dwarf, err = elfFile.DWARF()
|
||||||
if err != nil {
|
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) {
|
func (dbp *Process) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) {
|
||||||
|
|||||||
@ -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")
|
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)
|
peFile, err := openExecutablePath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, path, err
|
||||||
}
|
}
|
||||||
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
|
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
|
||||||
return nil, UnsupportedArchErr
|
return nil, path, UnsupportedArchErr
|
||||||
}
|
}
|
||||||
dbp.dwarf, err = dwarfFromPE(peFile)
|
dbp.dwarf, err = dwarfFromPE(peFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, path, err
|
||||||
}
|
}
|
||||||
return peFile, nil
|
return peFile, path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openExecutablePath(path string) (*pe.File, error) {
|
func openExecutablePath(path string) (*pe.File, error) {
|
||||||
|
|||||||
@ -313,3 +313,8 @@ func (regs Registers) String() string {
|
|||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscardedBreakpoint struct {
|
||||||
|
Breakpoint *Breakpoint
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,11 +12,14 @@ type Client interface {
|
|||||||
// Returns the pid of the process we are debugging.
|
// Returns the pid of the process we are debugging.
|
||||||
ProcessPid() int
|
ProcessPid() int
|
||||||
|
|
||||||
|
// LastModified returns the time that the process' executable was modified.
|
||||||
|
LastModified() time.Time
|
||||||
|
|
||||||
// Detach detaches the debugger, optionally killing the process.
|
// Detach detaches the debugger, optionally killing the process.
|
||||||
Detach(killProcess bool) error
|
Detach(killProcess bool) error
|
||||||
|
|
||||||
// Restarts program.
|
// Restarts program.
|
||||||
Restart() error
|
Restart() ([]api.DiscardedBreakpoint, error)
|
||||||
|
|
||||||
// GetState returns the current debugger state.
|
// GetState returns the current debugger state.
|
||||||
GetState() (*api.DebuggerState, error)
|
GetState() (*api.DebuggerState, error)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/proc"
|
"github.com/derekparker/delve/proc"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
@ -81,6 +82,12 @@ func (d *Debugger) ProcessPid() int {
|
|||||||
return d.process.Pid
|
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.
|
// Detach detaches from the target process.
|
||||||
// If `kill` is true we will kill the process after
|
// If `kill` is true we will kill the process after
|
||||||
// detaching.
|
// detaching.
|
||||||
@ -100,7 +107,7 @@ func (d *Debugger) detach(kill bool) error {
|
|||||||
|
|
||||||
// Restart will restart the target process, first killing
|
// Restart will restart the target process, first killing
|
||||||
// and then exec'ing it again.
|
// and then exec'ing it again.
|
||||||
func (d *Debugger) Restart() error {
|
func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
|
||||||
d.processMutex.Lock()
|
d.processMutex.Lock()
|
||||||
defer d.processMutex.Unlock()
|
defer d.processMutex.Unlock()
|
||||||
|
|
||||||
@ -110,30 +117,38 @@ func (d *Debugger) Restart() error {
|
|||||||
}
|
}
|
||||||
// Ensure the process is in a PTRACE_STOP.
|
// Ensure the process is in a PTRACE_STOP.
|
||||||
if err := stopProcess(d.ProcessPid()); err != nil {
|
if err := stopProcess(d.ProcessPid()); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := d.detach(true); err != nil {
|
if err := d.detach(true); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir)
|
p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir)
|
||||||
if err != nil {
|
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() {
|
for _, oldBp := range d.breakpoints() {
|
||||||
if oldBp.ID < 0 {
|
if oldBp.ID < 0 {
|
||||||
continue
|
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)
|
newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.process = p
|
d.process = p
|
||||||
return nil
|
return discarded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// State returns the current state of the debugger.
|
// State returns the current state of the debugger.
|
||||||
|
|||||||
@ -36,7 +36,8 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
|
|||||||
if s.config.AttachPid != 0 {
|
if s.config.AttachPid != 0 {
|
||||||
return errors.New("cannot restart process Delve did not create")
|
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 {
|
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"net/rpc/jsonrpc"
|
"net/rpc/jsonrpc"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
@ -37,14 +38,21 @@ func (c *RPCClient) ProcessPid() int {
|
|||||||
return out.Pid
|
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 {
|
func (c *RPCClient) Detach(kill bool) error {
|
||||||
out := new(DetachOut)
|
out := new(DetachOut)
|
||||||
return c.call("Detach", DetachIn{kill}, out)
|
return c.call("Detach", DetachIn{kill}, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) Restart() error {
|
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) {
|
||||||
out := new(RestartOut)
|
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) {
|
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package rpc2
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
@ -33,6 +34,18 @@ func (s *RPCServer) ProcessPid(arg ProcessPidIn, out *ProcessPidOut) error {
|
|||||||
return nil
|
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 {
|
type DetachIn struct {
|
||||||
Kill bool
|
Kill bool
|
||||||
}
|
}
|
||||||
@ -49,6 +62,7 @@ type RestartIn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RestartOut struct {
|
type RestartOut struct {
|
||||||
|
DiscardedBreakpoints []api.DiscardedBreakpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart restarts program.
|
// Restart restarts program.
|
||||||
@ -56,7 +70,9 @@ func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error {
|
|||||||
if s.config.AttachPid != 0 {
|
if s.config.AttachPid != 0 {
|
||||||
return errors.New("cannot restart process Delve did not create")
|
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 {
|
type StateIn struct {
|
||||||
|
|||||||
@ -71,7 +71,7 @@ func TestRestart_afterExit(t *testing.T) {
|
|||||||
if !state.Exited {
|
if !state.Exited {
|
||||||
t.Fatal("expected initial process to have exited")
|
t.Fatal("expected initial process to have exited")
|
||||||
}
|
}
|
||||||
if err := c.Restart(); err != nil {
|
if _, err := c.Restart(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if c.ProcessPid() == origPid {
|
if c.ProcessPid() == origPid {
|
||||||
@ -124,7 +124,7 @@ func TestRestart_duringStop(t *testing.T) {
|
|||||||
if state.CurrentThread.Breakpoint == nil {
|
if state.CurrentThread.Breakpoint == nil {
|
||||||
t.Fatal("did not hit breakpoint")
|
t.Fatal("did not hit breakpoint")
|
||||||
}
|
}
|
||||||
if err := c.Restart(); err != nil {
|
if _, err := c.Restart(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if c.ProcessPid() == origPid {
|
if c.ProcessPid() == origPid {
|
||||||
|
|||||||
@ -568,10 +568,14 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func restart(t *Term, ctx callContext, args string) error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("Process restarted with PID", t.client.ProcessPid())
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,6 +1270,12 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
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)
|
buf := bufio.NewScanner(file)
|
||||||
l := line
|
l := line
|
||||||
for i := 1; i < l-5; i++ {
|
for i := 1; i < l-5; i++ {
|
||||||
|
|||||||
Reference in New Issue
Block a user