diff --git a/_fixtures/testnextnethttp.go b/_fixtures/testnextnethttp.go index afd5b7ab..ac95d882 100644 --- a/_fixtures/testnextnethttp.go +++ b/_fixtures/testnextnethttp.go @@ -12,5 +12,13 @@ func main() { header := w.Header().Get("Content-Type") w.Write([]byte(msg + header)) }) - http.ListenAndServe(":9191", nil) + http.HandleFunc("/nobp", func(w http.ResponseWriter, req *http.Request) { + msg := "hello, world!" + header := w.Header().Get("Content-Type") + w.Write([]byte(msg + header)) + }) + err := http.ListenAndServe(":9191", nil) + if err != nil { + panic(err) + } } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index de9aa639..c9199bcc 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -132,7 +132,7 @@ func (dbp *Process) Detach(kill bool) (err error) { } } dbp.execPtraceFunc(func() { - err = PtraceDetach(dbp.pid, 0) + err = dbp.detach() if err != nil { return } diff --git a/pkg/proc/proc_darwin.go b/pkg/proc/proc_darwin.go index 2a713008..5882e94b 100644 --- a/pkg/proc/proc_darwin.go +++ b/pkg/proc/proc_darwin.go @@ -509,3 +509,7 @@ func (dbp *Process) resume() error { } return nil } + +func (dbp *Process) detach() error { + return PtraceDetach(dbp.pid, 0) +} diff --git a/pkg/proc/proc_linux.go b/pkg/proc/proc_linux.go index 4969f0f9..f1dd8cd7 100644 --- a/pkg/proc/proc_linux.go +++ b/pkg/proc/proc_linux.go @@ -484,6 +484,24 @@ func (dbp *Process) resume() error { return nil } +func (dbp *Process) detach() error { + for threadID := range dbp.threads { + err := PtraceDetach(threadID, 0) + if err != nil { + return err + } + } + // For some reason the process will sometimes enter stopped state after a + // detach, this doesn't happen immediately either. + // We have to wait a bit here, then check if the main thread is stopped and + // SIGCONT it if it is. + time.Sleep(50 * time.Millisecond) + if s := status(dbp.pid, dbp.os.comm); s == 'T' { + sys.Kill(dbp.pid, sys.SIGCONT) + } + return nil +} + func killProcess(pid int) error { return sys.Kill(pid, sys.SIGINT) } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 82ffdcd5..313d5e1b 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/constant" "go/token" + "io/ioutil" "net" "net/http" "os" @@ -2581,3 +2582,55 @@ func TestStacktraceWithBarriers(t *testing.T) { } }) } + +func TestAttachDetach(t *testing.T) { + if runtime.GOOS == "darwin" { + // does not work on darwin + return + } + fixture := protest.BuildFixture("testnextnethttp") + cmd := exec.Command(fixture.Path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + assertNoError(cmd.Start(), t, "starting fixture") + + // wait for testnextnethttp to start listening + t0 := time.Now() + for { + conn, err := net.Dial("tcp", "localhost:9191") + if err == nil { + conn.Close() + break + } + time.Sleep(50 * time.Millisecond) + if time.Since(t0) > 10*time.Second { + t.Fatal("fixture did not start") + } + } + + p, err := Attach(cmd.Process.Pid) + assertNoError(err, t, "Attach") + go func() { + time.Sleep(1 * time.Second) + http.Get("http://localhost:9191") + }() + + assertNoError(p.Continue(), t, "Continue") + + f, ln := currentLineNumber(p, t) + if ln != 11 { + t.Fatalf("Expected line :11 got %s:%d", f, ln) + } + + assertNoError(p.Detach(false), t, "Detach") + + resp, err := http.Get("http://localhost:9191/nobp") + assertNoError(err, t, "Page request after detach") + bs, err := ioutil.ReadAll(resp.Body) + assertNoError(err, t, "Reading /nobp page") + if out := string(bs); strings.Index(out, "hello, world!") < 0 { + t.Fatalf("/nobp page does not contain \"hello, world!\": %q", out) + } + + cmd.Process.Kill() +} diff --git a/pkg/proc/proc_windows.go b/pkg/proc/proc_windows.go index d3b7773d..e3fff9f2 100644 --- a/pkg/proc/proc_windows.go +++ b/pkg/proc/proc_windows.go @@ -663,6 +663,16 @@ func (dbp *Process) resume() error { return nil } +func (dbp *Process) detach() error { + for _, thread := range dbp.threads { + _, err := _ResumeThread(thread.os.hThread) + if err != nil { + return err + } + } + return PtraceDetach(dbp.pid, 0) +} + func killProcess(pid int) error { p, err := os.FindProcess(pid) if err != nil {