proc: Do not panic when a command is executed on an exited process
Fixes #355
This commit is contained in:
parent
fb7210934d
commit
3a8730de72
30
proc/proc.go
30
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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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()")
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user