Skip to content

Commit

Permalink
Support zstd-compressed ELF sections.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
khuey committed Aug 24, 2024
1 parent 84b6c1d commit 45dc6bd
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 12 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -80,6 +92,11 @@ 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.os, 'ubuntu') && 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
Expand Down Expand Up @@ -248,7 +265,7 @@ jobs:
with:
submodules: true
- name: Install Rust
run: rustup update 1.65.0 --no-self-update && rustup default 1.65.0
run: rustup update 1.73.0 --no-self-update && rustup default 1.73.0
- run: cargo build

miri:
Expand Down
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ autoexamples = true
autotests = true
edition = "2021"
exclude = ["/ci/"]
rust-version = "1.65.0"
rust-version = "1.73.0"

[workspace]
members = ['crates/cpp_smoke_test', 'crates/as-if-std']
Expand All @@ -40,6 +40,7 @@ cpp_demangle = { default-features = false, version = "0.4.0", optional = true, f

[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies]
miniz_oxide = { version = "0.7.0", default-features = false }
ruzstd = { version = "0.7.0", default-features = false }
addr2line = { version = "0.24.0", default-features = false }
libc = { version = "0.2.146", default-features = false }

Expand Down
3 changes: 2 additions & 1 deletion crates/as-if-std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ libc = { version = "0.2.146", default-features = false }

[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies]
miniz_oxide = { version = "0.7.0", optional = true, default-features = false }
ruzstd = { version = "0.7.0", 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]
Expand All @@ -32,7 +33,7 @@ windows-bindgen = "0.56"

[features]
default = ['backtrace']
backtrace = ['addr2line', 'miniz_oxide', 'object']
backtrace = ['addr2line', 'miniz_oxide', 'object', 'ruzstd']
std = []

[lints.rust]
Expand Down
35 changes: 26 additions & 9 deletions src/symbolize/gimli/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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};
Expand Down Expand Up @@ -170,22 +172,30 @@ 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.
return Some(data.0);
}

let header = data.read::<<Elf as FileHeader>::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
Expand Down Expand Up @@ -304,6 +314,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 {
Expand Down

0 comments on commit 45dc6bd

Please sign in to comment.