From 9be65a8dfec1844e3b91e2948e1adea39bdf6931 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 2 Sep 2026 19:38:12 +0000 Subject: [PATCH] Strip local symbols from macOS and Linux release bundles (#63429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure if this PR is 100% good, since it removes symbols from local traces, leaving addresses only — but this what Windows seem to do already, and we have those symbols on Sentry to restore from, which were needed to normally read Linux perf traces already (see `perf.zip` in https://github.com/zed-industries/zed/issues/59689#issuecomment-5452720248) On the upside, uncompressed binaries' are now ~100MB less, which is a lot — hence decided to open the PR for the review ------------------- Strips debug info and local symbols from shipped macOS and Linux binaries, matching what Windows already does with PDBs: `bundle-mac` now always runs `dsymutil` before a new `strip -x` step, and `bundle-linux` adds `--discard-all` plus `zed.dbg`/`remote_server.dbg` extraction via `--only-keep-debug`. Sentry uploads are unchanged and precede stripping. The macOS binary UUID and the GNU build id survive stripping, so user-provided traces stay recoverable without rebuilding at the reporter's commit: `sample` output resolves via `atos` against the archived `zed.dwarf`, and `perf.data` via `perf buildid-cache` against the Sentry-archived binary. zed binary: macOS (2026-08-29 nightly): 420478976 -> 316503984 bytes (-24.7%, -104.0MB) Linux (v1.17.2 aarch64): 439417448 -> 336967256 bytes (-23.3%, -102.5MB) Downloads shrink far less, symbols compress ~7:1: the macOS DMG loses only ~15MB. Release Notes: - Improved installed size on macOS and Linux by stripping debug symbols from release binaries (roughly -25%) --- docs/src/development/linux.md | 11 ++++++--- docs/src/development/macos.md | 34 ++++++++++++++++++++++++++++ script/bundle-linux | 13 +++++++---- script/bundle-mac | 42 ++++++++++++++++++++++++----------- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/docs/src/development/linux.md b/docs/src/development/linux.md index 3a627f6e74..2edeecd438 100644 --- a/docs/src/development/linux.md +++ b/docs/src/development/linux.md @@ -129,7 +129,7 @@ Use this when Zed is using a lot of CPU. It is not useful for hangs. - Perf record: Run `sudo perf record -g --call-graph dwarf -p `, wait a few seconds to gather data, then press Ctrl+C. You should now have a `perf.data` file. - The `--call-graph dwarf` part records the callers of every sampled function: without it, the profile shows isolated hot symbols with no way to tell who invoked them, which is rarely enough to act on. + The `--call-graph dwarf` option records callers of every sampled function and unwinds through the `.eh_frame` data kept in stripped release binaries; plain `-g` does not work without frame pointers. - Make the output file user owned: run `sudo chown $USER:$USER perf.data` @@ -143,8 +143,13 @@ The `perf.data` file can be sent to Zed together with the exact commit. This can be done by Zed staff. -- Build Zed with symbols: - Check out the commit found previously and modify `Cargo.toml`. +Released Linux binaries are stripped, but the unstripped binary for every release is archived in Sentry (uploaded by `script/bundle-linux`). +Download it from the Sentry project's Debug Files page (search by the release version or the binary's build id), then continue with the `perf buildid-cache` step below. +Stripping preserves the build id, so the downloaded binary matches the user's `perf.data`. + +Alternatively, rebuild the binary with symbols: + +- Check out the commit found previously and modify `Cargo.toml`. Apply the following diff, then make a release build. ```diff diff --git a/docs/src/development/macos.md b/docs/src/development/macos.md index 6315ddcab8..42f07aa4f2 100644 --- a/docs/src/development/macos.md +++ b/docs/src/development/macos.md @@ -106,6 +106,40 @@ UPDATE_BASELINE=1 cargo run -p zed --bin zed_visual_test_runner --features visua > **Note:** In the future, baselines may be stored externally. For now, they > remain local-only to keep the git repository lightweight. +## Sampling released builds + +How to get a symbolicated CPU profile from a released (non-dev) Zed instance. +Use this when Zed is using a lot of CPU. + +Released macOS binaries are stripped of local symbols, so `sample` and Instruments show raw addresses for most frames. +The debug symbols for every release are archived: `script/bundle-mac` uploads `zed.dwarf` to Sentry before stripping. + +### During the incident + +- Run `sample Zed 10 -f zed-sample.txt` (adjust the process name for Preview or Nightly). +- Get the exact build: type {#action zed::About} in the command palette and copy the version and commit. + +The `zed-sample.txt` file can be sent to Zed together with the exact version. + +### Later + +This can be done by Zed staff. + +- Find the binary UUID in the `Binary Images` section at the bottom of the sample output. +- Download the matching `zed.dwarf` from the Sentry project's Debug Files page by searching for that UUID. +- Resolve the addresses: + `atos -o zed.dwarf -l ` + Each unresolved frame in the sample output prints its `load address` and absolute address. + +To profile a released build on your own machine with full symbols, download the matching `zed.dwarf`, convert it into a `.dSYM` bundle Spotlight can index, and re-run `sample`: + +```sh +mkdir -p Zed.dSYM/Contents/Resources/DWARF +cp zed.dwarf Zed.dSYM/Contents/Resources/DWARF/zed +``` + +Frames then resolve automatically, including file and line information. + ## Troubleshooting ### Error compiling metal shaders diff --git a/script/bundle-linux b/script/bundle-linux index 1664fbf5dc..e7525ffbdd 100755 --- a/script/bundle-linux +++ b/script/bundle-linux @@ -121,12 +121,17 @@ else fi fi -# Strip debug symbols and save them for upload to DigitalOcean. +# Split debug symbols into local .dbg files, then strip debug info and local +# symbols from the shipped binaries. Sentry receives the full unstripped +# binaries above; the .dbg files keep symbols recoverable when that upload +# is skipped. # We use llvm-objcopy because GNU objcopy on older distros (e.g. Ubuntu 20.04) # doesn't understand CREL sections produced by newer LLVM. -llvm-objcopy --strip-debug "${target_dir}/${target_triple}/release/zed" -llvm-objcopy --strip-debug "${target_dir}/${target_triple}/release/cli" -llvm-objcopy --strip-debug "${target_dir}/${remote_server_triple}/release/remote_server" +llvm-objcopy --only-keep-debug "${target_dir}/${target_triple}/release/zed" "${target_dir}/${target_triple}/release/zed.dbg" +llvm-objcopy --only-keep-debug "${target_dir}/${remote_server_triple}/release/remote_server" "${target_dir}/${remote_server_triple}/release/remote_server.dbg" +llvm-objcopy --strip-debug --discard-all "${target_dir}/${target_triple}/release/zed" +llvm-objcopy --strip-debug --discard-all "${target_dir}/${target_triple}/release/cli" +llvm-objcopy --strip-debug --discard-all "${target_dir}/${remote_server_triple}/release/remote_server" # Ensure that remote_server does not depend on libssl nor libcrypto, as we got rid of these deps. if ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl'; then diff --git a/script/bundle-mac b/script/bundle-mac index 477efb5a4f..4194d0f7ba 100755 --- a/script/bundle-mac +++ b/script/bundle-mac @@ -292,24 +292,30 @@ function sign_binary() { fi } +debug_symbols_extracted=false +function extract_debug_symbols() { + if [ "$debug_symbols_extracted" = true ]; then + return + fi + debug_symbols_extracted=true + if ! dsymutil --flat "target/${target_triple}/${target_dir}/zed" 2> target/dsymutil.log; then + echo "dsymutil failed" + cat target/dsymutil.log + exit 1 + fi + if ! dsymutil --flat "target/${target_triple}/${target_dir}/remote_server" 2> target/dsymutil.log; then + echo "dsymutil failed" + cat target/dsymutil.log + exit 1 + fi +} + function upload_debug_symbols() { if [ "$local_install" = true ]; then echo "local install; skipping sentry upload." elif [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then echo "Uploading zed debug symbols to sentry..." - exe_path="target/${target_triple}/release/Zed" - if ! dsymutil --flat "target/${target_triple}/${target_dir}/zed" 2> target/dsymutil.log; then - echo "dsymutil failed" - cat target/dsymutil.log - exit 1 - fi - if ! dsymutil --flat "target/${target_triple}/${target_dir}/remote_server" 2> target/dsymutil.log; then - echo "dsymutil failed" - cat target/dsymutil.log - exit 1 - fi - # note: this uploads the unstripped binary which is needed because it contains - # .eh_frame data for stack unwinding. see https://github.com/getsentry/symbolic/issues/783 + extract_debug_symbols # Try uploading up to 3 times for attempt in 1 2 3; do echo "Sentry upload attempt $attempt..." @@ -332,6 +338,16 @@ function upload_debug_symbols() { upload_debug_symbols +# Sentry receives the extracted zed.dwarf, not the binary, so debug symbols +# must be extracted before the binaries are stripped below. The .dwarf files +# also keep symbols recoverable when the sentry upload is skipped. +if [[ "$target_dir" == "release" && "$local_install" != true ]]; then + extract_debug_symbols + strip -x "target/${target_triple}/${target_dir}/zed" + strip -x "target/${target_triple}/${target_dir}/cli" + strip -x "target/${target_triple}/${target_dir}/remote_server" +fi + cp target/${target_triple}/${target_dir}/zed "${app_path}/Contents/MacOS/zed" cp target/${target_triple}/${target_dir}/cli "${app_path}/Contents/MacOS/cli" sign_app_binaries