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 13, 2024
1 parent fd0aed5 commit c97a931
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 10 deletions.
16 changes: 16 additions & 0 deletions .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,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
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.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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.22.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.22.0", optional = true, default-features = false }

[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object]
Expand All @@ -33,7 +34,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 c97a931

Please sign in to comment.