From 684dc92ccd1775fcef90bca42110caf86615ff05 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Sat, 21 Mar 2015 17:31:53 -0500 Subject: [PATCH] Improve handling of process natural death (OS X) --- client/cli/cli.go | 66 ++++++++++++++++++++++------------------- proctl/proctl.go | 11 +++++++ proctl/proctl_darwin.c | 6 ++-- proctl/proctl_darwin.go | 15 ++++++++-- 4 files changed, 63 insertions(+), 35 deletions(-) diff --git a/client/cli/cli.go b/client/cli/cli.go index cff8bc8a..ab07acb7 100644 --- a/client/cli/cli.go +++ b/client/cli/cli.go @@ -105,11 +105,15 @@ func Run(args []string) { } cmdstr, args := parseCommand(cmdstr) - if cmdstr == "exit" { handleExit(dbp, t, 0) } + if dbp.Exited() && cmdstr != "help" { + fmt.Fprintf(os.Stderr, "Process has already exited.\n") + continue + } + cmd := cmds.Find(cmdstr) if err := cmd(dbp, args...); err != nil { switch err.(type) { @@ -132,39 +136,41 @@ func handleExit(dbp *proctl.DebuggedProcess, t *Term, status int) { f.Close() } - answer, err := t.line.Prompt("Would you like to kill the process? [y/n]") - if err != nil { - t.die(2, io.EOF) - } - answer = strings.TrimSuffix(answer, "\n") - - for _, bp := range dbp.HWBreakPoints { - if bp == nil { - continue + if !dbp.Exited() { + for _, bp := range dbp.HWBreakPoints { + if bp == nil { + continue + } + if _, err := dbp.Clear(bp.Addr); err != nil { + fmt.Printf("Can't clear breakpoint @%x: %s\n", bp.Addr, err) + } } - if _, err := dbp.Clear(bp.Addr); err != nil { - fmt.Printf("Can't clear breakpoint @%x: %s\n", bp.Addr, err) + + for pc := range dbp.BreakPoints { + if _, err := dbp.Clear(pc); err != nil { + fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err) + } } - } - for pc := range dbp.BreakPoints { - if _, err := dbp.Clear(pc); err != nil { - fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err) - } - } - - fmt.Println("Detaching from process...") - err = sys.PtraceDetach(dbp.Process.Pid) - if err != nil { - t.die(2, "Could not detach", err) - } - - if answer == "y" { - fmt.Println("Killing process", dbp.Process.Pid) - - err := dbp.Process.Kill() + answer, err := t.line.Prompt("Would you like to kill the process? [y/n]") if err != nil { - fmt.Println("Could not kill process", err) + t.die(2, io.EOF) + } + answer = strings.TrimSuffix(answer, "\n") + + fmt.Println("Detaching from process...") + err = sys.PtraceDetach(dbp.Process.Pid) + if err != nil { + t.die(2, "Could not detach", err) + } + + if answer == "y" { + fmt.Println("Killing process", dbp.Process.Pid) + + err := dbp.Process.Kill() + if err != nil { + fmt.Println("Could not kill process", err) + } } } diff --git a/proctl/proctl.go b/proctl/proctl.go index 6ec83531..03e7a3f9 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -35,6 +35,7 @@ type DebuggedProcess struct { breakpointIDCounter int running bool halt bool + exited bool } // A ManualStopError happens when the user triggers a @@ -87,6 +88,12 @@ func Launch(cmd []string) (*DebuggedProcess, error) { return newDebugProcess(proc.Process.Pid, false) } +// Returns whether or not Delve thinks the debugged +// process has exited. +func (dbp *DebuggedProcess) Exited() bool { + return dbp.exited +} + // Returns whether or not Delve thinks the debugged // process is currently executing. func (dbp *DebuggedProcess) Running() bool { @@ -242,6 +249,7 @@ func (dbp *DebuggedProcess) Continue() error { if err != nil { return err } + thread, ok := dbp.Threads[wpid] if !ok { return fmt.Errorf("could not find thread for %d", wpid) @@ -380,6 +388,9 @@ func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) { } func (dbp *DebuggedProcess) run(fn func() error) error { + if dbp.exited { + return fmt.Errorf("process has already exited") + } dbp.running = true dbp.halt = false defer func() { dbp.running = false }() diff --git a/proctl/proctl_darwin.c b/proctl/proctl_darwin.c index e91433a1..0c6e129c 100644 --- a/proctl/proctl_darwin.c +++ b/proctl/proctl_darwin.c @@ -122,14 +122,14 @@ mach_port_wait(mach_port_t port_set) { // Wait for mach msg. kret = mach_msg(&msg.hdr, MACH_RCV_MSG|MACH_RCV_INTERRUPT, 0, sizeof(msg.data), port_set, 0, MACH_PORT_NULL); - - if (kret == MACH_RCV_INTERRUPTED || kret != MACH_MSG_SUCCESS) return 0; + if (kret == MACH_RCV_INTERRUPTED) return kret; + if (kret != MACH_MSG_SUCCESS) return 0; mach_msg_body_t *bod = (mach_msg_body_t*)(&msg.hdr + 1); mach_msg_port_descriptor_t *desc = (mach_msg_port_descriptor_t *)(bod + 1); thread = desc[0].name; - // Exception + switch (msg.hdr.msgh_id) { case 2401: // Exception kret = thread_suspend(thread); diff --git a/proctl/proctl_darwin.go b/proctl/proctl_darwin.go index 116a6669..bf48ec68 100644 --- a/proctl/proctl_darwin.go +++ b/proctl/proctl_darwin.go @@ -177,20 +177,31 @@ func (dbp *DebuggedProcess) findExecutable() (*macho.File, error) { func trapWait(dbp *DebuggedProcess, pid int) (int, error) { port := C.mach_port_wait(dbp.os.portSet) + switch port { - case C.MACH_RCV_INTERRUPTED: - return -1, ManualStopError{} case dbp.os.notificationPort: _, status, err := wait(dbp.Pid, 0) if err != nil { return -1, err } + dbp.exited = true return -1, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()} + case C.MACH_RCV_INTERRUPTED: + if !dbp.halt { + // Call trapWait again, it seems + // MACH_RCV_INTERRUPTED is emitted before + // process natural death _sometimes_. + return trapWait(dbp, pid) + } + return -1, ManualStopError{} case 0: return -1, fmt.Errorf("error while waiting for task") } + // Since we cannot be notified of new threads on OS X + // this is as good a time as any to check for them. dbp.updateThreadList() + return int(port), nil }