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()
+}
// The system leaves an 8K PAGE_GUARD region at the bottom of
// the stack (in theory VirtualQuery isn't supposed to include
// that, but it does). Add an additional 8K of slop for
- // calling C functions that don't have stack checks. We
- // shouldn't be anywhere near this bound anyway.
+ // calling C functions that don't have stack checks and for
+ // lastcontinuehandler. We shouldn't be anywhere near this
+ // bound anyway.
base := mbi.allocationBase + 16<<10
// Sanity check the stack bounds.
g0 := getg()
}
}
+// isgoexception returns true if this exception should be translated
+// into a Go panic.
+//
+// It is nosplit to avoid growing the stack in case we're aborting
+// because of a stack overflow.
+//
+//go:nosplit
func isgoexception(info *exceptionrecord, r *context) bool {
// Only handle exception if executing instructions in Go binary
// (not Windows library code).
// Called by sigtramp from Windows VEH handler.
// Return value signals whether the exception has been handled (EXCEPTION_CONTINUE_EXECUTION)
// or should be made available to other handlers in the chain (EXCEPTION_CONTINUE_SEARCH).
+//
+// This is the first entry into Go code for exception handling. This
+// is nosplit to avoid growing the stack until we've checked for
+// _EXCEPTION_BREAKPOINT, which is raised if we overflow the g0 stack,
+//
+//go:nosplit
func exceptionhandler(info *exceptionrecord, r *context, gp *g) int32 {
if !isgoexception(info, r) {
return _EXCEPTION_CONTINUE_SEARCH
}
+ // After this point, it is safe to grow the stack.
+
if gp.throwsplit {
// We can't safely sigpanic because it may grow the
// stack. Let it fall through.
// if ExceptionHandler returns EXCEPTION_CONTINUE_EXECUTION.
// firstcontinuehandler will stop that search,
// if exceptionhandler did the same earlier.
+//
+// It is nosplit for the same reason as exceptionhandler.
+//
+//go:nosplit
func firstcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
if !isgoexception(info, r) {
return _EXCEPTION_CONTINUE_SEARCH
// lastcontinuehandler is reached, because runtime cannot handle
// current exception. lastcontinuehandler will print crash info and exit.
+//
+// It is nosplit for the same reason as exceptionhandler.
+//
+//go:nosplit
func lastcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
if testingWER {
return _EXCEPTION_CONTINUE_SEARCH
}
panicking = 1
+ // In case we're handling a g0 stack overflow, blow away the
+ // g0 stack bounds so we have room to print the traceback. If
+ // this somehow overflows the stack, the OS will trap it.
+ _g_.stack.lo = 0
+ _g_.stackguard0 = _g_.stack.lo + _StackGuard
+ _g_.stackguard1 = _g_.stackguard0
+
print("Exception ", hex(info.exceptioncode), " ", hex(info.exceptioninformation[0]), " ", hex(info.exceptioninformation[1]), " ", hex(r.ip()), "\n")
print("PC=", hex(r.ip()), "\n")