From e6c9f1726cca910c464fc807aefebc404c8e7a12 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 3 Sep 2026 13:29:50 -0300 Subject: crash: a panic record must not be able to hang the process it is recording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversarial re-review, confirmed against std's source and then measured. `captureCurrentStackTrace` is not the safe half of `writeCurrentStackTrace`. `StackIterator.init` picks the `.di` strategy whenever `SelfInfo` can unwind, `stratOk` accepts `.di` regardless of `allow_unsafe_unwind`, and `.di` takes `SelfInfo`'s rwlock EXCLUSIVELY on its first call — the only kind of call a panic record makes — across `dl_iterate_phdr`, a DWARF CFI machine and an allocation. A panic in there (a smashed stack is a leading reason to be in a panic handler at all) leaves the lock held, because the unlock is a `defer` in a frame that never returns, and `defaultPanic` then waits on it for the life of the process. A crash becomes a hang, which is worse than what this file was added to improve on. The frames stay on stderr, where defaultPanic prints them under the staging that makes them safe; the record keeps what can be gathered without asking the process any questions. ONE record per process, never released. With the guard released on the way out, one panic wrote two records: the real message, then "reached unreachable code" under it. That second panic is this handler's own `vaxis.recover()` running a second time — it closes the vaxis tty and never clears the global saying there is one, so the double close is `recoverableOsBugDetected` and an `unreachable` in a Debug build. Guarded now in both the panic and the segfault handler; that half is a fix older than the crash file. `clock_gettime`'s return is checked, unlike dump.zig's, because a failure here leaves `ts` undefined and an undefined large-positive `sec` walks `calculateYearDay`'s u16 year past 65535 and overflow-panics inside the panic handler. Debug fills it with 0xaa and lands in 1970, which is why it reads as harmless. Verified end to end with a temporary probe: one panic, one record. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf --- src/macos.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/macos.zig') diff --git a/src/macos.zig b/src/macos.zig index a9c9d0f3..f2e68a85 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -50,7 +50,7 @@ const crash = @import("crash.zig"); /// terminal to restore either, which is the rest of what main.zig's does. pub const panic = std.debug.FullPanic(struct { fn call(msg: []const u8, ret_addr: ?usize) noreturn { - crash.record(msg, ret_addr); + crash.record(msg); std.debug.defaultPanic(msg, ret_addr); } }.call); -- cgit v1.3