]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.8] runtime: print user stack on other threads during GOTRACBEACK...
authorAustin Clements <austin@google.com>
Fri, 10 Mar 2017 15:59:39 +0000 (10:59 -0500)
committerAustin Clements <austin@google.com>
Wed, 5 Apr 2017 16:58:26 +0000 (16:58 +0000)
Currently, when printing tracebacks of other threads during
GOTRACEBACK=crash, if the thread is on the system stack we print only
the header for the user goroutine and fail to print its stack. This
happens because we passed the g0 to traceback instead of curg. The g0
never has anything set in its gobuf, so traceback doesn't print
anything.

Fix this by passing _g_.m.curg to traceback instead of the g0.

Fixes #19494.
Fixes #19637 (backport).

Change-Id: Idfabf94d6a725e9cdf94a3923dead6455ef3b217
Reviewed-on: https://go-review.googlesource.com/39600
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/runtime/crash_unix_test.go
src/runtime/export_test.go
src/runtime/signal_sighandler.go

index 97deed8b9d97ec44a7937ef64f5ae6a7828d4d18..182c84b6392c661534049b574ccb7efa4487197f 100644 (file)
@@ -9,6 +9,7 @@ package runtime_test
 import (
        "bytes"
        "internal/testenv"
+       "io"
        "io/ioutil"
        "os"
        "os/exec"
@@ -153,6 +154,78 @@ func loop(i int, c chan bool) {
 }
 `
 
+func TestPanicSystemstack(t *testing.T) {
+       // Test that GOTRACEBACK=crash prints both the system and user
+       // stack of other threads.
+
+       // The GOTRACEBACK=crash handler takes 0.1 seconds even if
+       // it's not writing a core file and potentially much longer if
+       // it is. Skip in short mode.
+       if testing.Short() {
+               t.Skip("Skipping in short mode (GOTRACEBACK=crash is slow)")
+       }
+
+       t.Parallel()
+       cmd := exec.Command(os.Args[0], "testPanicSystemstackInternal")
+       cmd = testEnv(cmd)
+       cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
+       pr, pw, err := os.Pipe()
+       if err != nil {
+               t.Fatal("creating pipe: ", err)
+       }
+       cmd.Stderr = pw
+       if err := cmd.Start(); err != nil {
+               t.Fatal("starting command: ", err)
+       }
+       defer cmd.Process.Wait()
+       defer cmd.Process.Kill()
+       if err := pw.Close(); err != nil {
+               t.Log("closing write pipe: ", err)
+       }
+       defer pr.Close()
+
+       // Wait for "x\nx\n" to indicate readiness.
+       buf := make([]byte, 4)
+       _, err = io.ReadFull(pr, buf)
+       if err != nil || string(buf) != "x\nx\n" {
+               t.Fatal("subprocess failed; output:\n", string(buf))
+       }
+
+       // Send SIGQUIT.
+       if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
+               t.Fatal("signaling subprocess: ", err)
+       }
+
+       // Get traceback.
+       tb, err := ioutil.ReadAll(pr)
+       if err != nil {
+               t.Fatal("reading traceback from pipe: ", err)
+       }
+
+       // Traceback should have two testPanicSystemstackInternal's
+       // and two blockOnSystemStackInternal's.
+       if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 {
+               t.Fatal("traceback missing user stack:\n", string(tb))
+       } else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 {
+               t.Fatal("traceback missing system stack:\n", string(tb))
+       }
+}
+
+func init() {
+       if len(os.Args) >= 2 && os.Args[1] == "testPanicSystemstackInternal" {
+               // Get two threads running on the system stack with
+               // something recognizable in the stack trace.
+               runtime.GOMAXPROCS(2)
+               go testPanicSystemstackInternal()
+               testPanicSystemstackInternal()
+       }
+}
+
+func testPanicSystemstackInternal() {
+       runtime.BlockOnSystemStack()
+       os.Exit(1) // Should be unreachable.
+}
+
 func TestSignalExitStatus(t *testing.T) {
        testenv.MustHaveGoBuild(t)
        exe, err := buildTestProg(t, "testprog")
index 9b765550ca2e3b806e54b0f16b4aedf1acb87c4e..3b33c8422b9aef13d92550152cdc6647d68c07b5 100644 (file)
@@ -245,3 +245,16 @@ func CountPagesInUse() (pagesInUse, counted uintptr) {
 
        return
 }
+
+// BlockOnSystemStack switches to the system stack, prints "x\n" to
+// stderr, and blocks in a stack containing
+// "runtime.blockOnSystemStackInternal".
+func BlockOnSystemStack() {
+       systemstack(blockOnSystemStackInternal)
+}
+
+func blockOnSystemStackInternal() {
+       print("x\n")
+       lock(&deadlock)
+       lock(&deadlock)
+}
index 5af12d7b2fedb8c42aa4c2349f26e07ce6c95cb6..758e42f6ee9d706e6c5176111cdeb0c36a281e98 100644 (file)
@@ -101,7 +101,7 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
                if crashing > 0 && gp != _g_.m.curg && _g_.m.curg != nil && readgstatus(_g_.m.curg)&^_Gscan == _Grunning {
                        // tracebackothers on original m skipped this one; trace it now.
                        goroutineheader(_g_.m.curg)
-                       traceback(^uintptr(0), ^uintptr(0), 0, gp)
+                       traceback(^uintptr(0), ^uintptr(0), 0, _g_.m.curg)
                } else if crashing == 0 {
                        tracebackothers(gp)
                        print("\n")