runtime: handle g0 stack overflows gracefully

Currently, if the runtime overflows the g0 stack on Windows, it leads
to an infinite recursion:

1. Something overflows the g0 stack bounds and calls morestack.

2. morestack determines it's on the g0 stack and hence cannot grow the
stack, so it calls badmorestackg0 (which prints "fatal: morestack on
g0") followed by abort.

3. abort performs an INT $3, which turns into a Windows
_EXCEPTION_BREAKPOINT exception.

4. This enters the Windows sigtramp, which ensures we're on the g0
stack and calls exceptionhandler.

5. exceptionhandler has a stack check prologue, so it determines that
it's out of stack and calls morestack.

6. goto 2

Fix this by making the exception handler avoid stack checks until it
has ruled out an abort and by blowing away the stack bounds in
lastcontinuehandler before we print the final fatal traceback (which
itself involves a lot of stack bounds checks).

Fixes #21382.

Change-Id: Ie66e91f708e18d131d97f22b43f9ac26f3aece5a
Reviewed-on: https://go-review.googlesource.com/120857
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
This commit is contained in:
Austin Clements 2018-06-25 18:00:43 -04:00
parent d6b56bb301
commit 78561c4ae9
5 changed files with 78 additions and 2 deletions

View file

@ -687,3 +687,32 @@ func TestRuntimePanic(t *testing.T) {
t.Errorf("output did not contain expected string %q", want)
}
}
// Test that g0 stack overflows are handled gracefully.
func TestG0StackOverflow(t *testing.T) {
testenv.MustHaveExec(t)
switch runtime.GOOS {
case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd":
t.Skipf("g0 stack is wrong on pthread platforms (see golang.org/issue/26061)")
}
if os.Getenv("TEST_G0_STACK_OVERFLOW") != "1" {
cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestG0StackOverflow", "-test.v"))
cmd.Env = append(cmd.Env, "TEST_G0_STACK_OVERFLOW=1")
out, err := cmd.CombinedOutput()
// Don't check err since it's expected to crash.
if n := strings.Count(string(out), "morestack on g0\n"); n != 1 {
t.Fatalf("%s\n(exit status %v)", out, err)
}
// Check that it's a signal-style traceback.
if runtime.GOOS != "windows" {
if want := "PC="; !strings.Contains(string(out), want) {
t.Errorf("output does not contain %q:\n%s", want, out)
}
}
return
}
runtime.G0StackOverflow()
}