diff --git a/proctl/proctl.go b/proctl/proctl.go index da2e4be8..46d96610 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -185,12 +185,14 @@ func (dbp *DebuggedProcess) FindLocation(str string) (uint64, error) { // Sends out a request that the debugged process halt // execution. Sends SIGSTOP to all threads. -func (dbp *DebuggedProcess) RequestManualStop() { +func (dbp *DebuggedProcess) RequestManualStop() error { dbp.halt = true - for _, th := range dbp.Threads { - th.Halt() + err := dbp.requestManualStop() + if err != nil { + return err } dbp.running = false + return nil } // Sets a breakpoint at addr, and stores it in the process wide @@ -331,10 +333,8 @@ func (dbp *DebuggedProcess) resume() error { if err != nil { return err } - if dbp.CurrentBreakpoint != nil { - if !dbp.CurrentBreakpoint.Temp { - return dbp.Halt() - } + if dbp.CurrentBreakpoint != nil || dbp.halt { + return dbp.Halt() } // Check to see if we hit a runtime.breakpoint fn := dbp.goSymTable.PCToFunc(pc) @@ -407,6 +407,15 @@ func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) { return allg, nil } +func (dbp *DebuggedProcess) Halt() (err error) { + for _, th := range dbp.Threads { + if err := th.Halt(); err != nil { + return err + } + } + return nil +} + // Obtains register values from what Delve considers to be the current // thread of the traced process. func (dbp *DebuggedProcess) Registers() (Registers, error) { diff --git a/proctl/proctl_darwin.c b/proctl/proctl_darwin.c index 0f4456a2..a5861c28 100644 --- a/proctl/proctl_darwin.c +++ b/proctl/proctl_darwin.c @@ -158,3 +158,8 @@ mach_port_wait(mach_port_t port_set) { return thread; } + +kern_return_t +raise_exception(mach_port_t task, mach_port_t thread, mach_port_t exception_port, exception_type_t exception) { + return exception_raise(exception_port, thread, task, exception, 0, 0); +} diff --git a/proctl/proctl_darwin.go b/proctl/proctl_darwin.go index 881d55b7..1cc8f994 100644 --- a/proctl/proctl_darwin.go +++ b/proctl/proctl_darwin.go @@ -22,12 +22,15 @@ type OSProcessDetails struct { notificationPort C.mach_port_t } -func (dbp *DebuggedProcess) Halt() (err error) { - for _, th := range dbp.Threads { - err := th.Halt() - if err != nil { - return err - } +func (dbp *DebuggedProcess) requestManualStop() (err error) { + var ( + task = C.mach_port_t(dbp.os.task) + thread = C.mach_port_t(dbp.CurrentThread.os.thread_act) + exceptionPort = C.mach_port_t(dbp.os.exceptionPort) + ) + kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT) + if kret != C.KERN_SUCCESS { + return fmt.Errorf("could not raise mach exception") } return nil } diff --git a/proctl/proctl_darwin.h b/proctl/proctl_darwin.h index 9345393c..a92965a1 100644 --- a/proctl/proctl_darwin.h +++ b/proctl/proctl_darwin.h @@ -37,3 +37,7 @@ thread_count(task_t task); mach_port_t mach_port_wait(mach_port_t); + +kern_return_t +raise_exception(mach_port_t, mach_port_t, mach_port_t, exception_type_t); + diff --git a/proctl/proctl_linux.go b/proctl/proctl_linux.go index e3503f41..18107ced 100644 --- a/proctl/proctl_linux.go +++ b/proctl/proctl_linux.go @@ -25,14 +25,8 @@ const ( // Not actually needed for Linux. type OSProcessDetails interface{} -func (dbp *DebuggedProcess) Halt() (err error) { - for _, th := range dbp.Threads { - err := th.Halt() - if err != nil { - return err - } - } - return nil +func (dbp *DebuggedProcess) requestManualStop() (err error) { + return sys.Kill(dbp.Pid, sys.SIGSTOP) } // Attach to a newly created thread, and store that thread in our list of diff --git a/proctl/proctl_test.go b/proctl/proctl_test.go index 8414dcfc..1a187d52 100644 --- a/proctl/proctl_test.go +++ b/proctl/proctl_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "testing" + "time" ) func withTestProcess(name string, t *testing.T, fn func(p *DebuggedProcess)) { @@ -87,6 +88,31 @@ func TestExit(t *testing.T) { }) } +func TestHalt(t *testing.T) { + withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { + go func() { + time.Sleep(10 * time.Millisecond) + err := p.RequestManualStop() + if err != nil { + t.Fatal(err) + } + }() + err := p.Continue() + if err != nil { + t.Fatal(err) + } + // Loop through threads and make sure they are all + // actually stopped, err will not be nil if the process + // is still running. + for _, th := range p.Threads { + _, err := th.Registers() + if err != nil { + t.Error(err) + } + } + }) +} + func TestStep(t *testing.T) { withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { helloworldfunc := p.goSymTable.LookupFunc("main.helloworld")