Skip to content

Commit 392e2a0

Browse files
committed
fixup! Support .gnu_debugdata section
1 parent e97bc6b commit 392e2a0

File tree

8 files changed

+83
-95
lines changed

8 files changed

+83
-95
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ Unreleased
22
----------
33
- Added support for `/usr/lib/debug/.build-id` debug link target
44
directory
5+
- Added support for compressed `.gnu_debugdata` ELF sections
6+
(MiniDebugInfo)
57

68

79
0.2.2
@@ -11,7 +13,6 @@ Unreleased
1113
provided by the user
1214
- Fixed backtrace reporting on contextualized errors
1315
- Added bindings for Go language
14-
- Added support for .gnu_debugdata
1516

1617

1718
0.2.1

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ default = [
8888
"demangle",
8989
"dwarf",
9090
"zlib",
91-
"xz2",
9291
]
9392
# Enable this feature to enable APK support (mostly relevant for
9493
# Android).
@@ -119,8 +118,9 @@ zlib = ["dep:miniz_oxide"]
119118
# currently only used for handling compressed debug information.
120119
zstd = ["dep:zstd"]
121120
# Enable this feature to enable support for xz decompression. This is
122-
# currently only used for handling .gnu_debugdata section (MiniDebugInfo)
123-
xz2 = ["dep:xz2"]
121+
# currently only used for handling `.gnu_debugdata` section data
122+
# (MiniDebugInfo)
123+
xz = ["dep:xz2"]
124124

125125
# Below here are dev-mostly features that should not be needed by
126126
# regular users.

data/gnu_debugdata.sh

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
1-
# taken from the original MiniDebugInfo documentation found here:
1+
# Taken from the original MiniDebugInfo documentation found here:
22
# https://www.sourceware.org/gdb/current/onlinedocs/gdb.html/MiniDebugInfo.html
33

4+
# Create temporary directory
5+
tmpdir=$(mktemp -d)
6+
cleanup() { rm -rf "$tmpdir"; }
7+
trap cleanup EXIT
8+
49
# Extract the dynamic symbols from the main binary, there is no need
510
# to also have these in the normal symbol table.
6-
nm -D $1 --format=posix --defined-only \
7-
| awk '{ print $1 }' | sort > dynsyms
11+
nm --dynamic "$1" --format=posix --defined-only \
12+
| awk '{ print $1 }' | sort > "$tmpdir/dynsyms"
813

914
# Extract all the text (i.e. function) symbols from the debuginfo.
1015
# (Note that we actually also accept "D" symbols, for the benefit
1116
# of platforms like PowerPC64 that use function descriptors.)
12-
nm $1 --format=posix --defined-only \
17+
nm "$1" --format=posix --defined-only \
1318
| awk '{ if ($2 == "T" || $2 == "t" || $2 == "D") print $1 }' \
14-
| sort > funcsyms
19+
| sort > "$tmpdir/funcsyms"
1520

1621
# Keep all the function symbols not already in the dynamic symbol
1722
# table.
18-
comm -13 dynsyms funcsyms > keep_symbols
23+
comm -13 "$tmpdir/dynsyms" "$tmpdir/funcsyms" > "$tmpdir/keep_symbols"
1924

2025
# Separate full debug info into debug binary.
21-
objcopy --only-keep-debug $1 debug
26+
objcopy --only-keep-debug "$1" "$tmpdir/debug"
2227

2328
# Copy the full debuginfo, keeping only a minimal set of symbols and
2429
# removing some unnecessary sections.
25-
objcopy -S --remove-section .gdb_index --remove-section .comment \
26-
--keep-symbols=keep_symbols debug mini_debuginfo
27-
28-
# cp src to dst and operate on that
29-
cp $1 $2
30+
objcopy --strip-all --remove-section .gdb_index --remove-section .comment \
31+
--keep-symbols="$tmpdir/keep_symbols" "$tmpdir/debug" "$tmpdir/mini_debuginfo"
3032

31-
# Drop the full debug info from the original binary.
32-
strip --strip-all -R .comment $2
33+
# Copy src to dst and operate on that.
34+
cp "$1" "$2"
3335

34-
# Inject the compressed data into the .gnu_debugdata section of the
35-
# original binary.
36-
xz mini_debuginfo
37-
objcopy --add-section .gnu_debugdata=mini_debuginfo.xz $2
36+
# Drop the full debug info from the target binary.
37+
strip --strip-all --remove-section .comment "$2"
3838

39-
# Clean up temporary files.
40-
rm dynsyms funcsyms keep_symbols debug mini_debuginfo.xz
39+
# Inject the compressed data into the `.gnu_debugdata` section of the
40+
# target binary.
41+
xz "$tmpdir/mini_debuginfo"
42+
objcopy --add-section .gnu_debugdata="$tmpdir/mini_debuginfo.xz" "$2"

dev/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ libbpf-sys = {version = "1.6", default-features = false, optional = true}
4848
# TODO: Enable `zstd` feature once toolchain support for it is more
4949
# widespread (enabled by default in `ld`). Remove conditionals in
5050
# test code alongside.
51-
blazesym = {path = "../", features = ["apk", "breakpad", "gsym", "tracing", "test"]}
51+
blazesym = {path = "../", features = ["apk", "breakpad", "gsym", "tracing", "test", "xz"]}
5252
libc = "0.2"
5353

5454
[target.'cfg(target_os = "linux")'.dependencies]

dev/build.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,17 @@ fn strip(src: &Path, dst: impl AsRef<OsStr>, options: &[&str]) {
273273
toolize_o("strip", src, dst, options)
274274
}
275275

276-
/// move symbols from .symtab into `.gnu_debugdata`
276+
/// Create a file with compressed `.gnu_debugdata` data based on a
277+
/// source binary.
277278
fn gnu_debugdata(src: &Path, dst: impl AsRef<OsStr>) {
278279
let dst = src.with_file_name(dst);
279280
let convert_sh = data_dir().join("gnu_debugdata.sh");
280281
println!("cargo:rerun-if-changed={}", src.display());
281282
println!("cargo:rerun-if-changed={}", dst.display());
282-
println!("cargo:rerun-if-env-changed={}", convert_sh.display());
283+
println!("cargo:rerun-if-changed={}", convert_sh.display());
283284

284285
let () = run("/bin/sh", [&convert_sh, src, &dst], identity)
285-
.expect("failed to convert to .gnu_debugdata");
286+
.expect("failed to create mini debuginfo file");
286287

287288
let () = adjust_mtime(&dst).unwrap();
288289
}
@@ -663,7 +664,6 @@ fn prepare_test_files() {
663664
&["-gstrict-dwarf", "-gdwarf-5", "-gz=zstd"],
664665
);
665666
}
666-
gnu_debugdata(&data_dir.join("test-no-debug.bin"), "test-debugdata.bin");
667667

668668
// Generate this binary by passing the source file name without a
669669
// path to the compiler (which means we need to `cd` into the
@@ -755,6 +755,7 @@ fn prepare_test_files() {
755755
&format!("--add-gnu-debuglink={}", dbg.display()),
756756
],
757757
);
758+
gnu_debugdata(&src, "test-stable-addrs-debugdata.bin");
758759

759760
let elf = data_dir.join("test-stable-addrs-no-dwarf.bin");
760761
objcopy(

src/elf/parser.rs

Lines changed: 23 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::fmt::Formatter;
77
use std::fmt::Result as FmtResult;
88
use std::fs::File;
99
use std::io;
10+
#[cfg(feature = "xz")]
1011
use std::io::Read as _;
1112
use std::io::Seek as _;
1213
use std::io::SeekFrom;
@@ -272,7 +273,7 @@ fn decompress_zstd(_data: &[u8]) -> Result<Vec<u8>> {
272273
))
273274
}
274275

275-
#[cfg(feature = "xz2")]
276+
#[cfg(feature = "xz")]
276277
fn decompress_xz(data: &[u8]) -> Result<Vec<u8>> {
277278
use xz2::read::XzDecoder;
278279

@@ -285,7 +286,7 @@ fn decompress_xz(data: &[u8]) -> Result<Vec<u8>> {
285286
Ok(decompressed)
286287
}
287288

288-
#[cfg(not(feature = "xz2"))]
289+
#[cfg(not(feature = "xz"))]
289290
fn decompress_xz(_data: &[u8]) -> Result<Vec<u8>> {
290291
Err(Error::with_unsupported(
291292
"ELF section is xz compressed but xz compression support is not enabled",
@@ -458,8 +459,6 @@ struct Cache<'elf, B> {
458459
dynsym: OnceCell<SymbolTableCache<'elf>>,
459460
/// The section data.
460461
section_data: OnceCell<Box<[OnceCell<Cow<'elf, [u8]>>]>>,
461-
/// Potential decompressed `.gnu_debugdata` contents.
462-
debugdata: OnceCell<Cow<'elf, [u8]>>,
463462
}
464463

465464
impl<'elf, B> Cache<'elf, B>
@@ -477,7 +476,6 @@ where
477476
symtab: OnceCell::new(),
478477
dynsym: OnceCell::new(),
479478
section_data: OnceCell::new(),
480-
debugdata: OnceCell::new(),
481479
}
482480
}
483481

@@ -832,58 +830,35 @@ where
832830
Ok(syms)
833831
}
834832

835-
#[cfg(feature = "xz2")]
836-
fn parse_debugdata(&self) -> Result<Option<Vec<u8>>> {
837-
let idx = if let Some(idx) = self.find_section(".gnu_debugdata")? {
838-
idx
833+
fn parse_debugdata(&self) -> Result<Vec<u8>> {
834+
if let Some(idx) = self.find_section(".gnu_debugdata")? {
835+
// NB: We assume here that `.gnu_debugdata` will never be
836+
// of type `SHT_NOBITS`, which just logically doesn't
837+
// make sense to be used.
838+
self.section_data_raw(idx)
839+
.and_then(|data| decompress_xz(&data))
839840
} else {
840-
// The symbol table does not exists. Fake an empty one.
841-
return Ok(None)
842-
};
843-
844-
let shdr = self.section_hdr(idx)?;
845-
if shdr.type_() == SHT_NOBITS {
846-
return Ok(None)
841+
Ok(Vec::new())
847842
}
848-
849-
let sh_size = shdr.size();
850-
let sh_offset = shdr.offset();
851-
852-
let data = self
853-
.backend
854-
.read_pod_slice::<u8>(sh_offset, sh_size as usize)
855-
.context("failed to read .gnu_debugdata section data")?;
856-
857-
let decompressed = decompress_xz(&data)?;
858-
859-
Ok(Some(decompressed))
860-
}
861-
862-
#[cfg(not(feature = "xz2"))]
863-
fn parse_debugdata(&self) -> Result<Option<Vec<u8>>> {
864-
// We don't have the means to decompress it, fake an empty one.
865-
return Ok(None)
866843
}
867844

868845
fn ensure_symtab_cache(&self) -> Result<&SymbolTableCache<'elf>> {
869-
self.symtab.get_or_try_init_(|| {
846+
self.symtab.get_or_try_init_(move || {
870847
let mut syms = self.parse_syms(".symtab")?;
871848
let mut strtab = self.parse_strs(".strtab")?;
849+
872850
if syms.is_empty() {
873-
let data = self.debugdata.get_or_try_init_(|| {
874-
if let Some(data) = self.parse_debugdata()? {
875-
Result::<_, Error>::Ok(Cow::Owned(data))
876-
} else {
877-
Ok(Cow::Borrowed([].as_slice()))
878-
}
879-
})?;
851+
let data = self.parse_debugdata()?;
880852
if !data.is_empty() {
881-
// to allow self-referentiality here, we have to artificially
882-
// extend the lifetime of `data` to `'elf`
883-
let data_slice = unsafe { mem::transmute::<&[u8], &'elf [u8]>(data) };
884-
let cache = Cache::new(data_slice);
885-
syms = cache.parse_syms(".symtab")?;
886-
strtab = cache.parse_strs(".strtab")?;
853+
let cache = Cache::new(data.as_slice());
854+
// Because we don't store the decompressed data
855+
// anywhere (and we don't want to to avoid
856+
// self-referentiality), convert the parsed data
857+
// into their owned counterparts.
858+
syms = cache.parse_syms(".symtab")?.to_owned();
859+
strtab = cache
860+
.parse_strs(".strtab")
861+
.map(|strs| Cow::Owned(strs.to_vec()))?;
887862
}
888863
}
889864
let cache = SymbolTableCache::new(syms, strtab);
@@ -2127,7 +2102,6 @@ mod tests {
21272102
symtab: OnceCell::new(),
21282103
dynsym: OnceCell::new(),
21292104
section_data: OnceCell::new(),
2130-
debugdata: OnceCell::new(),
21312105
};
21322106

21332107
assert_eq!(cache.find_section(".symtab").unwrap(), Some(2));
@@ -2234,29 +2208,12 @@ mod tests {
22342208
symtab: OnceCell::new(),
22352209
dynsym: OnceCell::new(),
22362210
section_data: OnceCell::from(vec![OnceCell::new(), OnceCell::new()].into_boxed_slice()),
2237-
debugdata: OnceCell::new(),
22382211
};
22392212

22402213
let new_data = cache.section_data(1).unwrap();
22412214
assert_eq!(new_data, data);
22422215
}
22432216

2244-
/// Make sure that we can look up a symbol residing in `.gnu_debugdata`.
2245-
#[cfg(feature = "xz2")]
2246-
#[test]
2247-
fn lookup_from_gnu_debugdata() {
2248-
let bin_name = Path::new(&env!("CARGO_MANIFEST_DIR"))
2249-
.join("data")
2250-
.join("test-debugdata.bin");
2251-
2252-
let parser = ElfParser::open(bin_name.as_path()).unwrap();
2253-
let opts = FindAddrOpts::default();
2254-
let addr = parser.find_addr("fibonacci", &opts).unwrap();
2255-
assert_eq!(addr.len(), 1);
2256-
let sym = &addr[0];
2257-
assert_eq!(sym.name, "fibonacci");
2258-
}
2259-
22602217
/// Benchmark creation of our "str2symtab" table.
22612218
///
22622219
/// Creating this table exercises a lot of the parser code paths and

src/elf/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ where
115115
),
116116
}
117117
}
118+
119+
pub fn to_owned(&self) -> ElfNSlice<'static, T> {
120+
match self {
121+
Self::B32(slice) => ElfNSlice::B32(Cow::Owned(slice.to_vec())),
122+
Self::B64(slice) => ElfNSlice::B64(Cow::Owned(slice.to_vec())),
123+
}
124+
}
118125
}
119126

120127

tests/suite/symbolize.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,26 @@ fn symbolize_elf_stripped() {
463463
assert_eq!(result, Symbolized::Unknown(Reason::MissingSyms));
464464
}
465465

466+
/// Make sure that we can symbolize data from a compressed
467+
/// `.gnu_debugdata` section.
468+
#[tag(other_os)]
469+
#[test]
470+
fn symbolize_elf_compressed_gnu_debugdata() {
471+
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
472+
.join("data")
473+
.join("test-stable-addrs-debugdata.bin");
474+
let src = Source::Elf(Elf::new(path));
475+
let symbolizer = Symbolizer::new();
476+
let result = symbolizer
477+
.symbolize_single(&src, Input::VirtOffset(0x2000200))
478+
.unwrap()
479+
.into_sym()
480+
.unwrap();
481+
482+
assert_eq!(result.name, "factorial");
483+
assert_eq!(result.addr, 0x2000200);
484+
}
485+
466486
/// Check that we can symbolize data in a non-existent ELF binary after
467487
/// caching it.
468488
#[test]

0 commit comments

Comments
 (0)