diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-03 13:10:59 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-03 13:10:59 -0300 |
| commit | ffc8c1f6f19a5dc48e98f99938b438baf9a97165 (patch) | |
| tree | 81ede7b26e6a1713597a236973014c1b64934d00 /src/main.zig | |
| parent | 3f2d6f43199d0e230490396deb50f8dc49c7b8b0 (diff) | |
| download | pardes-ffc8c1f6f19a5dc48e98f99938b438baf9a97165.tar.gz pardes-ffc8c1f6f19a5dc48e98f99938b438baf9a97165.zip | |
crash: a panic writes itself down beside the init file, where stderr cannot lose it
Every crash this program has ever had went to stderr and nowhere else, and
stderr is the one place it cannot keep anything. In the tty shell stderr IS the
screen, so the trace lands on the grid the terminal is being reset out of; the
SDL and AppKit shells have no terminal at all; a --detach session's goes
wherever its launcher left it. src/crash.zig appends a record to
<config dir>/crashes first: one line naming the build (version, commit, UTC,
os-arch, pid) and under it the panic message and the frames behind it.
RETURN ADDRESSES and not the symbolised trace, which is measured rather than
chosen. `std.debug.writeCurrentStackTrace` called from a panic handler BEFORE
defaultPanic wedges the process at 0% CPU: symbolising reads DWARF, that read
can itself panic, and the staging which turns a nested panic into "aborting due
to recursive panic" is defaultPanic's own and private. Reproduced in a
standalone build with this program's std_options_debug_io and inside a test
binary. `captureCurrentStackTrace` only walks frames, so the addresses go in
the file and `addr2line -e` finishes the job; stderr still gets the symbolised
trace from defaultPanic, unchanged.
The AppKit shell gets a panic handler of its own here too: the macOS build
roots at macos.zig, so main.zig's had never run there — in the shell with the
least useful stderr of the four. The config directory is COPIED rather than
borrowed, because that host's lives in an arena its own errdefer frees. One
record at a time, so two panicking threads cannot interleave into one buffer.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 15 |
1 files changed, 15 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig index 1fcc2770..41495a48 100644 --- a/src/main.zig +++ b/src/main.zig @@ -46,9 +46,15 @@ fn logFn( // A panic must restore the terminal (cooked mode, main screen, mouse off) // before the trace prints, or it lands garbled in a raw alt screen. recover() // no-ops unless the vaxis tty is live, so gui/tty share the handler. +// +// Then the same message and trace are appended to `<config dir>/crashes` +// BEFORE stderr gets them, because stderr is the one place this program cannot +// keep them: see crash.zig. Silent on every failure, so the fallback is +// exactly the behaviour that was here before. pub const panic = if (is_emscripten) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.FullPanic(struct { fn call(msg: []const u8, ret_addr: ?usize) noreturn { @import("vaxis").recover(); + @import("crash.zig").record(msg, ret_addr); std.debug.defaultPanic(msg, ret_addr); } }.call); @@ -338,6 +344,11 @@ fn nativeMain(init: std.process.Init) !void { opts.startup_config = found.bytes; opts.startup_config_path = found.path; opts.config_dir = found.dir; + // The panic handler above writes beside that init file, and this is the + // only place it can learn where that is — it runs with no `Options` in + // reach. Set for every native entry through this file, including the + // `--detach` daemon below, whose stderr nobody is reading. + if (found.dir) |d| @import("crash.zig").setDir(d); // `--detach` is the core with no terminal and `--attach` is a terminal // with no core, so the two together are a contradiction with no useful // reading. Refused rather than resolved by declaration order, which would @@ -425,6 +436,10 @@ fn parseCtrlKey(raw: []const u8) ?u21 { // needs its own line here. test { _ = @import("user_config.zig"); + // Reached only from the panic handler and from `nativeMain`, neither of + // which a test build analyses — so without this line the crash file has no + // test at all. + _ = @import("crash.zig"); _ = @import("allocators.zig"); _ = @import("fs_service.zig"); // acme's control filesystem, both halves, and NOT their own b.addTest |
