From 78167bb503a66a941f302084bc54eebf74c56636 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 24 May 2024 06:20:55 -0700 Subject: [PATCH 1/2] Support zstd-compressed ELF sections. zstd has been introduced as an alternative to zlib for the compression of debug sections.[0] Toolchain support is widely present at this time but lack of support in backtrace is a severe limitation on using this feature in Rust programs. Unlike zlib compression, the most widely used Rust library for zstd is a set of bindings to the zstd C library. There does exist a Rust reimplementation (ruzstd) however the author claims it is significantly slower than the C library and it sees substantially less usage, so I have not used it here. Using the C library poses challenges because it requires compiling native code. I made a few adjustments to the CI configuration to make that possible on all platforms, and pinned the zstd crate to a slightly older version due to gyscos/zstd-rs#271 breaking the crate on wasm targets on the very latest version. Additionally, I've disabled this entirely on illumos rather than try to get native code to compile successfully there. [0] https://maskray.me/blog/2022-09-09-zstd-compressed-debug-sections --- .github/workflows/main.yml | 16 +++++++ Cargo.lock | 49 ++++++++++++++++++++ Cargo.toml | 3 ++ ci/docker/arm-linux-androideabi/Dockerfile | 1 + ci/docker/armv7-linux-androideabi/Dockerfile | 1 + ci/docker/i686-linux-android/Dockerfile | 1 + crates/as-if-std/Cargo.toml | 5 +- src/symbolize/gimli/elf.rs | 32 +++++++++---- 8 files changed, 99 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ed2863cf..ca8a4b81c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,12 @@ jobs: rust: beta - os: ubuntu-20.04 rust: nightly + - os: ubuntu-24.04 + rust: stable + - os: ubuntu-24.04 + rust: beta + - os: ubuntu-24.04 + rust: nightly - os: macos-latest rust: stable - os: macos-latest @@ -48,6 +54,12 @@ jobs: - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV shell: bash + # Starting with Ubuntu 22.04 libc6-dbg is needed. + - name: Install libc debug info + run: sudo apt-get install -y libc6-dbg + shell: bash + if: contains(matrix.os, 'ubuntu-24.04') + # full fidelity of backtraces on 32-bit msvc requires frame pointers, so # enable that for our tests - name: Force frame pointers @@ -85,6 +97,10 @@ jobs: if: contains(matrix.os, 'ubuntu') env: RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib-gnu" + - run: cargo test + if: contains(matrix.os, 'ubuntu-24.04') + env: + RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zstd" # Test that, on macOS, packed/unpacked debuginfo both work - run: cargo clean && cargo test diff --git a/Cargo.lock b/Cargo.lock index 52472f32e..d22781de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", + "zstd", ] [[package]] @@ -46,6 +47,7 @@ dependencies = [ "rustc-demangle", "serde", "winapi", + "zstd", ] [[package]] @@ -53,6 +55,10 @@ name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +dependencies = [ + "jobserver", + "libc", +] [[package]] name = "cfg-if" @@ -87,6 +93,15 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "libc" version = "0.2.147" @@ -127,6 +142,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -209,3 +230,31 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index bf8cf869d..72d062c7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,9 @@ miniz_oxide = { version = "0.7.0", default-features = false } addr2line = { version = "0.22.0", default-features = false } libc = { version = "0.2.146", default-features = false } +[target.'cfg(not(any(all(windows, target_env = "msvc", not(target_vendor = "uwp")), target_os = "illumos")))'.dependencies] +zstd = { version = "= 0.13.0", default-features = false } + [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] version = "0.35.0" default-features = false diff --git a/ci/docker/arm-linux-androideabi/Dockerfile b/ci/docker/arm-linux-androideabi/Dockerfile index 7471dcb73..e2fb5e08a 100644 --- a/ci/docker/arm-linux-androideabi/Dockerfile +++ b/ci/docker/arm-linux-androideabi/Dockerfile @@ -15,4 +15,5 @@ ENV PATH=$PATH:/android-toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin # TODO: run tests in an emulator eventually ENV CARGO_TARGET_ARM_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi19-clang \ + CC=armv7a-linux-androideabi19-clang \ CARGO_TARGET_ARM_LINUX_ANDROIDEABI_RUNNER=echo diff --git a/ci/docker/armv7-linux-androideabi/Dockerfile b/ci/docker/armv7-linux-androideabi/Dockerfile index 543fcd243..a251e2dc3 100644 --- a/ci/docker/armv7-linux-androideabi/Dockerfile +++ b/ci/docker/armv7-linux-androideabi/Dockerfile @@ -15,4 +15,5 @@ ENV PATH=$PATH:/android-toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin # TODO: run tests in an emulator eventually ENV CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi19-clang \ + CC=armv7a-linux-androideabi19-clang \ CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_RUNNER=echo diff --git a/ci/docker/i686-linux-android/Dockerfile b/ci/docker/i686-linux-android/Dockerfile index 61cb9d075..bfac4f015 100644 --- a/ci/docker/i686-linux-android/Dockerfile +++ b/ci/docker/i686-linux-android/Dockerfile @@ -15,4 +15,5 @@ ENV PATH=$PATH:/android-toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin # TODO: run tests in an emulator eventually ENV CARGO_TARGET_I686_LINUX_ANDROID_LINKER=i686-linux-android19-clang \ + CC=i686-linux-android19-clang \ CARGO_TARGET_I686_LINUX_ANDROID_RUNNER=echo diff --git a/crates/as-if-std/Cargo.toml b/crates/as-if-std/Cargo.toml index b451c024a..488fffd28 100644 --- a/crates/as-if-std/Cargo.toml +++ b/crates/as-if-std/Cargo.toml @@ -20,6 +20,9 @@ libc = { version = "0.2.146", default-features = false } miniz_oxide = { version = "0.7.0", optional = true, default-features = false } addr2line = { version = "0.22.0", optional = true, default-features = false } +[target.'cfg(not(any(all(windows, target_env = "msvc", not(target_vendor = "uwp")), target_os = "illumos")))'.dependencies] +zstd = { version = "= 0.13.0", optional = true, default-features = false } + [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] version = "0.35.0" default-features = false @@ -32,7 +35,7 @@ cc = "1.0.90" [features] default = ['backtrace'] -backtrace = ['addr2line', 'miniz_oxide', 'object'] +backtrace = ['addr2line', 'miniz_oxide', 'object', 'zstd'] std = [] [lints.rust] diff --git a/src/symbolize/gimli/elf.rs b/src/symbolize/gimli/elf.rs index 906a30054..20f2d3f7e 100644 --- a/src/symbolize/gimli/elf.rs +++ b/src/symbolize/gimli/elf.rs @@ -7,6 +7,8 @@ use super::{gimli, Context, Endian, EndianSlice, Mapping, Stash, Vec}; use alloc::sync::Arc; use core::convert::{TryFrom, TryInto}; use core::str; +#[cfg(not(target_os = "illumos"))] +use object::elf::ELFCOMPRESS_ZSTD; use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED}; use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym}; use object::read::StringTable; @@ -170,7 +172,8 @@ impl<'a> Object<'a> { let mut data = Bytes(section.data(self.endian, self.data).ok()?); // Check for DWARF-standard (gABI) compression, i.e., as generated - // by ld's `--compress-debug-sections=zlib-gabi` flag. + // by ld's `--compress-debug-sections=zlib-gabi` and + // `--compress-debug-sections=zstd` flags. let flags: u64 = section.sh_flags(self.endian).into(); if (flags & u64::from(SHF_COMPRESSED)) == 0 { // Not compressed. @@ -178,14 +181,22 @@ impl<'a> Object<'a> { } let header = data.read::<::CompressionHeader>().ok()?; - if header.ch_type(self.endian) != ELFCOMPRESS_ZLIB { - // Zlib compression is the only known type. - return None; + match header.ch_type(self.endian) { + ELFCOMPRESS_ZLIB => { + let size = usize::try_from(header.ch_size(self.endian)).ok()?; + let buf = stash.allocate(size); + decompress_zlib(data.0, buf)?; + return Some(buf); + } + #[cfg(not(target_os = "illumos"))] + ELFCOMPRESS_ZSTD => { + let size = usize::try_from(header.ch_size(self.endian)).ok()?; + let buf = stash.allocate(size); + decompress_zstd(data.0, buf)?; + return Some(buf); + } + _ => return None, // Unknown compression type. } - let size = usize::try_from(header.ch_size(self.endian)).ok()?; - let buf = stash.allocate(size); - decompress_zlib(data.0, buf)?; - return Some(buf); } // Check for the nonstandard GNU compression format, i.e., as generated @@ -304,6 +315,11 @@ fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> { } } +#[cfg(not(target_os = "illumos"))] +fn decompress_zstd(input: &[u8], output: &mut [u8]) -> Option<()> { + zstd::stream::copy_decode(input, output).ok() +} + const DEBUG_PATH: &[u8] = b"/usr/lib/debug"; fn debug_path_exists() -> bool { From eda500c64378e18ba2de47585fe86ac1a0bea9b8 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 24 May 2024 17:34:47 -0700 Subject: [PATCH 2/2] Clean up cfgs for crate includes. The existing dependency cfgs are a mess. Rework them based on a few principles. 1. Windows targets use PDBs if compiled with MSVC, or DWARF if compiled with the GNU toolchain. Gate addr2line and object, which is only used with addr2line, on not(msvc). 2. Windows targets never use ELF. Gate miniz_oxide and zstd, which are used for ELF compression, on not(windows). 3. libc is used by the addr2line code, Fuschia specific code (which implies DWARF), and libunwind specific code (which implies DWARF), so gate it on not(msvc) as well. 4. The Windows UWP target only allows a reduced set of APIs. That set of APIs excludes some used by addr2line, so additionally gate all the DWARF related crates on not(uwp). --- Cargo.toml | 14 ++++++++++---- crates/as-if-std/Cargo.toml | 10 ++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72d062c7c..941f7a0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,15 +39,21 @@ cpp_demangle = { default-features = false, version = "0.4.0", optional = true, f "alloc", ] } -[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] -miniz_oxide = { version = "0.7.0", default-features = false } +# MSVC uses PDB, not DWARF. But UWP doesn't allow for APIs gimli (via addr2line) uses, +# so skip that too. +[target.'cfg(not(any(target_env = "msvc", target_vendor = "uwp")))'.dependencies] addr2line = { version = "0.22.0", default-features = false } libc = { version = "0.2.146", default-features = false } -[target.'cfg(not(any(all(windows, target_env = "msvc", not(target_vendor = "uwp")), target_os = "illumos")))'.dependencies] +# Windows uses PE, not ELF, so libraries for ELF compression are unnecessary. +[target.'cfg(not(windows))'.dependencies] +miniz_oxide = { version = "0.7.0", default-features = false } + +# Also used for ELF compression, but doesn't build on illumos in CI. +[target.'cfg(not(any(windows, target_os = "illumos")))'.dependencies] zstd = { version = "= 0.13.0", default-features = false } -[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] +[target.'cfg(not(any(target_env = "msvc", target_vendor = "uwp")))'.dependencies.object] version = "0.35.0" default-features = false features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive'] diff --git a/crates/as-if-std/Cargo.toml b/crates/as-if-std/Cargo.toml index 488fffd28..73e3ce5fa 100644 --- a/crates/as-if-std/Cargo.toml +++ b/crates/as-if-std/Cargo.toml @@ -14,16 +14,18 @@ bench = false [dependencies] cfg-if = "1.0" rustc-demangle = "0.1.21" + +[target.'cfg(not(any(target_env = "msvc", target_vendor = "uwp")))'.dependencies] libc = { version = "0.2.146", default-features = false } +addr2line = { version = "0.22.0", optional = true, default-features = false } -[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] +[target.'cfg(not(windows))'.dependencies] miniz_oxide = { version = "0.7.0", optional = true, default-features = false } -addr2line = { version = "0.22.0", optional = true, default-features = false } -[target.'cfg(not(any(all(windows, target_env = "msvc", not(target_vendor = "uwp")), target_os = "illumos")))'.dependencies] +[target.'cfg(not(any(windows, target_os = "illumos")))'.dependencies] zstd = { version = "= 0.13.0", optional = true, default-features = false } -[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] +[target.'cfg(not(any(target_env = "msvc", target_vendor = "uwp")))'.dependencies.object] version = "0.35.0" default-features = false optional = true