mirror of
https://github.com/go-delve/delve.git
synced 2025-10-29 09:46:56 +08:00
proc,terminal: Ensure correct exit status (#2543)
Ensure that any command executed after the process we are trying to debug prints a correct and consistent exit status. Previously the exit code was being lost after the first time we printed that a process has exited. Additionally, certain commands would print the PID of the process and other would not. This change makes everything more correct and consistent.
This commit is contained in:
@ -26,6 +26,10 @@ type Process interface {
|
|||||||
// the `proc` package.
|
// the `proc` package.
|
||||||
// This is temporary and in support of an ongoing refactor.
|
// This is temporary and in support of an ongoing refactor.
|
||||||
type ProcessInternal interface {
|
type ProcessInternal interface {
|
||||||
|
// Valid returns true if this Process can be used. When it returns false it
|
||||||
|
// also returns an error describing why the Process is invalid (either
|
||||||
|
// ErrProcessExited or ErrProcessDetached).
|
||||||
|
Valid() (bool, error)
|
||||||
// Restart restarts the recording from the specified position, or from the
|
// Restart restarts the recording from the specified position, or from the
|
||||||
// last checkpoint if pos == "".
|
// last checkpoint if pos == "".
|
||||||
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
|
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
|
||||||
@ -87,10 +91,6 @@ type Info interface {
|
|||||||
// ResumeNotify specifies a channel that will be closed the next time
|
// ResumeNotify specifies a channel that will be closed the next time
|
||||||
// ContinueOnce finishes resuming the target.
|
// ContinueOnce finishes resuming the target.
|
||||||
ResumeNotify(chan<- struct{})
|
ResumeNotify(chan<- struct{})
|
||||||
// Valid returns true if this Process can be used. When it returns false it
|
|
||||||
// also returns an error describing why the Process is invalid (either
|
|
||||||
// ErrProcessExited or ErrProcessDetached).
|
|
||||||
Valid() (bool, error)
|
|
||||||
BinInfo() *BinaryInfo
|
BinInfo() *BinaryInfo
|
||||||
EntryPoint() (uint64, error)
|
EntryPoint() (uint64, error)
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,10 @@ type Target struct {
|
|||||||
// This must be cleared whenever the target is resumed.
|
// This must be cleared whenever the target is resumed.
|
||||||
gcache goroutineCache
|
gcache goroutineCache
|
||||||
iscgo *bool
|
iscgo *bool
|
||||||
|
|
||||||
|
// exitStatus is the exit status of the process we are debugging.
|
||||||
|
// Saved here to relay to any future commands.
|
||||||
|
exitStatus int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrProcessExited indicates that the process has exited and contains both
|
// ErrProcessExited indicates that the process has exited and contains both
|
||||||
@ -203,6 +207,20 @@ func (t *Target) IsCgo() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns true if this Process can be used. When it returns false it
|
||||||
|
// also returns an error describing why the Process is invalid (either
|
||||||
|
// ErrProcessExited or ErrProcessDetached).
|
||||||
|
func (t *Target) Valid() (bool, error) {
|
||||||
|
ok, err := t.proc.Valid()
|
||||||
|
if !ok && err != nil {
|
||||||
|
if pe, ok := err.(ErrProcessExited); ok {
|
||||||
|
pe.Status = t.exitStatus
|
||||||
|
err = pe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok, err
|
||||||
|
}
|
||||||
|
|
||||||
// SupportsFunctionCalls returns whether or not the backend supports
|
// SupportsFunctionCalls returns whether or not the backend supports
|
||||||
// calling functions during a debug session.
|
// calling functions during a debug session.
|
||||||
// Currently only non-recorded processes running on AMD64 support
|
// Currently only non-recorded processes running on AMD64 support
|
||||||
|
|||||||
@ -83,6 +83,9 @@ func (dbp *Target) Continue() error {
|
|||||||
dbp.selectedGoroutine, _ = GetG(curth)
|
dbp.selectedGoroutine, _ = GetG(curth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pe, ok := err.(ErrProcessExited); ok {
|
||||||
|
dbp.exitStatus = pe.Status
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dbp.StopReason == StopLaunched {
|
if dbp.StopReason == StopLaunched {
|
||||||
|
|||||||
@ -1308,7 +1308,7 @@ func scopePrefixSwitch(t *Term, ctx callContext) error {
|
|||||||
|
|
||||||
func exitedToError(state *api.DebuggerState, err error) (*api.DebuggerState, error) {
|
func exitedToError(state *api.DebuggerState, err error) (*api.DebuggerState, error) {
|
||||||
if err == nil && state.Exited {
|
if err == nil && state.Exited {
|
||||||
return nil, fmt.Errorf("Process has exited with status %d", state.ExitStatus)
|
return nil, fmt.Errorf("Process %d has exited with status %d", state.Pid, state.ExitStatus)
|
||||||
}
|
}
|
||||||
return state, err
|
return state, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ var ErrNotExecutable = errors.New("not an executable file")
|
|||||||
|
|
||||||
// DebuggerState represents the current context of the debugger.
|
// DebuggerState represents the current context of the debugger.
|
||||||
type DebuggerState struct {
|
type DebuggerState struct {
|
||||||
|
// PID of the process we are debugging.
|
||||||
|
Pid int
|
||||||
// Running is true if the process is running and no other information can be collected.
|
// Running is true if the process is running and no other information can be collected.
|
||||||
Running bool
|
Running bool
|
||||||
// Recording is true if the process is currently being recorded and no other
|
// Recording is true if the process is currently being recorded and no other
|
||||||
|
|||||||
@ -1189,11 +1189,12 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitedErr, exited := err.(proc.ErrProcessExited); command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && exited {
|
if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
|
||||||
state := &api.DebuggerState{}
|
state := &api.DebuggerState{}
|
||||||
|
state.Pid = d.target.Pid()
|
||||||
state.Exited = true
|
state.Exited = true
|
||||||
state.ExitStatus = exitedErr.Status
|
state.ExitStatus = pe.Status
|
||||||
state.Err = errors.New(exitedErr.Error())
|
state.Err = pe
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -1773,6 +1773,23 @@ func TestClientServerConsistentExit(t *testing.T) {
|
|||||||
if state.ExitStatus != 2 {
|
if state.ExitStatus != 2 {
|
||||||
t.Fatalf("Process exit status is not 2, got: %v", state.ExitStatus)
|
t.Fatalf("Process exit status is not 2, got: %v", state.ExitStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure future commands also return the correct exit status.
|
||||||
|
// Previously there was a bug where the command which prompted the
|
||||||
|
// process to exit (continue, next, etc...) would return the corrent
|
||||||
|
// exit status but subsequent commands would return an incorrect exit
|
||||||
|
// status of 0. To test this we simply repeat the 'next' command and
|
||||||
|
// ensure we get the correct response again.
|
||||||
|
state, err = c.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !state.Exited {
|
||||||
|
t.Fatal("Second process state is not exited")
|
||||||
|
}
|
||||||
|
if state.ExitStatus != 2 {
|
||||||
|
t.Fatalf("Second process exit status is not 2, got: %v", state.ExitStatus)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user