runtime: add Windows vectored exception handler for recoverable panics

Register a vectored exception handler at startup so Windows hardware
exceptions can be translated into Go panics. Access violations become
nil pointer panics, and integer divide-by-zero exceptions become divide
by zero panics, allowing defer/recover to handle them like ordinary
runtime panics.
This commit is contained in:
Jake Bailey
2026-05-10 15:56:22 -07:00
committed by Ron Evans
parent 29b4c6723f
commit 8ffabbea64
3 changed files with 59 additions and 0 deletions
+2
View File
@@ -485,6 +485,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"--no-insert-timestamp",
"--no-dynamicbase",
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/runtime_windows.c")
case "wasm", "wasip1", "wasip2":
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
default:
+29
View File
@@ -0,0 +1,29 @@
//go:build none
// This file is included on Windows (despite the //go:build line above).
#include <windows.h>
#include <stdint.h>
void tinygo_sigpanic_windows(int32_t exception_code);
static LONG WINAPI tinygo_exception_handler(EXCEPTION_POINTERS *info) {
DWORD code = info->ExceptionRecord->ExceptionCode;
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_IN_PAGE_ERROR:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
tinygo_sigpanic_windows((int32_t)code);
// If runtimePanic triggers longjmp, we never reach here.
// If it doesn't (no defer frame), it will abort and we also
// never reach here.
return EXCEPTION_CONTINUE_SEARCH;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
}
void tinygo_init_exception_handler(void) {
AddVectoredExceptionHandler(1, tinygo_exception_handler);
}
+28
View File
@@ -60,6 +60,10 @@ func mainCRTStartup() int {
_QueryPerformanceFrequency(&performanceFrequency)
}
// Register vectored exception handler so that access violations and
// divide-by-zero exceptions can be recovered with defer/recover.
tinygo_init_exception_handler()
// Obtain the initial stack pointer right before calling the run() function.
// The run function has been moved to a separate (non-inlined) function so
// that the correct stack pointer is read.
@@ -294,3 +298,27 @@ func hardwareRand() (n uint64, ok bool) {
//
//export SystemFunction036
func _RtlGenRandom(buf unsafe.Pointer, len int) bool
const (
_EXCEPTION_ACCESS_VIOLATION = 0xC0000005
_EXCEPTION_IN_PAGE_ERROR = 0xC0000006
_EXCEPTION_INT_DIVIDE_BY_ZERO = 0xC0000094
_EXCEPTION_INT_OVERFLOW = 0xC0000095
)
//export tinygo_init_exception_handler
func tinygo_init_exception_handler()
//export tinygo_sigpanic_windows
func tinygo_sigpanic_windows(exceptionCode int32) {
switch uint32(exceptionCode) {
case _EXCEPTION_ACCESS_VIOLATION, _EXCEPTION_IN_PAGE_ERROR:
runtimePanic("nil pointer dereference")
case _EXCEPTION_INT_DIVIDE_BY_ZERO:
runtimePanic("divide by zero")
case _EXCEPTION_INT_OVERFLOW:
runtimePanic("integer overflow")
default:
runtimePanic("unknown exception")
}
}