diff --git a/client/cli/cli.go b/client/cli/cli.go index b34c97d9fb10a6c5e22e3202f61870298bc5bedc..859ed53b81a1a5ca6046c496549e534c28991778 100644 --- a/client/cli/cli.go +++ b/client/cli/cli.go @@ -87,9 +87,14 @@ func Run(run bool, pid int, args []string) { } cmd := cmds.Find(cmdstr) - err = cmd(dbp, args...) - if err != nil { - fmt.Fprintf(os.Stderr, "Command failed: %s\n", err) + if err := cmd(dbp, args...); err != nil { + switch err.(type) { + case proctl.ProcessExitedError: + pe := err.(proctl.ProcessExitedError) + fmt.Fprintf(os.Stderr, "Process exited with status %d\n", pe.Status) + default: + fmt.Fprintf(os.Stderr, "Command failed: %s\n", err) + } } } } diff --git a/proctl/proctl.go b/proctl/proctl.go index c209fddd8f5b8f03195640bd7fa6a45df120e1d5..0c2372c046eb59c42181a904b45c6c1bdf1000e6 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -45,6 +45,17 @@ func (mse ManualStopError) Error() string { return "Manual stop requested" } +// ProcessExitedError indicates that the process has exited and contains both +// process id and exit status. +type ProcessExitedError struct { + Pid int + Status int +} + +func (pe ProcessExitedError) Error() string { + return fmt.Sprintf("process %d has exited with status %d", pe.Pid, pe.Status) +} + // Attach to an existing process with the given PID. func Attach(pid int) (*DebuggedProcess, error) { dbp, err := newDebugProcess(pid, true) @@ -238,7 +249,7 @@ func (dbp *DebuggedProcess) Continue() error { } fn := func() error { - wpid, _, err := trapWait(dbp, -1) + wpid, err := trapWait(dbp, -1) if err != nil { return err } @@ -380,11 +391,3 @@ func (dbp *DebuggedProcess) run(fn func() error) error { } return nil } - -type ProcessExitedError struct { - pid int -} - -func (pe ProcessExitedError) Error() string { - return fmt.Sprintf("process %d has exited", pe.pid) -} diff --git a/proctl/proctl_darwin.go b/proctl/proctl_darwin.go index 7667920a4012c3beda2549ca46c93a1000fd4e56..18d8ac1e0e452bae3e59077de083f020a864c413 100644 --- a/proctl/proctl_darwin.go +++ b/proctl/proctl_darwin.go @@ -172,14 +172,14 @@ func (dbp *DebuggedProcess) findExecutable() (*macho.File, error) { return macho.Open(C.GoString(pathptr)) } -func trapWait(dbp *DebuggedProcess, pid int) (int, *sys.WaitStatus, error) { +func trapWait(dbp *DebuggedProcess, pid int) (int, error) { port := C.mach_port_wait(dbp.os.exceptionPort) if port == 0 { - return -1, nil, ProcessExitedError{} + return -1, ProcessExitedError{Pid: dbp.Pid} } dbp.updateThreadList() - return int(port), nil, nil + return int(port), nil } func wait(pid, options int) (int, *sys.WaitStatus, error) { diff --git a/proctl/proctl_linux.go b/proctl/proctl_linux.go index cb1b0e552674862f27c7da0d0379ae09203c224d..70d65485cdf5d8241bfc41ee7050c56a16f1343e 100644 --- a/proctl/proctl_linux.go +++ b/proctl/proctl_linux.go @@ -223,11 +223,11 @@ func stopped(pid int) bool { return false } -func trapWait(dbp *DebuggedProcess, pid int) (int, *sys.WaitStatus, error) { +func trapWait(dbp *DebuggedProcess, pid int) (int, error) { for { wpid, status, err := wait(pid, 0) if err != nil { - return -1, nil, fmt.Errorf("wait err %s %d", err, pid) + return -1, fmt.Errorf("wait err %s %d", err, pid) } if wpid == 0 { continue @@ -235,38 +235,39 @@ func trapWait(dbp *DebuggedProcess, pid int) (int, *sys.WaitStatus, error) { if th, ok := dbp.Threads[wpid]; ok { th.Status = status } + if status.Exited() && wpid == dbp.Pid { - return -1, status, ProcessExitedError{wpid} + return -1, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()} } if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { // A traced thread has cloned a new thread, grab the pid and // add it to our list of traced threads. cloned, err := sys.PtraceGetEventMsg(wpid) if err != nil { - return -1, nil, fmt.Errorf("could not get event message: %s", err) + return -1, fmt.Errorf("could not get event message: %s", err) } th, err := dbp.addThread(int(cloned), false) if err != nil { - return -1, nil, err + return -1, err } err = th.Continue() if err != nil { - return -1, nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) + return -1, fmt.Errorf("could not continue new thread %d %s", cloned, err) } err = dbp.Threads[int(wpid)].Continue() if err != nil { - return -1, nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) + return -1, fmt.Errorf("could not continue new thread %d %s", cloned, err) } continue } if status.StopSignal() == sys.SIGTRAP { - return wpid, status, nil + return wpid, nil } if status.StopSignal() == sys.SIGSTOP && dbp.halt { - return -1, nil, ManualStopError{} + return -1, ManualStopError{} } } } diff --git a/proctl/proctl_test.go b/proctl/proctl_test.go index 948ea22a4070c94cd262d4a301d45f7f9d3140a4..ef88323acd5bcaf142060cb25540129d83b69bb1 100644 --- a/proctl/proctl_test.go +++ b/proctl/proctl_test.go @@ -71,6 +71,22 @@ func currentLineNumber(p *DebuggedProcess, t *testing.T) (string, int) { return f, l } +func TestExit(t *testing.T) { + withTestProcess("../_fixtures/continuetestprog", t, func(p *DebuggedProcess) { + err := p.Continue() + pe, ok := err.(ProcessExitedError) + if !ok { + t.Fatalf("Continue() returned unexpected error type") + } + if pe.Status != 0 { + t.Errorf("Unexpected error status: %d", pe.Status) + } + if pe.Pid != p.Pid { + t.Errorf("Unexpected process id: %d", pe.Pid) + } + }) +} + func TestStep(t *testing.T) { withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { helloworldfunc := p.GoSymTable.LookupFunc("main.helloworld") diff --git a/proctl/threads.go b/proctl/threads.go index 77d35491bae9475ff932e677e66a04cc67eac815..88c8f23efb6948905d5848fe1852a0fccd463ba7 100644 --- a/proctl/threads.go +++ b/proctl/threads.go @@ -35,7 +35,7 @@ type Registers interface { func (thread *ThreadContext) Registers() (Registers, error) { regs, err := registers(thread) if err != nil { - return nil, fmt.Errorf("could not get registers %s", err) + return nil, fmt.Errorf("could not get registers: %s", err) } return regs, nil } @@ -70,13 +70,13 @@ func (thread *ThreadContext) PrintInfo() error { // we step over any breakpoints. It will restore the instruction, // step, and then restore the breakpoint and continue. func (thread *ThreadContext) Continue() error { - // Check whether we are stopped at a breakpoint, and - // if so, single step over it before continuing. regs, err := thread.Registers() if err != nil { - return fmt.Errorf("could not get registers %s", err) + return err } + // Check whether we are stopped at a breakpoint, and + // if so, single step over it before continuing. if _, ok := thread.Process.BreakPoints[regs.PC()-1]; ok { err := thread.Step() if err != nil { @@ -195,7 +195,7 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame return err } // Wait on -1, just in case scheduler switches threads for this G. - wpid, _, err := trapWait(thread.Process, -1) + wpid, err := trapWait(thread.Process, -1) if err != nil { return err }