Skip to content

Commit a06a00a

Browse files
crash handler: walk frame pointers seeded from the fault context (#31163)
## Problem Native crash traces had regressed to `libc::backtrace()` / `RtlCaptureStackBackTrace`, which depend on unwind tables. Two things make that fail in shipped builds: - Release strips the unwind tables those APIs need on POSIX (`-fno-asynchronous-unwind-tables`, `--no-eh-frame-hdr`). - The POSIX signal handler runs on an `SA_ONSTACK` altstack, whose frame chain is disjoint from the faulting thread's stack. So native crash captures collapsed to the crash handler's *own* frames (or nothing at all on Linux), and every native crash got reported — and grouped — under `Bun__captureStackTrace`, with no sign of the code that actually faulted. ## Fix This restores the Zig build's mechanism — frame-pointer walking on POSIX, native `.pdata` on Windows — with the trace seeded from the fault register context. **POSIX:** walk frame pointers (force-enabled in every build via `-Cforce-frame-pointers=yes` / `-fno-omit-frame-pointer`, so this works with or without `.eh_frame`). The walker (`frame_address`, `MemoryAccessor`, `StackIterator`) lives in `bun_core::debug`; `btjs.rs` is deduped to re-export it (−433 lines). For real faults, the signal handler reads `(pc, fp)` from the saved `ucontext` (previously discarded) and seeds the walk from there — frame 0 is the exact faulting instruction and the handler/altstack frames are never in the chain. **Windows:** keep the native `.pdata`-based `RtlCaptureStackBackTrace` (frame-pointer walking derails once it crosses into code that doesn't maintain `rbp` — the prebuilt JavaScriptCore, LLInt assembly). Prepend `ExceptionAddress` as frame 0 and trim the handler/ntdll frames by address-matching against it. `.pdata` is emitted in all configs, so this works in debug and release. **Panic format matches Zig:** `-Zlocation-detail=none` stays (the panic call site is recoverable from the now-working backtrace, so embedding ~320 KB of `#[track_caller]` `Location` structs is redundant — Zig had ~0 embedded source paths). The panic hook drops the `(file:line:col)` suffix from the message and emits just the panic text, like Zig did; the location is in the trace. **Removed:** the dead `Bun__captureStackTrace` C-ABI export and the `libc::backtrace`/numeric-skip paths. ## Validation CI green on every shipped target (286/286): Linux x64/aarch64 (glibc and musl), macOS x64/aarch64, Windows x64/aarch64. Binary size: +48–65 KB vs main. Manually validated traces on macOS arm64, Linux x64, Windows x64, debug and release: frame 0 is the fault site, the trace is the real call stack with no handler frames, and bun.report's existing fingerprint logic (top 5 symbolized frames) groups distinct crashes correctly without changes. ## Limitations - **Windows faults at addresses without `.pdata`** (calling a corrupted/null function pointer, JIT code): RtlCapture cannot unwind past the fault, so the trace is just the fault PC. No worse than before — main captures handler+ntdll noise there; neither recovers the real callers. - **FreeBSD / Android**: best-effort. The walker runs, but no fault context is extracted (panics fine, signals degraded). bun.report has no platform code for FreeBSD and folds Android into Linux, so end-to-end reporting there is unsupported regardless. No regression vs main. ## Out of scope Fault-register capture and the trace v3 format (#29607 / bun.report#24) — separate follow-up. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 0d84cdd commit a06a00a

9 files changed

Lines changed: 559 additions & 827 deletions

File tree

scripts/build/rust.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -456,14 +456,15 @@ export function emitRust(n: Ninja, cfg: Config, inputs: RustBuildInputs): string
456456
}
457457
// Drop `#[track_caller]` source-location capture in release. Every
458458
// `Option::unwrap`/`slice[i]`/`RefCell::borrow` etc. otherwise emits a
459-
// `&'static core::panic::Location` (file/line/col), and the file path is a
460-
// separate `&'static str` — together ~180 KB of `.data.rel.ro` across the
461-
// crate graph (plus the per-call-site `lea` to load it). Release ships
462-
// `panic = "abort"` and the crash handler resolves backtraces from frame
463-
// pointers, so the textual location is never printed anyway. Kept for
464-
// debug and `release-assertions` where panic messages are read by humans.
465-
// Nightly-only flag; the pinned toolchain in `rust-toolchain.toml` is
466-
// nightly.
459+
// `&'static core::panic::Location` (file/line/col) plus the file-path string
460+
// and a per-call-site `lea` to load it — ~320 KB across the crate graph
461+
// (measured macOS arm64). Release ships `panic = "abort"` and the crash
462+
// handler captures a frame-pointer backtrace that bun.report symbolizes to
463+
// file:line server-side, so the panic call site is recoverable from the trace
464+
// without embedding the location in the binary — same as the Zig build, which
465+
// had ~0 embedded source paths. Kept off for debug and `release-assertions`
466+
// where panic messages are read locally. Nightly-only; the pinned toolchain
467+
// is nightly.
467468
if (cfg.release && !cfg.assertions) {
468469
rustflags.push("-Zlocation-detail=none");
469470
}

src/bun_bin/phase_c_exports.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
//! unimplemented anywhere (no Zig `export fn`, no C++ body) — those are
1414
//! `unreachable!` so a stray call is loud rather than silent garbage.
1515
//!
16-
//! `__wrap_gettid` and `Bun__captureStackTrace` are NOT here — they live in
17-
//! `bun_core` (their proper, already-linked home).
16+
//! `__wrap_gettid` is NOT here — it lives in `bun_core` (its proper,
17+
//! already-linked home).
1818
//!
1919
//! Calling convention: `jsc.conv` is plain `"C"` on every non-Windows-x64
2020
//! target, so `extern "C"` is correct on Linux/macOS. The Windows path is not

0 commit comments

Comments
 (0)