From d6ab68043c25d06b9e2ca3e84616c0abc9e51b0a Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 24 May 2024 14:50:35 -0700 Subject: [PATCH] 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. This uses a Rust reimplementation of zstd (the ruzstd crate). This has the benefit of simplifying the build process, but this crate is less used and admittedly slower than the zstd crate that binds to the C libzstd. [0] https://maskray.me/blog/2022-09-09-zstd-compressed-debug-sections --- .github/workflows/main.yml | 16 ++++++++++++++++ Cargo.lock | 8 ++++++++ Cargo.toml | 1 + crates/as-if-std/Cargo.toml | 3 ++- src/symbolize/gimli/elf.rs | 35 ++++++++++++++++++++++++++--------- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0103a099..b3201166 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 @@ -55,6 +61,12 @@ jobs: shell: bash if: contains(matrix.rust, 'i686') + # 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') + - name: Enable collapse_debuginfo based on version run: echo RUSTFLAGS="--cfg dbginfo=\"collapsible\" $RUSTFLAGS" >> $GITHUB_ENV shell: bash @@ -80,6 +92,10 @@ jobs: if: contains(matrix.os, 'ubuntu') env: RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib" + - run: cargo test + if: contains(matrix.os, 'ubuntu-24.04') || contains(matrix.rust, 'nightly') + 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 8f4388b0..ea6b625f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", + "ruzstd", "windows-targets", ] @@ -43,6 +44,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", + "ruzstd", "serde", "windows-targets", ] @@ -150,6 +152,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "ruzstd" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c3938e133aac070997ddc684d4b393777d293ba170f2988c8fd5ea2ad4ce21" + [[package]] name = "serde" version = "1.0.203" diff --git a/Cargo.toml b/Cargo.toml index 00e9b246..46ee959a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ windows-targets = "0.52.6" [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] miniz_oxide = { version = "0.8", default-features = false } +ruzstd = { version = "0.7.2", default-features = false } addr2line = { version = "0.24.0", default-features = false } libc = { version = "0.2.156", default-features = false } diff --git a/crates/as-if-std/Cargo.toml b/crates/as-if-std/Cargo.toml index a9c29cec..092905fe 100644 --- a/crates/as-if-std/Cargo.toml +++ b/crates/as-if-std/Cargo.toml @@ -18,6 +18,7 @@ libc = { version = "0.2.156", default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] miniz_oxide = { version = "0.8", optional = true, default-features = false } +ruzstd = { version = "0.7.2", optional = true, default-features = false } addr2line = { version = "0.24.0", optional = true, default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] @@ -31,7 +32,7 @@ windows-targets = "0.52.6" [features] default = ['backtrace'] -backtrace = ['addr2line', 'miniz_oxide', 'object'] +backtrace = ['addr2line', 'miniz_oxide', 'object', 'ruzstd'] std = [] [lints.rust] diff --git a/src/symbolize/gimli/elf.rs b/src/symbolize/gimli/elf.rs index 82c53df0..f920a6fe 100644 --- a/src/symbolize/gimli/elf.rs +++ b/src/symbolize/gimli/elf.rs @@ -9,7 +9,9 @@ use super::{gimli, Context, Endian, EndianSlice, Mapping, Stash, Vec}; use alloc::sync::Arc; use core::convert::{TryFrom, TryInto}; use core::str; -use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED}; +use object::elf::{ + ELFCOMPRESS_ZLIB, ELFCOMPRESS_ZSTD, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED, +}; use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym}; use object::read::StringTable; use object::{BigEndian, Bytes, NativeEndian}; @@ -213,7 +215,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. @@ -221,14 +224,21 @@ 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); + } + 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 @@ -347,6 +357,13 @@ fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> { } } +fn decompress_zstd(input: &[u8], output: &mut [u8]) -> Option<()> { + use ruzstd::io::Read; + + let mut decoder = ruzstd::StreamingDecoder::new(input).ok()?; + decoder.read_exact(output).ok() +} + const DEBUG_PATH: &[u8] = b"/usr/lib/debug"; fn debug_path_exists() -> bool {