Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
// oven-sh/WebKit main: macOS + Windows artifacts cross-compiled on Linux,
// -lto variants built with ThinLTO (per-module summaries for cross-language
// importing), every x64 at the nehalem floor (no separate -baseline variant),
// typed-array constructor ClassInfo kept address-unique under LTO, and the
// Windows ICU data table filtered + per-item zstd compressed.
export const WEBKIT_VERSION = "c9296e353e365ecf0de82f273bb0a88a3df465be";
// typed-array constructor ClassInfo kept address-unique under LTO, the
// Windows ICU data table filtered + per-item zstd compressed, and Windows
// unwind info (RtlAddGrowableFunctionTable) registered for the fixed JIT
// pool (LLInt pending offlineasm .seh_* emission).
export const WEBKIT_VERSION = "a40d462206e1caf8388062120acde61e37a4ae7d";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
4 changes: 4 additions & 0 deletions src/bun_core/Global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,8 +718,12 @@ pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! {
// preconditions, so `safe fn` discharges the link-time proof.
unsafe extern "system" {
safe fn RemoveVectoredExceptionHandler(Handle: *mut core::ffi::c_void) -> u32;
safe fn SetUnhandledExceptionFilter(
f: Option<unsafe extern "system" fn(*mut core::ffi::c_void) -> i32>,
) -> Option<unsafe extern "system" fn(*mut core::ffi::c_void) -> i32>;
}
let _ = RemoveVectoredExceptionHandler(handle);
let _ = SetUnhandledExceptionFilter(None);
}
}

Expand Down
161 changes: 138 additions & 23 deletions src/crash_handler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1765,6 +1765,10 @@ mod draft {
}
#[cfg(windows)]
{
let range = bun_sys::windows::exe_image_range();
WINDOWS_EXE_IMAGE_BASE.store(range.start, Ordering::Relaxed);
WINDOWS_EXE_IMAGE_END.store(range.end, Ordering::Relaxed);

// SAFETY: AddVectoredExceptionHandler is a valid Win32 call
unsafe {
// SAFETY: ABI-identical `extern "system" fn(*mut _) -> i32` —
Expand All @@ -1781,6 +1785,19 @@ mod draft {
// `reset_on_posix`). `HANDLE` is `*mut c_void`; cast is identity.
bun_core::WINDOWS_SEGFAULT_HANDLE
.store(handle as *mut core::ffi::c_void, Ordering::Relaxed);

// Backstop for exceptions the VEH passed on: runs only after
// every frame-based (SEH) handler has declined, so an
// exception a foreign module handles itself never reaches it.
// The handler JSC registers for JIT frames
// (ZigGlobalObject.cpp -> setJITExceptionHandlerWin) catches
// the under-JIT case before this.
bun_sys::windows::kernel32::SetUnhandledExceptionFilter(Some(
bun_ptr::cast_fn_ptr::<
extern "system" fn(*mut bun_sys::windows::EXCEPTION_POINTERS) -> c_long,
unsafe extern "system" fn(*mut core::ffi::c_void) -> i32,
>(handle_unhandled_exception_windows),
));
}
}
#[cfg(any(
Expand Down Expand Up @@ -2020,6 +2037,11 @@ mod draft {
unsafe { bun_sys::windows::kernel32::RemoveVectoredExceptionHandler(handle) };
debug_assert!(rc != 0);
}
// SAFETY: no memory-safety preconditions; clears the top-level
// filter back to the OS default.
unsafe {
bun_sys::windows::kernel32::SetUnhandledExceptionFilter(None);
}
return;
Comment thread
robobun marked this conversation as resolved.
}

Expand All @@ -2038,41 +2060,73 @@ mod draft {
}

#[cfg(windows)]
pub(crate) extern "system" fn handle_segfault_windows(
info: *mut bun_sys::windows::EXCEPTION_POINTERS,
) -> c_long {
// SAFETY: kernel provides a valid EXCEPTION_POINTERS
let info = unsafe { &*info };
let reason = match unsafe { (*info.ExceptionRecord).ExceptionCode } {
static WINDOWS_EXE_IMAGE_BASE: AtomicUsize = AtomicUsize::new(0);
#[cfg(windows)]
static WINDOWS_EXE_IMAGE_END: AtomicUsize = AtomicUsize::new(0);

#[cfg(windows)]
fn classify_exception_windows(
record: &bun_sys::windows::EXCEPTION_RECORD,
) -> Option<CrashReason> {
Some(match record.ExceptionCode {
bun_sys::windows::EXCEPTION_DATATYPE_MISALIGNMENT => CrashReason::DatatypeMisalignment,
bun_sys::windows::EXCEPTION_ACCESS_VIOLATION => {
CrashReason::SegmentationFault(unsafe {
(*info.ExceptionRecord).ExceptionInformation[1]
})
CrashReason::SegmentationFault(record.ExceptionInformation[1])
}
bun_sys::windows::EXCEPTION_ILLEGAL_INSTRUCTION => {
// `ExceptionAddress` is the faulting RIP for `STATUS_ILLEGAL_
// INSTRUCTION` (winnt.h); avoids depending on the arch-specific
// `CONTEXT` layout.
CrashReason::IllegalInstruction(
unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize
)
CrashReason::IllegalInstruction(record.ExceptionAddress as usize)
}
bun_sys::windows::EXCEPTION_STACK_OVERFLOW => CrashReason::StackOverflow,
_ => return None,
})
}

// exception used for thread naming
// https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception
// related commit
// https://github.com/go-delve/delve/pull/1384
bun_sys::windows::MS_VC_EXCEPTION => {
return bun_sys::windows::EXCEPTION_CONTINUE_EXECUTION;
}
#[cfg(windows)]
pub(crate) extern "system" fn handle_segfault_windows(
info: *mut bun_sys::windows::EXCEPTION_POINTERS,
) -> c_long {
// SAFETY: kernel provides a valid EXCEPTION_POINTERS / EXCEPTION_RECORD.
let info = unsafe { &*info };
let record = unsafe { &*info.ExceptionRecord };

// exception used for thread naming
// https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception
// related commit
// https://github.com/go-delve/delve/pull/1384
if record.ExceptionCode == bun_sys::windows::MS_VC_EXCEPTION {
return bun_sys::windows::EXCEPTION_CONTINUE_EXECUTION;
}

_ => return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH,
let Some(reason) = classify_exception_windows(record) else {
return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH;
};
// SAFETY: kernel provides a valid EXCEPTION_RECORD; ExceptionAddress is
// the faulting instruction.
let pc = unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize;

// VEH runs before any frame-based (SEH) handler. Windows system code
// deliberately uses SEH to probe unchecked handles: CRYPTSP.dll, for
// example, validates an HCRYPTPROV by reading `[rcx+0E8h]` inside a
// `__try`/`__except` that turns the access violation into
// `ERROR_INVALID_PARAMETER`. Treating that first-chance exception as
// fatal kills the process for what the callee was about to recover
// from. So only take over here when the faulting instruction is inside
// Bun's own image; for foreign code, let SEH dispatch proceed. JSC
// registers unwind info for its JIT pool with a language-specific
// handler (LLInt is pending build-time offlineasm .seh_* emission)
// that routes back to `Bun__crashHandlerFromJSCFrame`, and
// `handle_unhandled_exception_windows` reports anything that still
// goes unhandled. Stack overflow is always claimed here: no foreign
// `__except` recovers from it in practice, and SEH dispatch itself
// costs stack the guard reserve may not have.
let pc = record.ExceptionAddress as usize;
let base = WINDOWS_EXE_IMAGE_BASE.load(Ordering::Relaxed);
let end = WINDOWS_EXE_IMAGE_END.load(Ordering::Relaxed);
if !matches!(reason, CrashReason::StackOverflow) && base != 0 && !(base..end).contains(&pc)
{
return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH;
Comment thread
robobun marked this conversation as resolved.
}
Comment thread
robobun marked this conversation as resolved.

// Windows: capture_from_context walks via RtlVirtualUnwind seeded from
// the fault CONTEXT, so the handler's own frames are never captured.
crash_handler(
Expand All @@ -2084,6 +2138,67 @@ mod draft {
);
}

/// Called from JSC's `jscJITSEHHandler` when SEH dispatch reaches a JIT
/// frame with an unhandled exception. Reports the crash if the reason is
/// one we classify; otherwise continues the search so an outer handler
/// (or UEF) can claim it.
#[cfg(windows)]
#[unsafe(no_mangle)]
pub(crate) extern "C" fn Bun__crashHandlerFromJSCFrame(
record: *mut bun_sys::windows::EXCEPTION_RECORD,
_establisher_frame: *mut core::ffi::c_void,
context: *mut core::ffi::c_void,
_dispatcher: *mut core::ffi::c_void,
) -> c_long {
use bun_sys::windows::disposition::ExceptionContinueSearch;
// SAFETY: kernel provides a valid EXCEPTION_RECORD.
let record = unsafe { &*record };
// A PEXCEPTION_ROUTINE can also be invoked during the unwind phase if
// the frame's UNWIND_INFO carries UNW_FLAG_UHANDLER (the WebKit side
// currently sets EHANDLER only; this matches SpiderMonkey's guard).
// Also decline once `reset_segfault_handler` has torn down the VEH so
// a re-fault during teardown reaches the OS default instead of
// re-entering `crash_handler`.
if record.ExceptionFlags & bun_sys::windows::EXCEPTION_UNWIND != 0
|| bun_core::WINDOWS_SEGFAULT_HANDLE
.load(Ordering::Relaxed)
.is_null()
{
return ExceptionContinueSearch;
}
let Some(reason) = classify_exception_windows(record) else {
return ExceptionContinueSearch;
};
let pc = record.ExceptionAddress as usize;
crash_handler(
reason,
TraceSeed::Fault {
pc,
fp: context as usize,
},
);
}

#[cfg(windows)]
pub(crate) extern "system" fn handle_unhandled_exception_windows(
info: *mut bun_sys::windows::EXCEPTION_POINTERS,
) -> c_long {
// SAFETY: kernel provides a valid EXCEPTION_POINTERS / EXCEPTION_RECORD.
let info = unsafe { &*info };
let record = unsafe { &*info.ExceptionRecord };
let Some(reason) = classify_exception_windows(record) else {
return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH;
};
Comment thread
robobun marked this conversation as resolved.
let pc = record.ExceptionAddress as usize;
crash_handler(
reason,
TraceSeed::Fault {
pc,
fp: info.ContextRecord as usize,
},
);
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]
unsafe extern "C" {
fn gnu_get_libc_version() -> *const c_char;
Expand Down
22 changes: 22 additions & 0 deletions src/jsc/bindings/JSCTestingHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
#include <JavaScriptCore/JSString.h>
#include "ZigGlobalObject.h"

#if OS(WINDOWS)
#include <JavaScriptCore/ExecutableAllocator.h>
#include <JavaScriptCore/JSBigInt.h>
#endif

namespace Bun {
using namespace JSC;

Expand Down Expand Up @@ -49,6 +54,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionIsLatin1String,
return {};
}

#if OS(WINDOWS)
JSC_DEFINE_HOST_FUNCTION(jsFunctionStartOfFixedExecutableMemoryPool,
(JSGlobalObject * globalObject, CallFrame*))
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
RELEASE_AND_RETURN(scope, JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(globalObject, static_cast<uint64_t>(JSC::startOfFixedExecutableMemoryPool<uintptr_t>()))));
}
Comment thread
robobun marked this conversation as resolved.
#endif

JSC::JSValue createJSCTestingHelpers(Zig::GlobalObject* globalObject)
{
auto& vm = JSC::getVM(globalObject);
Expand All @@ -65,6 +80,13 @@ JSC::JSValue createJSCTestingHelpers(Zig::GlobalObject* globalObject)
jsFunctionIsLatin1String, ImplementationVisibility::Public, NoIntrinsic,
JSC::PropertyAttribute::DontDelete | 0);

#if OS(WINDOWS)
object->putDirectNativeFunction(
vm, globalObject, JSC::Identifier::fromString(vm, "startOfFixedExecutableMemoryPool"_s), 0,
jsFunctionStartOfFixedExecutableMemoryPool, ImplementationVisibility::Public, NoIntrinsic,
JSC::PropertyAttribute::DontDelete | 0);
#endif

return object;
}

Expand Down
14 changes: 14 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ extern "C" unsigned getJSCBytecodeCacheVersion()
extern "C" void Bun__REPRL__registerFuzzilliFunctions(Zig::GlobalObject*);
#endif

#if OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64))
#include <JavaScriptCore/ExecutableAllocator.h>
extern "C" long Bun__crashHandlerFromJSCFrame(void*, void*, void*, void*);
#endif

extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(const char* ptr, size_t length), bool evalMode, bool oneShotStartup)
{
static std::once_flag jsc_init_flag;
Expand Down Expand Up @@ -357,6 +362,15 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
}
JSC::Options::assertOptionsAreCoherent();
}); // end JSC::initialize lambda

#if OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64))
// JSC::initialize() registered unwind info + a language-specific SEH
// handler for the JIT pool. Route that handler to the crash reporter
// so a hardware fault under a JIT frame is reported deterministically
// at the JSC boundary. LLInt is not yet covered (needs build-time
// offlineasm .seh_* emission).
JSC::setJITExceptionHandlerWin(&Bun__crashHandlerFromJSCFrame);
Comment thread
robobun marked this conversation as resolved.
#endif
}); // end std::call_once lambda

// NOLINTEND
Expand Down
36 changes: 36 additions & 0 deletions src/sys/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,42 @@ pub const EXCEPTION_CONTINUE_EXECUTION: i32 = -1;
pub const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
pub const MS_VC_EXCEPTION: u32 = 0x406d1388;

/// `EXCEPTION_DISPOSITION` (excpt.h): return type of a `PEXCEPTION_ROUTINE`
/// language-specific handler. A different enum from the filter/VEH constants
/// above (`EXCEPTION_CONTINUE_SEARCH == 0` there, `ExceptionContinueSearch ==
/// 1` here); mixing the two turns "continue search" into "resume at fault".
#[allow(nonstandard_style)]
pub mod disposition {
use core::ffi::c_long;
pub const ExceptionContinueExecution: c_long = 0;
pub const ExceptionContinueSearch: c_long = 1;
}

/// `EXCEPTION_UNWIND` (winnt.h): mask of `ExceptionFlags` bits set during the
/// unwind (not search) phase of frame-based dispatch.
pub const EXCEPTION_UNWIND: u32 = 0x66;

/// `[base, base + SizeOfImage)` of the process executable, read once from the
/// mapped PE header. The crash handler uses this to tell first-chance
/// exceptions raised inside Bun's own code from those raised inside foreign
/// modules.
pub fn exe_image_range() -> core::ops::Range<usize> {
// SAFETY: null module name returns the exe's HMODULE, which on Windows is
// its mapped base address. The IMAGE_DOS_HEADER at `base` and
// IMAGE_NT_HEADERS at `base + e_lfanew` are part of the loader-mapped
// image and remain valid for the process lifetime.
unsafe {
let base = bun_windows_sys::kernel32::GetModuleHandleW(ptr::null()) as usize;
let e_lfanew = *(base as *const u8).add(0x3C).cast::<u32>() as usize;
// IMAGE_NT_HEADERS64: Signature(4) + IMAGE_FILE_HEADER(20) +
// IMAGE_OPTIONAL_HEADER64.SizeOfImage at offset 56.
let size_of_image = *(base as *const u8)
.add(e_lfanew + 4 + 20 + 56)
.cast::<u32>() as usize;
base..base + size_of_image
}
}

// `STATUS_*` values surfaced as `ExceptionCode` (winnt.h).
pub const EXCEPTION_ACCESS_VIOLATION: u32 = 0xC0000005;
pub const EXCEPTION_DATATYPE_MISALIGNMENT: u32 = 0x80000002;
Expand Down
8 changes: 8 additions & 0 deletions src/windows_sys/externs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,14 @@ pub mod kernel32 {
First: u32,
Handler: unsafe extern "system" fn(*mut c_void) -> i32,
) -> *mut c_void;
/// `SetUnhandledExceptionFilter` (`errhandlingapi.h`). Runs after all
/// frame-based (SEH) handlers have declined.
pub fn SetUnhandledExceptionFilter(
lpTopLevelExceptionFilter: Option<unsafe extern "system" fn(*mut c_void) -> i32>,
) -> Option<unsafe extern "system" fn(*mut c_void) -> i32>;
/// `GetModuleHandleW` (`libloaderapi.h`). `lpModuleName == null` returns
/// the base address of the calling process's executable image.
pub fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE;
/// `RemoveVectoredExceptionHandler` (`errhandlingapi.h`).
pub fn RemoveVectoredExceptionHandler(Handle: *mut c_void) -> u32;
}
Expand Down
Loading
Loading