| author | |
| committer | |
| log | 2ab751892691170d86c0c09e585a9534a9c90b28 |
| tree | 872ba4d224a8e387cc4887ebad8256b26a43ab7a |
| parent | 7069fb2abee3f488a947853152d7830c53c650f0 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
- 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.55 files changed, 36 insertions(+), 6 deletions(-)
.cargo/config.toml created+4| ... | @@ -0,0 +1,4 @@ | ||
| 1 | # Frame pointers let perf, Instruments and other sampling profilers walk the stack. | ||
| 2 | # A cfg table, unlike build.rustflags, joins the flags the platform scripts give each target. | ||
| 3 | [target.'cfg(all())'] | ||
| 4 | rustflags = ["-C", "force-frame-pointers=yes"] | ||
crates/snowbound/linux/package.sh+1-1| ... | @@ -10,7 +10,7 @@ mkdir -p "$target/dist" | ... | @@ -10,7 +10,7 @@ mkdir -p "$target/dist" |
| 10 | [ $# -gt 0 ] || set -- x86_64 aarch64 | 10 | [ $# -gt 0 ] || set -- x86_64 aarch64 |
| 11 | for arch do | 11 | for arch do |
| 12 | triple=$arch-unknown-linux-gnu | 12 | triple=$arch-unknown-linux-gnu |
| 13 | CARGO_PROFILE_RELEASE_STRIP=symbols sh "$root/platform/linux/cargo.sh" "$arch" build \ | 13 | sh "$root/platform/linux/cargo.sh" "$arch" build \ |
| 14 | --manifest-path "$root/Cargo.toml" --release -p snowbound | 14 | --manifest-path "$root/Cargo.toml" --release -p snowbound |
| 15 | # PT_INTERP becomes PT_NULL, so the kernel starts the executable at `loader`'s entry, which | 15 | # PT_INTERP becomes PT_NULL, so the kernel starts the executable at `loader`'s entry, which |
| 16 | # finds the system's dynamic loader, NixOS's too. zig rejects --no-dynamic-linker. | 16 | # finds the system's dynamic loader, NixOS's too. zig rejects --no-dynamic-linker. |
platform/linux/cc.sh+14-3| ... | @@ -1,8 +1,19 @@ | ... | @@ -1,8 +1,19 @@ |
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | # zig cc for $ZIG_TARGET, as `cargo.sh` sets it: the C compiler and linker. zig rejects | 2 | # zig cc for $ZIG_TARGET, as `cargo.sh` sets it: the C compiler and linker. zig rejects |
| 3 | # rustc's --target, --no-undefined-version and -O1. | 3 | # rustc's --target, --no-undefined-version and -O1, and strips the symbol table with the debug |
| 4 | # info, so the debug info goes after linking. | ||
| 5 | strip= output= previous= | ||
| 4 | for arg do | 6 | for arg do |
| 5 | shift | 7 | shift |
| 6 | case $arg in --target=* | -Wl,--no-undefined-version | -Wl,-O1) ;; *) set -- "$@" "$arg" ;; esac | 8 | [ "$previous" = -o ] && output=$arg |
| 9 | previous=$arg | ||
| 10 | case $arg in | ||
| 11 | --target=* | -Wl,--no-undefined-version | -Wl,-O1) ;; | ||
| 12 | -Wl,--strip-debug) strip=1 ;; | ||
| 13 | *) set -- "$@" "$arg" ;; | ||
| 14 | esac | ||
| 7 | done | 15 | done |
| 8 | exec zig cc -target "$ZIG_TARGET" "$@" | 16 | zig cc -target "$ZIG_TARGET" "$@" || exit |
| 17 | [ -z "$strip" ] || | ||
| 18 | "$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/rust-objcopy" \ | ||
| 19 | --strip-debug "$output" |
tools/RELEASE.md+16| ... | @@ -146,6 +146,22 @@ version above 2.17. The Linux executable links only libc, libm, libpthread and | ... | @@ -146,6 +146,22 @@ version above 2.17. The Linux executable links only libc, libm, libpthread and |
| 146 | libdl; Wayland, X11, xkbcommon, EGL, Vulkan, fontconfig, GStreamer and Enchant | 146 | libdl; Wayland, X11, xkbcommon, EGL, Vulkan, fontconfig, GStreamer and Enchant |
| 147 | are loaded at run time. | 147 | are loaded at run time. |
| 148 | 148 | ||
| 149 | ## Symbols and frame pointers | ||
| 150 | |||
| 151 | Every executable keeps its symbol table, so backtraces, crash logs, `perf`, gdb | ||
| 152 | and Instruments name functions; Cargo's release profile strips only debug info | ||
| 153 | (`platform/linux/cc.sh` does that itself, as zig's linker would strip both). | ||
| 154 | `.cargo/config.toml` builds every target with frame pointers, and Rust's | ||
| 155 | standard library ships with them. The symbols cost about a fifth: Linux x86_64 | ||
| 156 | grows from 53 to 62 MB, Windows x86_64 from 55 to 72 MB, and the macOS app | ||
| 157 | already carried them. Line tables would take an executable to some 250 MB, and | ||
| 158 | a dSYM adds 34 MB zipped per Mac architecture, so neither ships; build with | ||
| 159 | `CARGO_PROFILE_RELEASE_DEBUG=line-tables-only` for files and lines. Windows | ||
| 160 | tools that read only PDBs see no names: the Rust targets here emit DWARF, from | ||
| 161 | which lld's PDB keeps only global symbols. Linux's crash log gives a signal's | ||
| 162 | frames as `snowbound(+0x1a2b3c)`, since glibc's `backtrace_symbols_fd` reads only | ||
| 163 | dynamic symbols; `addr2line -f -e snowbound 0x1a2b3c` names them. | ||
| 164 | |||
| 149 | ## In the app | 165 | ## In the app |
| 150 | 166 | ||
| 151 | `update.rs` runs one thread. With automatic checks on (Options, General) and a | 167 | `update.rs` runs one thread. With automatic checks on (Options, General) and a |
tools/release.py+1-2| ... | @@ -188,8 +188,7 @@ def build_windows(architectures): | ... | @@ -188,8 +188,7 @@ def build_windows(architectures): |
| 188 | Windows it supports.""" | 188 | Windows it supports.""" |
| 189 | built = {} | 189 | built = {} |
| 190 | for arch in architectures: | 190 | for arch in architectures: |
| 191 | run(['sh', ROOT / 'platform/windows/cargo.sh', arch, 'build', '--release', '-p', 'snowbound'], | 191 | run(['sh', ROOT / 'platform/windows/cargo.sh', arch, 'build', '--release', '-p', 'snowbound']) |
| 192 | env={**os.environ, 'CARGO_PROFILE_RELEASE_STRIP': 'symbols'}) | ||
| 193 | built[f'windows-{arch}'] = ROOT / f'target/windows/{WINDOWS[arch]}/release/snowbound.exe' | 192 | built[f'windows-{arch}'] = ROOT / f'target/windows/{WINDOWS[arch]}/release/snowbound.exe' |
| 194 | return built | 193 | return built |
| 195 | 194 |