Skip to content

Commit ff5695d

Browse files
committed
[release-branch.go1.8] runtime: print user stack on other threads during GOTRACBEACK=crash
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 <[email protected]> Reviewed-by: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 517a38c commit ff5695d

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

src/runtime/crash_unix_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package runtime_test
99
import (
1010
"bytes"
1111
"internal/testenv"
12+
"io"
1213
"io/ioutil"
1314
"os"
1415
"os/exec"
@@ -153,6 +154,78 @@ func loop(i int, c chan bool) {
153154
}
154155
`
155156

157+
func TestPanicSystemstack(t *testing.T) {
158+
// Test that GOTRACEBACK=crash prints both the system and user
159+
// stack of other threads.
160+
161+
// The GOTRACEBACK=crash handler takes 0.1 seconds even if
162+
// it's not writing a core file and potentially much longer if
163+
// it is. Skip in short mode.
164+
if testing.Short() {
165+
t.Skip("Skipping in short mode (GOTRACEBACK=crash is slow)")
166+
}
167+
168+
t.Parallel()
169+
cmd := exec.Command(os.Args[0], "testPanicSystemstackInternal")
170+
cmd = testEnv(cmd)
171+
cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
172+
pr, pw, err := os.Pipe()
173+
if err != nil {
174+
t.Fatal("creating pipe: ", err)
175+
}
176+
cmd.Stderr = pw
177+
if err := cmd.Start(); err != nil {
178+
t.Fatal("starting command: ", err)
179+
}
180+
defer cmd.Process.Wait()
181+
defer cmd.Process.Kill()
182+
if err := pw.Close(); err != nil {
183+
t.Log("closing write pipe: ", err)
184+
}
185+
defer pr.Close()
186+
187+
// Wait for "x\nx\n" to indicate readiness.
188+
buf := make([]byte, 4)
189+
_, err = io.ReadFull(pr, buf)
190+
if err != nil || string(buf) != "x\nx\n" {
191+
t.Fatal("subprocess failed; output:\n", string(buf))
192+
}
193+
194+
// Send SIGQUIT.
195+
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
196+
t.Fatal("signaling subprocess: ", err)
197+
}
198+
199+
// Get traceback.
200+
tb, err := ioutil.ReadAll(pr)
201+
if err != nil {
202+
t.Fatal("reading traceback from pipe: ", err)
203+
}
204+
205+
// Traceback should have two testPanicSystemstackInternal's
206+
// and two blockOnSystemStackInternal's.
207+
if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 {
208+
t.Fatal("traceback missing user stack:\n", string(tb))
209+
} else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 {
210+
t.Fatal("traceback missing system stack:\n", string(tb))
211+
}
212+
}
213+
214+
func init() {
215+
if len(os.Args) >= 2 && os.Args[1] == "testPanicSystemstackInternal" {
216+
// Get two threads running on the system stack with
217+
// something recognizable in the stack trace.
218+
runtime.GOMAXPROCS(2)
219+
go testPanicSystemstackInternal()
220+
testPanicSystemstackInternal()
221+
}
222+
}
223+
224+
func testPanicSystemstackInternal() {
225+
runtime.BlockOnSystemStack()
226+
os.Exit(1) // Should be unreachable.
227+
}
228+
156229
func TestSignalExitStatus(t *testing.T) {
157230
testenv.MustHaveGoBuild(t)
158231
exe, err := buildTestProg(t, "testprog")

src/runtime/export_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,16 @@ func CountPagesInUse() (pagesInUse, counted uintptr) {
245245

246246
return
247247
}
248+
249+
// BlockOnSystemStack switches to the system stack, prints "x\n" to
250+
// stderr, and blocks in a stack containing
251+
// "runtime.blockOnSystemStackInternal".
252+
func BlockOnSystemStack() {
253+
systemstack(blockOnSystemStackInternal)
254+
}
255+
256+
func blockOnSystemStackInternal() {
257+
print("x\n")
258+
lock(&deadlock)
259+
lock(&deadlock)
260+
}

src/runtime/signal_sighandler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
101101
if crashing > 0 && gp != _g_.m.curg && _g_.m.curg != nil && readgstatus(_g_.m.curg)&^_Gscan == _Grunning {
102102
// tracebackothers on original m skipped this one; trace it now.
103103
goroutineheader(_g_.m.curg)
104-
traceback(^uintptr(0), ^uintptr(0), 0, gp)
104+
traceback(^uintptr(0), ^uintptr(0), 0, _g_.m.curg)
105105
} else if crashing == 0 {
106106
tracebackothers(gp)
107107
print("\n")

0 commit comments

Comments
 (0)