]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: support GOTRACEBACK=wer on Windows
authorqmuntal <quimmuntal@gmail.com>
Thu, 9 Mar 2023 13:55:31 +0000 (14:55 +0100)
committerQuim Muntal <quimmuntal@gmail.com>
Mon, 10 Apr 2023 18:52:58 +0000 (18:52 +0000)
GOTRACEBACK=wer is a new traceback level that acts as "crash" and
also enables WER. The same effect can be achieved using
debug.SetTraceback("wer").

The Go runtime currently crashes using exit(2), which bypasses WER
even if it is enabled. To best way to trigger WER is calling
RaiseFailFastException [1] instead, which internally launches the
WER machinery.

This CL also changes how GOTRACEBACK=crash crashes, so both "wer" and
"crash" crash using RaiseFailFastException, which simplifies the
implementation and resolves a longstanding TODO.

Fixes #57441
Fixes #20498

[1] https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-raisefailfastexception

Change-Id: I45669d619fbbd2f6413ce5e5f08425ed1d9aeb64
Reviewed-on: https://go-review.googlesource.com/c/go/+/474915
Reviewed-by: Davis Goodin <dagood@microsoft.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Quim Muntal <quimmuntal@gmail.com>

src/runtime/defs_windows.go
src/runtime/export_windows_test.go
src/runtime/extern.go
src/runtime/nonwindows_stub.go [moved from src/runtime/relax_stub.go with 82% similarity]
src/runtime/os_windows.go
src/runtime/runtime1.go
src/runtime/signal_windows.go
src/runtime/syscall_windows_test.go

index 60f20a5c2cbe915e8e38c8e4f4d3daa1932ff6a0..56698fa56c4366761cbc692201eba37145ce0567 100644 (file)
@@ -67,7 +67,7 @@ type exceptionrecord struct {
        exceptioncode        uint32
        exceptionflags       uint32
        exceptionrecord      *exceptionrecord
-       exceptionaddress     *byte
+       exceptionaddress     uintptr
        numberparameters     uint32
        exceptioninformation [15]uintptr
 }
index d4b1e1fad32e4ea8de626bb60a5f076773111cff..332136b586fcdfd9c853591d40674e16b221852f 100644 (file)
@@ -11,7 +11,6 @@ import "unsafe"
 const MaxArgs = maxArgs
 
 var (
-       TestingWER              = &testingWER
        OsYield                 = osyield
        TimeBeginPeriodRetValue = &timeBeginPeriodRetValue
 )
index 03d593906ee03d97c66995820a4ee53da9fa6250..189b4d4bb90b4e731d8ede4757072da970885301 100644 (file)
@@ -217,6 +217,7 @@ and shows goroutines created internally by the run-time.
 GOTRACEBACK=crash is like “system” but crashes in an operating system-specific
 manner instead of exiting. For example, on Unix systems, the crash raises
 SIGABRT to trigger a core dump.
+GOTRACEBACK=wer is like “crash” but doesn't disable Windows Error Reporting (WER).
 For historical reasons, the GOTRACEBACK settings 0, 1, and 2 are synonyms for
 none, all, and system, respectively.
 The runtime/debug package's SetTraceback function allows increasing the
similarity index 82%
rename from src/runtime/relax_stub.go
rename to src/runtime/nonwindows_stub.go
index e507702fc10e83a94b4205b6540ebd3b699cfb3e..033f026c421702d32c574f6af787d78af00beb98 100644 (file)
@@ -15,3 +15,7 @@ const osRelaxMinNS = 0
 // osRelax is called by the scheduler when transitioning to and from
 // all Ps being idle.
 func osRelax(relax bool) {}
+
+// enableWER is called by setTraceback("wer").
+// Windows Error Reporting (WER) is only supported on Windows.
+func enableWER() {}
index 41f8f7784890b1eb5deeed372fd0a48ef9be8e3f..10b445837e4c8563cba84ef0de3050b1996a7fdc 100644 (file)
@@ -30,6 +30,7 @@ const (
 //go:cgo_import_dynamic runtime._GetConsoleMode GetConsoleMode%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._GetCurrentThreadId GetCurrentThreadId%0 "kernel32.dll"
 //go:cgo_import_dynamic runtime._GetEnvironmentStringsW GetEnvironmentStringsW%0 "kernel32.dll"
+//go:cgo_import_dynamic runtime._GetErrorMode GetErrorMode%0 "kernel32.dll"
 //go:cgo_import_dynamic runtime._GetProcAddress GetProcAddress%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._GetProcessAffinityMask GetProcessAffinityMask%3 "kernel32.dll"
 //go:cgo_import_dynamic runtime._GetQueuedCompletionStatusEx GetQueuedCompletionStatusEx%6 "kernel32.dll"
@@ -41,6 +42,7 @@ const (
 //go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll"
 //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
 //go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
+//go:cgo_import_dynamic runtime._RaiseFailFastException RaiseFailFastException%3 "kernel32.dll"
 //go:cgo_import_dynamic runtime._ResumeThread ResumeThread%1 "kernel32.dll"
 //go:cgo_import_dynamic runtime._SetConsoleCtrlHandler SetConsoleCtrlHandler%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._SetErrorMode SetErrorMode%1 "kernel32.dll"
@@ -57,6 +59,8 @@ const (
 //go:cgo_import_dynamic runtime._VirtualQuery VirtualQuery%3 "kernel32.dll"
 //go:cgo_import_dynamic runtime._WaitForSingleObject WaitForSingleObject%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._WaitForMultipleObjects WaitForMultipleObjects%4 "kernel32.dll"
+//go:cgo_import_dynamic runtime._WerGetFlags WerGetFlags%2 "kernel32.dll"
+//go:cgo_import_dynamic runtime._WerSetFlags WerSetFlags%1 "kernel32.dll"
 //go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "kernel32.dll"
 //go:cgo_import_dynamic runtime._WriteFile WriteFile%5 "kernel32.dll"
 
@@ -80,6 +84,7 @@ var (
        _GetConsoleMode,
        _GetCurrentThreadId,
        _GetEnvironmentStringsW,
+       _GetErrorMode,
        _GetProcAddress,
        _GetProcessAffinityMask,
        _GetQueuedCompletionStatusEx,
@@ -94,6 +99,7 @@ var (
        _PostQueuedCompletionStatus,
        _QueryPerformanceCounter,
        _QueryPerformanceFrequency,
+       _RaiseFailFastException,
        _ResumeThread,
        _SetConsoleCtrlHandler,
        _SetErrorMode,
@@ -110,6 +116,8 @@ var (
        _VirtualQuery,
        _WaitForSingleObject,
        _WaitForMultipleObjects,
+       _WerGetFlags,
+       _WerSetFlags,
        _WriteConsoleW,
        _WriteFile,
        _ stdFunction
@@ -519,7 +527,7 @@ func osinit() {
 
        loadOptionalSyscalls()
 
-       disableWER()
+       preventErrorDialogs()
 
        initExceptionHandler()
 
index 68a090a3c722b2b314e32a72ec247914f89ef2f3..98c5c84c0185d3606a2cd017a8571ef045b81c6c 100644 (file)
@@ -513,6 +513,13 @@ func setTraceback(level string) {
                t = 2<<tracebackShift | tracebackAll
        case "crash":
                t = 2<<tracebackShift | tracebackAll | tracebackCrash
+       case "wer":
+               if GOOS == "windows" {
+                       t = 2<<tracebackShift | tracebackAll | tracebackCrash
+                       enableWER()
+                       break
+               }
+               fallthrough
        default:
                t = tracebackAll
                if n, ok := atoi(level); ok && n == int(uint32(n)) {
index e4258f01b0865e6fd3b32c4836371b4acf1cb381..59c261ac1993354af07bad1ceefbf41803dbc48c 100644 (file)
@@ -10,16 +10,34 @@ import (
        "unsafe"
 )
 
-func disableWER() {
-       // do not display Windows Error Reporting dialogue
-       const (
-               SEM_FAILCRITICALERRORS     = 0x0001
-               SEM_NOGPFAULTERRORBOX      = 0x0002
-               SEM_NOALIGNMENTFAULTEXCEPT = 0x0004
-               SEM_NOOPENFILEERRORBOX     = 0x8000
-       )
-       errormode := uint32(stdcall1(_SetErrorMode, SEM_NOGPFAULTERRORBOX))
-       stdcall1(_SetErrorMode, uintptr(errormode)|SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX|SEM_NOOPENFILEERRORBOX)
+const (
+       _SEM_FAILCRITICALERRORS = 0x0001
+       _SEM_NOGPFAULTERRORBOX  = 0x0002
+       _SEM_NOOPENFILEERRORBOX = 0x8000
+
+       _WER_FAULT_REPORTING_NO_UI = 0x0020
+)
+
+func preventErrorDialogs() {
+       errormode := stdcall0(_GetErrorMode)
+       stdcall1(_SetErrorMode, errormode|_SEM_FAILCRITICALERRORS|_SEM_NOGPFAULTERRORBOX|_SEM_NOOPENFILEERRORBOX)
+
+       // Disable WER fault reporting UI.
+       // Do this even if WER is disabled as a whole,
+       // as WER might be enabled later with setTraceback("wer")
+       // and we still want the fault reporting UI to be disabled if this happens.
+       var werflags uintptr
+       stdcall2(_WerGetFlags, currentProcess, uintptr(unsafe.Pointer(&werflags)))
+       stdcall1(_WerSetFlags, werflags|_WER_FAULT_REPORTING_NO_UI)
+}
+
+// enableWER re-enables Windows error reporting without fault reporting UI.
+func enableWER() {
+       // re-enable Windows Error Reporting
+       errormode := stdcall0(_GetErrorMode)
+       if errormode&_SEM_NOGPFAULTERRORBOX != 0 {
+               stdcall1(_SetErrorMode, errormode^_SEM_NOGPFAULTERRORBOX)
+       }
 }
 
 // in sys_windows_386.s, sys_windows_amd64.s, sys_windows_arm.s, and sys_windows_arm64.s
@@ -259,8 +277,6 @@ func firstcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
        return _EXCEPTION_CONTINUE_EXECUTION
 }
 
-var testingWER bool
-
 // lastcontinuehandler is reached, because runtime cannot handle
 // current exception. lastcontinuehandler will print crash info and exit.
 //
@@ -274,9 +290,6 @@ func lastcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
                // should not take responsibility of crashing the process.
                return _EXCEPTION_CONTINUE_SEARCH
        }
-       if testingWER {
-               return _EXCEPTION_CONTINUE_SEARCH
-       }
 
        // VEH is called before SEH, but arm64 MSVC DLLs use SEH to trap
        // illegal instructions during runtime initialization to determine
@@ -333,7 +346,7 @@ func winthrow(info *exceptionrecord, r *context, gp *g) {
        }
 
        if docrash {
-               crash()
+               dieFromException(info, r)
        }
 
        exit(2)
@@ -396,14 +409,36 @@ func signame(sig uint32) string {
 
 //go:nosplit
 func crash() {
-       // TODO: This routine should do whatever is needed
-       // to make the Windows program abort/crash as it
-       // would if Go was not intercepting signals.
-       // On Unix the routine would remove the custom signal
-       // handler and then raise a signal (like SIGABRT).
-       // Something like that should happen here.
-       // It's okay to leave this empty for now: if crash returns
-       // the ordinary exit-after-panic happens.
+       dieFromException(nil, nil)
+}
+
+// dieFromException raises an exception that bypasses all exception handlers.
+// This provides the expected exit status for the shell.
+//
+//go:nosplit
+func dieFromException(info *exceptionrecord, r *context) {
+       if info == nil {
+               gp := getg()
+               if gp.sig != 0 {
+                       // Try to reconstruct an exception record from
+                       // the exception information stored in gp.
+                       info = &exceptionrecord{
+                               exceptionaddress: gp.sigpc,
+                               exceptioncode:    gp.sig,
+                               numberparameters: 2,
+                       }
+                       info.exceptioninformation[0] = gp.sigcode0
+                       info.exceptioninformation[1] = gp.sigcode1
+               } else {
+                       // By default, a failing Go application exits with exit code 2.
+                       // Use this value when gp does not contain exception info.
+                       info = &exceptionrecord{
+                               exceptioncode: 2,
+                       }
+               }
+       }
+       const FAIL_FAST_GENERATE_EXCEPTION_ADDRESS = 0x1
+       stdcall3(_RaiseFailFastException, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(r)), FAIL_FAST_GENERATE_EXCEPTION_ADDRESS)
 }
 
 // gsignalStack is unused on Windows.
index b49da323841453f89d95591fa092e67b5836f5d6..8686d3f7f83925865b8e3255b898fe012f54c4f9 100644 (file)
@@ -648,21 +648,26 @@ func TestZeroDivisionException(t *testing.T) {
 }
 
 func TestWERDialogue(t *testing.T) {
-       if os.Getenv("TESTING_WER_DIALOGUE") == "1" {
-               defer os.Exit(0)
-
-               *runtime.TestingWER = true
+       const exitcode = 0xbad
+       if os.Getenv("TEST_WER_DIALOGUE") == "1" {
                const EXCEPTION_NONCONTINUABLE = 1
                mod := syscall.MustLoadDLL("kernel32.dll")
                proc := mod.MustFindProc("RaiseException")
-               proc.Call(0xbad, EXCEPTION_NONCONTINUABLE, 0, 0)
-               println("RaiseException should not return")
+               proc.Call(exitcode, EXCEPTION_NONCONTINUABLE, 0, 0)
+               t.Fatal("RaiseException should not return")
                return
        }
-       cmd := exec.Command(os.Args[0], "-test.run=TestWERDialogue")
-       cmd.Env = []string{"TESTING_WER_DIALOGUE=1"}
+       cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestWERDialogue"))
+       cmd.Env = append(cmd.Env, "TEST_WER_DIALOGUE=1", "GOTRACEBACK=wer")
        // Child process should not open WER dialogue, but return immediately instead.
-       cmd.CombinedOutput()
+       _, err := cmd.CombinedOutput()
+       if err == nil {
+               t.Error("test program succeeded unexpectedly")
+       } else if ee, ok := err.(*exec.ExitError); !ok {
+               t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
+       } else if got := ee.ExitCode(); got != exitcode {
+               t.Fatalf("got exit code %d; want %d", got, exitcode)
+       }
 }
 
 func TestWindowsStackMemory(t *testing.T) {