Skip to content

Commit 4f3acf7

Browse files
Merge of #626 - 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
2 parents 0b91167 + fbfaf5a commit 4f3acf7

File tree

5 files changed

+82
-11
lines changed

5 files changed

+82
-11
lines changed

.github/workflows/main.yml

+18-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ jobs:
2222
rust: beta
2323
- os: ubuntu-20.04
2424
rust: nightly
25+
- os: ubuntu-24.04
26+
rust: stable
27+
- os: ubuntu-24.04
28+
rust: beta
29+
- os: ubuntu-24.04
30+
rust: nightly
2531
- os: macos-latest
2632
rust: stable
2733
- os: macos-latest
@@ -55,6 +61,12 @@ jobs:
5561
shell: bash
5662
if: contains(matrix.rust, 'i686')
5763

64+
# Starting with Ubuntu 22.04 libc6-dbg is needed.
65+
- name: Install libc debug info
66+
run: sudo apt-get install -y libc6-dbg
67+
shell: bash
68+
if: contains(matrix.os, 'ubuntu-24.04')
69+
5870
- name: Enable collapse_debuginfo based on version
5971
run: echo RUSTFLAGS="--cfg dbginfo=\"collapsible\" $RUSTFLAGS" >> $GITHUB_ENV
6072
shell: bash
@@ -80,6 +92,11 @@ jobs:
8092
if: contains(matrix.os, 'ubuntu')
8193
env:
8294
RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib"
95+
- run: cargo test
96+
if: contains(matrix.os, 'ubuntu-24.04') ||
97+
(contains(matrix.os, 'ubuntu') && contains(matrix.rust, 'nightly'))
98+
env:
99+
RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zstd"
83100

84101
# Test that, on macOS, packed/unpacked debuginfo both work
85102
- run: cargo clean && cargo test
@@ -248,7 +265,7 @@ jobs:
248265
with:
249266
submodules: true
250267
- name: Install Rust
251-
run: rustup update 1.65.0 --no-self-update && rustup default 1.65.0
268+
run: rustup update 1.73.0 --no-self-update && rustup default 1.73.0
252269
- run: cargo build
253270

254271
miri:

Cargo.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ windows-targets = "0.52.6"
4343

4444
[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies]
4545
miniz_oxide = { version = "0.8", default-features = false }
46+
ruzstd = { version = "0.7.2", default-features = false }
4647
addr2line = { version = "0.24.0", default-features = false }
4748
libc = { version = "0.2.156", default-features = false }
4849

crates/as-if-std/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ libc = { version = "0.2.156", default-features = false }
1818

1919
[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies]
2020
miniz_oxide = { version = "0.8", optional = true, default-features = false }
21+
ruzstd = { version = "0.7.2", optional = true, default-features = false }
2122
addr2line = { version = "0.24.0", optional = true, default-features = false }
2223

2324
[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object]
@@ -31,7 +32,7 @@ windows-targets = "0.52.6"
3132

3233
[features]
3334
default = ['backtrace']
34-
backtrace = ['addr2line', 'miniz_oxide', 'object']
35+
backtrace = ['addr2line', 'miniz_oxide', 'object', 'ruzstd']
3536
std = []
3637

3738
[lints.rust]

src/symbolize/gimli/elf.rs

+53-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use super::{gimli, Context, Endian, EndianSlice, Mapping, Stash, Vec};
99
use alloc::sync::Arc;
1010
use core::convert::{TryFrom, TryInto};
1111
use core::str;
12-
use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED};
12+
use object::elf::{
13+
ELFCOMPRESS_ZLIB, ELFCOMPRESS_ZSTD, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED,
14+
};
1315
use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym};
1416
use object::read::StringTable;
1517
use object::{BigEndian, Bytes, NativeEndian};
@@ -213,22 +215,30 @@ impl<'a> Object<'a> {
213215
let mut data = Bytes(section.data(self.endian, self.data).ok()?);
214216

215217
// Check for DWARF-standard (gABI) compression, i.e., as generated
216-
// by ld's `--compress-debug-sections=zlib-gabi` flag.
218+
// by ld's `--compress-debug-sections=zlib-gabi` and
219+
// `--compress-debug-sections=zstd` flags.
217220
let flags: u64 = section.sh_flags(self.endian).into();
218221
if (flags & u64::from(SHF_COMPRESSED)) == 0 {
219222
// Not compressed.
220223
return Some(data.0);
221224
}
222225

223226
let header = data.read::<<Elf as FileHeader>::CompressionHeader>().ok()?;
224-
if header.ch_type(self.endian) != ELFCOMPRESS_ZLIB {
225-
// Zlib compression is the only known type.
226-
return None;
227+
match header.ch_type(self.endian) {
228+
ELFCOMPRESS_ZLIB => {
229+
let size = usize::try_from(header.ch_size(self.endian)).ok()?;
230+
let buf = stash.allocate(size);
231+
decompress_zlib(data.0, buf)?;
232+
return Some(buf);
233+
}
234+
ELFCOMPRESS_ZSTD => {
235+
let size = usize::try_from(header.ch_size(self.endian)).ok()?;
236+
let buf = stash.allocate(size);
237+
decompress_zstd(data.0, buf)?;
238+
return Some(buf);
239+
}
240+
_ => return None, // Unknown compression type.
227241
}
228-
let size = usize::try_from(header.ch_size(self.endian)).ok()?;
229-
let buf = stash.allocate(size);
230-
decompress_zlib(data.0, buf)?;
231-
return Some(buf);
232242
}
233243

234244
// Check for the nonstandard GNU compression format, i.e., as generated
@@ -347,6 +357,40 @@ fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {
347357
}
348358
}
349359

360+
fn decompress_zstd(mut input: &[u8], mut output: &mut [u8]) -> Option<()> {
361+
use ruzstd::frame::ReadFrameHeaderError;
362+
use ruzstd::frame_decoder::FrameDecoderError;
363+
use ruzstd::io::Read;
364+
365+
while !input.is_empty() {
366+
let mut decoder = match ruzstd::StreamingDecoder::new(&mut input) {
367+
Ok(decoder) => decoder,
368+
Err(FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::SkipFrame {
369+
length,
370+
..
371+
})) => {
372+
input = &input.get(length as usize..)?;
373+
continue;
374+
}
375+
Err(_) => return None,
376+
};
377+
loop {
378+
let bytes_written = decoder.read(output).ok()?;
379+
if bytes_written == 0 {
380+
break;
381+
}
382+
output = &mut output[bytes_written..];
383+
}
384+
}
385+
386+
if !output.is_empty() {
387+
// Lengths didn't match, something is wrong.
388+
return None;
389+
}
390+
391+
Some(())
392+
}
393+
350394
const DEBUG_PATH: &[u8] = b"/usr/lib/debug";
351395

352396
fn debug_path_exists() -> bool {

0 commit comments

Comments
 (0)