mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Use std.debug.captureStackTrace on all platforms (#24456)
In the crash reporter, we currently use glibc's `backtrace()` function on glibc Linux targets. However, this has resulted in poor stack traces in many scenarios, particularly when a JSC signal handlers is involved, in which case the stack trace tends to have only one frame—the signal handler itself. Considering that JSC installs a signal handler for SEGV, this is particularly bad. Zig's `std.debug.captureStackTrace` generates considerably more complete stack traces, but it has an issue where the top frame is missing when a signal handler is involved. This is unfortunate, but it's still the better option for now. Note that our stack traces on macOS also have this missing frame issue. In the future, we will investigate backporting the changes to stack trace capturing that were recently made in Zig's `master` branch, since that seems to have fixed the missing frame issue. This PR still uses the stack trace provided by `backtrace()` if it returns more frames than `captureStackTrace`. In particular, ARM may need this behavior. (For internal tracking: fixes ENG-21406)
This commit is contained in:
@@ -163,6 +163,37 @@ pub const Action = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
fn captureLibcBacktrace(begin_addr: usize, stack_trace: *std.builtin.StackTrace) void {
|
||||
const backtrace = struct {
|
||||
extern "c" fn backtrace(buffer: [*]*anyopaque, size: c_int) c_int;
|
||||
}.backtrace;
|
||||
|
||||
const addrs = stack_trace.instruction_addresses;
|
||||
const count = backtrace(@ptrCast(addrs), @intCast(addrs.len));
|
||||
stack_trace.index = @intCast(count);
|
||||
|
||||
// Skip frames until we find begin_addr (or close to it)
|
||||
// backtrace() captures everything including crash handler frames
|
||||
const tolerance: usize = 128;
|
||||
const skip: usize = for (addrs[0..stack_trace.index], 0..) |addr, i| {
|
||||
// Check if this address is close to begin_addr (within tolerance)
|
||||
const delta = if (addr >= begin_addr)
|
||||
addr - begin_addr
|
||||
else
|
||||
begin_addr - addr;
|
||||
if (delta <= tolerance) break i;
|
||||
// Give up searching after 8 frames
|
||||
if (i >= 8) break 0;
|
||||
} else 0;
|
||||
|
||||
// Shift the addresses to skip crash handler frames
|
||||
// If begin_addr was not found, use the complete backtrace
|
||||
if (skip > 0) {
|
||||
std.mem.copyForwards(usize, addrs, addrs[skip..stack_trace.index]);
|
||||
stack_trace.index -= skip;
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is invoked when a crash happens. A crash is classified in `CrashReason`.
|
||||
pub fn crashHandler(
|
||||
reason: CrashReason,
|
||||
@@ -308,67 +339,31 @@ pub fn crashHandler(
|
||||
var trace_buf: std.builtin.StackTrace = undefined;
|
||||
|
||||
// If a trace was not provided, compute one now
|
||||
const trace = @as(?*std.builtin.StackTrace, if (error_return_trace) |ert|
|
||||
if (ert.index > 0)
|
||||
ert
|
||||
else
|
||||
null
|
||||
else
|
||||
null) orelse get_backtrace: {
|
||||
const trace = blk: {
|
||||
if (error_return_trace) |ert| {
|
||||
if (ert.index > 0) break :blk ert;
|
||||
}
|
||||
trace_buf = std.builtin.StackTrace{
|
||||
.index = 0,
|
||||
.instruction_addresses = &addr_buf,
|
||||
};
|
||||
const desired_begin_addr = begin_addr orelse @returnAddress();
|
||||
std.debug.captureStackTrace(desired_begin_addr, &trace_buf);
|
||||
|
||||
// On Linux with glibc, always use backtrace() instead of Zig's StackIterator
|
||||
// because Zig's frame pointer-based unwinding doesn't work reliably,
|
||||
// especially on aarch64. glibc's backtrace() uses DWARF unwinding.
|
||||
if (bun.Environment.isLinux and !bun.Environment.isMusl) {
|
||||
const backtrace_fn = struct {
|
||||
extern "c" fn backtrace(buffer: [*]?*anyopaque, size: c_int) c_int;
|
||||
}.backtrace;
|
||||
const count = backtrace_fn(@ptrCast(&addr_buf), addr_buf.len);
|
||||
if (count > 0) {
|
||||
trace_buf.index = @intCast(count);
|
||||
|
||||
// Skip frames until we find begin_addr (or close to it)
|
||||
// backtrace() captures everything including crash handler frames
|
||||
var skip: usize = 0;
|
||||
var found_begin = false;
|
||||
const tolerance: usize = 128;
|
||||
for (addr_buf[0..trace_buf.index], 0..) |addr, i| {
|
||||
// Check if this address is close to begin_addr (within tolerance)
|
||||
const delta = if (addr >= desired_begin_addr)
|
||||
addr - desired_begin_addr
|
||||
else
|
||||
desired_begin_addr - addr;
|
||||
if (delta <= tolerance) {
|
||||
skip = i;
|
||||
found_begin = true;
|
||||
break;
|
||||
}
|
||||
// Give up searching after 8 frames
|
||||
if (i >= 8) break;
|
||||
}
|
||||
|
||||
// Shift the addresses to skip crash handler frames
|
||||
// If begin_addr was not found, use the complete backtrace
|
||||
if (found_begin and skip > 0 and skip < trace_buf.index) {
|
||||
const remaining = trace_buf.index - skip;
|
||||
var j: usize = 0;
|
||||
while (j < remaining) : (j += 1) {
|
||||
addr_buf[j] = addr_buf[skip + j];
|
||||
}
|
||||
trace_buf.index = remaining;
|
||||
}
|
||||
if (comptime bun.Environment.isLinux and !bun.Environment.isMusl) {
|
||||
var addr_buf_libc: [20]usize = undefined;
|
||||
var trace_buf_libc: std.builtin.StackTrace = .{
|
||||
.index = 0,
|
||||
.instruction_addresses = &addr_buf_libc,
|
||||
};
|
||||
captureLibcBacktrace(desired_begin_addr, &trace_buf_libc);
|
||||
// Use stack trace from glibc's backtrace() if it has more frames
|
||||
if (trace_buf_libc.index > trace_buf.index) {
|
||||
addr_buf = addr_buf_libc;
|
||||
trace_buf.index = trace_buf_libc.index;
|
||||
}
|
||||
} else {
|
||||
// Fall back to Zig's stack capture on other platforms
|
||||
std.debug.captureStackTrace(desired_begin_addr, &trace_buf);
|
||||
}
|
||||
|
||||
break :get_backtrace &trace_buf;
|
||||
break :blk &trace_buf;
|
||||
};
|
||||
|
||||
if (debug_trace) {
|
||||
|
||||
Reference in New Issue
Block a user