diff --git a/proc/proc.go b/proc/proc.go index 29d10e62acee7bd49d2f13a315f0ee0e19e686df..d835dfb79fd2d8553e6b1cf50a0de831898a3994 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -229,6 +229,9 @@ func (dbp *Process) CurrentLocation() (*Location, error) { // RequestManualStop sets the `halt` flag and // sends SIGSTOP to all threads. func (dbp *Process) RequestManualStop() error { + if dbp.exited { + return &ProcessExitedError{} + } dbp.halt = true return dbp.requestManualStop() } @@ -237,6 +240,9 @@ func (dbp *Process) RequestManualStop() error { // break point table. Setting a break point must be thread specific due to // ptrace actions needing the thread to be in a signal-delivery-stop. func (dbp *Process) SetBreakpoint(addr uint64) (*Breakpoint, error) { + if dbp.exited { + return nil, &ProcessExitedError{} + } return dbp.setBreakpoint(dbp.CurrentThread.ID, addr, false) } @@ -247,6 +253,9 @@ func (dbp *Process) SetTempBreakpoint(addr uint64) (*Breakpoint, error) { // ClearBreakpoint clears the breakpoint at addr. func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) { + if dbp.exited { + return nil, &ProcessExitedError{} + } bp, ok := dbp.FindBreakpoint(addr) if !ok { return nil, NoBreakpointError{addr: addr} @@ -268,6 +277,9 @@ func (dbp *Process) Status() *WaitStatus { // Next continues execution until the next source line. func (dbp *Process) Next() (err error) { + if dbp.exited { + return &ProcessExitedError{} + } for i := range dbp.Breakpoints { if dbp.Breakpoints[i].Temp { return fmt.Errorf("next while nexting") @@ -357,6 +369,9 @@ func (dbp *Process) setChanRecvBreakpoints() (int, error) { // process. It will continue until it hits a breakpoint // or is otherwise stopped. func (dbp *Process) Continue() error { + if dbp.exited { + return &ProcessExitedError{} + } for { if err := dbp.resume(); err != nil { return err @@ -498,6 +513,9 @@ func (dbp *Process) StepInstruction() (err error) { // SwitchThread changes from current thread to the thread specified by `tid`. func (dbp *Process) SwitchThread(tid int) error { + if dbp.exited { + return &ProcessExitedError{} + } if th, ok := dbp.Threads[tid]; ok { dbp.CurrentThread = th dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG() @@ -509,6 +527,9 @@ func (dbp *Process) SwitchThread(tid int) error { // SwitchGoroutine changes from current thread to the thread // running the specified goroutine. func (dbp *Process) SwitchGoroutine(gid int) error { + if dbp.exited { + return &ProcessExitedError{} + } g, err := dbp.FindGoroutine(gid) if err != nil { return err @@ -527,6 +548,9 @@ func (dbp *Process) SwitchGoroutine(gid int) error { // GoroutinesInfo returns an array of G structures representing the information // Delve cares about from the internal runtime G structure. func (dbp *Process) GoroutinesInfo() ([]*G, error) { + if dbp.exited { + return nil, &ProcessExitedError{} + } if dbp.allGCache != nil { return dbp.allGCache, nil } @@ -597,6 +621,9 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { // Halt stops all threads. func (dbp *Process) Halt() (err error) { + if dbp.exited { + return &ProcessExitedError{} + } for _, th := range dbp.Threads { if err := th.Halt(); err != nil { return err @@ -817,6 +844,9 @@ func (dbp *Process) FindGoroutine(gid int) (*G, error) { // ConvertEvalScope returns a new EvalScope in the context of the // specified goroutine ID and stack frame. func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) { + if dbp.exited { + return nil, &ProcessExitedError{} + } g, err := dbp.FindGoroutine(gid) if err != nil { return nil, err diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index b423ea9647f77f7fb54e18c7c62c065e1d35778f..2a2d522789d7d1398904df6e91579cc0e0b1167d 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -213,22 +213,29 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint { } // Threads returns the threads of the target process. -func (d *Debugger) Threads() []*api.Thread { +func (d *Debugger) Threads() ([]*api.Thread, error) { + if d.process.Exited() { + return nil, &proc.ProcessExitedError{} + } threads := []*api.Thread{} for _, th := range d.process.Threads { threads = append(threads, api.ConvertThread(th)) } - return threads + return threads, nil } // FindThread returns the thread for the given 'id'. -func (d *Debugger) FindThread(id int) *api.Thread { - for _, thread := range d.Threads() { +func (d *Debugger) FindThread(id int) (*api.Thread, error) { + threads, err := d.Threads() + if err != nil { + return nil, err + } + for _, thread := range threads { if thread.ID == id { - return thread + return thread, nil } } - return nil + return nil, nil } // Command handles commands which control the debugger lifecycle diff --git a/service/rpc/server.go b/service/rpc/server.go index 7084880a6dece123aca1b4d71dc1a31a599ffcd8..b6d01209dd819c2198c6411b2c3bbfe5a20b6e1d 100644 --- a/service/rpc/server.go +++ b/service/rpc/server.go @@ -162,13 +162,16 @@ func (s *RPCServer) ClearBreakpoint(id int, breakpoint *api.Breakpoint) error { return nil } -func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) error { - *threads = s.debugger.Threads() - return nil +func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) (err error) { + *threads, err = s.debugger.Threads() + return err } func (s *RPCServer) GetThread(id int, thread *api.Thread) error { - t := s.debugger.FindThread(id) + t, err := s.debugger.FindThread(id) + if err != nil { + return err + } if t == nil { return fmt.Errorf("no thread with id %d", id) } @@ -201,7 +204,11 @@ type ThreadListArgs struct { } func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api.Variable) error { - if thread := s.debugger.FindThread(args.Id); thread == nil { + thread, err := s.debugger.FindThread(args.Id) + if err != nil { + return err + } + if thread == nil { return fmt.Errorf("no thread with id %d", args.Id) } diff --git a/service/test/integration_test.go b/service/test/integration_test.go index 58e79e754ede45beb95dcf5d5c8f450f4ffc16bb..0187faef3faeeb47553a9c79b86aa23f1131e29e 100644 --- a/service/test/integration_test.go +++ b/service/test/integration_test.go @@ -28,6 +28,14 @@ func assertNoError(err error, t *testing.T, s string) { } } +func assertError(err error, t *testing.T, s string) { + if err == nil { + _, file, line, _ := runtime.Caller(1) + fname := filepath.Base(file) + t.Fatalf("failed assertion at %s:%d: %s (no error)\n", fname, line, s) + } +} + func TestMain(m *testing.M) { os.Exit(protest.RunTestsWithFixtures(m)) } @@ -734,3 +742,59 @@ func TestClientServer_FullStacktrace(t *testing.T) { } }) } + +func TestIssue355(t *testing.T) { + // After the target process has terminated should return an error but not crash + withTestClient("continuetestprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) + assertNoError(err, t, "CreateBreakpoint()") + ch := c.Continue() + state := <-ch + tid := state.CurrentThread.ID + gid := state.SelectedGoroutine.ID + assertNoError(state.Err, t, "First Continue()") + ch = c.Continue() + state = <-ch + if !state.Exited { + t.Fatalf("Target did not terminate after second continue") + } + + ch = c.Continue() + state = <-ch + assertError(state.Err, t, "Continue()") + + _, err = c.Next() + assertError(err, t, "Next()") + _, err = c.Step() + assertError(err, t, "Step()") + _, err = c.StepInstruction() + assertError(err, t, "StepInstruction()") + _, err = c.SwitchThread(tid) + assertError(err, t, "SwitchThread()") + _, err = c.SwitchGoroutine(gid) + assertError(err, t, "SwitchGoroutine()") + _, err = c.Halt() + assertError(err, t, "Halt()") + _, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1}) + assertError(err, t, "CreateBreakpoint()") + _, err = c.ClearBreakpoint(bp.ID) + assertError(err, t, "ClearBreakpoint()") + _, err = c.ListThreads() + assertError(err, t, "ListThreads()") + _, err = c.GetThread(tid) + assertError(err, t, "GetThread()") + assertError(c.SetVariable(api.EvalScope{gid, 0}, "a", "10"), t, "SetVariable()") + _, err = c.ListLocalVariables(api.EvalScope{gid, 0}) + assertError(err, t, "ListLocalVariables()") + _, err = c.ListFunctionArgs(api.EvalScope{gid, 0}) + assertError(err, t, "ListFunctionArgs()") + _, err = c.ListRegisters() + assertError(err, t, "ListRegisters()") + _, err = c.ListGoroutines() + assertError(err, t, "ListGoroutines()") + _, err = c.Stacktrace(gid, 10, false) + assertError(err, t, "Stacktrace()") + _, err = c.FindLocation(api.EvalScope{gid, 0}, "+1") + assertError(err, t, "FindLocation()") + }) +}