From 2ab751892691170d86c0c09e585a9534a9c90b28 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Thu, 1 Oct 2026 16:21:57 -0700 Subject: [PATCH] feat: release builds keep their symbol tables and frame pointers - Release executables keep their symbol tables, so backtraces, crash logs, perf, gdb and Instruments name functions - Every target builds with frame pointers The Linux linker shim strips debug info itself after linking, since zig's linker drops the symbol table along with it. Assisted-by: claude-opus-5.5 --- .cargo/config.toml | 4 ++++ crates/snowbound/linux/package.sh | 2 +- platform/linux/cc.sh | 17 ++++++++++++++--- tools/RELEASE.md | 16 ++++++++++++++++ tools/release.py | 3 +-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..aa6d5b2e6d5d57a2c4430226a230a668e4b11515 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +# Frame pointers let perf, Instruments and other sampling profilers walk the stack. +# A cfg table, unlike build.rustflags, joins the flags the platform scripts give each target. +[target.'cfg(all())'] +rustflags = ["-C", "force-frame-pointers=yes"] diff --git a/crates/snowbound/linux/package.sh b/crates/snowbound/linux/package.sh index 5148246208f612efa89e62f3c4d3cd895cb1b78c..bef78477ae8b222b1f31aa24fc72bbd6d86376ff 100755 --- a/crates/snowbound/linux/package.sh +++ b/crates/snowbound/linux/package.sh @@ -10,7 +10,7 @@ mkdir -p "$target/dist" [ $# -gt 0 ] || set -- x86_64 aarch64 for arch do triple=$arch-unknown-linux-gnu - CARGO_PROFILE_RELEASE_STRIP=symbols sh "$root/platform/linux/cargo.sh" "$arch" build \ + sh "$root/platform/linux/cargo.sh" "$arch" build \ --manifest-path "$root/Cargo.toml" --release -p snowbound # PT_INTERP becomes PT_NULL, so the kernel starts the executable at `loader`'s entry, which # finds the system's dynamic loader, NixOS's too. zig rejects --no-dynamic-linker. diff --git a/platform/linux/cc.sh b/platform/linux/cc.sh index e0ab47ba4099c1fe5b9a9334fffec0eb62fd5200..6c24c8eefff93f4ab6dc7ad2a3f9538b5bad290b 100755 --- a/platform/linux/cc.sh +++ b/platform/linux/cc.sh @@ -1,8 +1,19 @@ #!/bin/sh # zig cc for $ZIG_TARGET, as `cargo.sh` sets it: the C compiler and linker. zig rejects -# rustc's --target, --no-undefined-version and -O1. +# rustc's --target, --no-undefined-version and -O1, and strips the symbol table with the debug +# info, so the debug info goes after linking. +strip= output= previous= for arg do shift - case $arg in --target=* | -Wl,--no-undefined-version | -Wl,-O1) ;; *) set -- "$@" "$arg" ;; esac + [ "$previous" = -o ] && output=$arg + previous=$arg + case $arg in + --target=* | -Wl,--no-undefined-version | -Wl,-O1) ;; + -Wl,--strip-debug) strip=1 ;; + *) set -- "$@" "$arg" ;; + esac done -exec zig cc -target "$ZIG_TARGET" "$@" +zig cc -target "$ZIG_TARGET" "$@" || exit +[ -z "$strip" ] || + "$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/rust-objcopy" \ + --strip-debug "$output" diff --git a/tools/RELEASE.md b/tools/RELEASE.md index db4fb11faaebc1f1c5b4c858a0d97e5fadb86243..9932ebe78b20620f7cf3ce25ef6252c8f41f2d02 100644 --- a/tools/RELEASE.md +++ b/tools/RELEASE.md @@ -146,6 +146,22 @@ version above 2.17. The Linux executable links only libc, libm, libpthread and libdl; Wayland, X11, xkbcommon, EGL, Vulkan, fontconfig, GStreamer and Enchant are loaded at run time. +## Symbols and frame pointers + +Every executable keeps its symbol table, so backtraces, crash logs, `perf`, gdb +and Instruments name functions; Cargo's release profile strips only debug info +(`platform/linux/cc.sh` does that itself, as zig's linker would strip both). +`.cargo/config.toml` builds every target with frame pointers, and Rust's +standard library ships with them. The symbols cost about a fifth: Linux x86_64 +grows from 53 to 62 MB, Windows x86_64 from 55 to 72 MB, and the macOS app +already carried them. Line tables would take an executable to some 250 MB, and +a dSYM adds 34 MB zipped per Mac architecture, so neither ships; build with +`CARGO_PROFILE_RELEASE_DEBUG=line-tables-only` for files and lines. Windows +tools that read only PDBs see no names: the Rust targets here emit DWARF, from +which lld's PDB keeps only global symbols. Linux's crash log gives a signal's +frames as `snowbound(+0x1a2b3c)`, since glibc's `backtrace_symbols_fd` reads only +dynamic symbols; `addr2line -f -e snowbound 0x1a2b3c` names them. + ## In the app `update.rs` runs one thread. With automatic checks on (Options, General) and a diff --git a/tools/release.py b/tools/release.py index 613c355cda313fc689194130fa70cbe8cd4390db..4d07a236a93c0e5b46ed9f0a15959039cb1c224b 100755 --- a/tools/release.py +++ b/tools/release.py @@ -188,8 +188,7 @@ def build_windows(architectures): Windows it supports.""" built = {} for arch in architectures: - run(['sh', ROOT / 'platform/windows/cargo.sh', arch, 'build', '--release', '-p', 'snowbound'], - env={**os.environ, 'CARGO_PROFILE_RELEASE_STRIP': 'symbols'}) + run(['sh', ROOT / 'platform/windows/cargo.sh', arch, 'build', '--release', '-p', 'snowbound']) built[f'windows-{arch}'] = ROOT / f'target/windows/{WINDOWS[arch]}/release/snowbound.exe' return built -- 2.54.0