Skip to content

Commit ed3689c

Browse files
authored
elf: support external and supplementary debuginfo (#427)
* elf: locate debuginfo via build ID or GNU debuglink * elf: supplementary object file support * Delete Context::new_sup * Reduce nesting in Mapping::new * Add documentation links for debuglink * Cache check for /usr/lib/debug and restrict to linux/freebsd * Fix debugaltlink path resolution * Canonicalize for relative debugaltlink path * Add debuglink tests
1 parent 4647e2a commit ed3689c

File tree

11 files changed

+445
-12
lines changed

11 files changed

+445
-12
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ jobs:
102102
env:
103103
RUSTFLAGS: "-C split-debuginfo=packed -Zunstable-options"
104104

105+
# Test that separate debug info works
106+
- run: ./ci/debuglink-docker.sh
107+
if: contains(matrix.os, 'ubuntu')
108+
105109
# Test that including as a submodule will still work
106110
- run: cargo build --manifest-path crates/as-if-std/Cargo.toml
107111

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ edition = "2018"
1717

1818
[workspace]
1919
members = ['crates/cpp_smoke_test', 'crates/as-if-std']
20-
exclude = ['crates/without_debuginfo', 'crates/macos_frames_test', 'crates/line-tables-only']
20+
exclude = [
21+
'crates/without_debuginfo',
22+
'crates/macos_frames_test',
23+
'crates/line-tables-only',
24+
'crates/debuglink',
25+
]
2126

2227
[dependencies]
2328
cfg-if = "1.0"

ci/debuglink-docker.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Small script to run debuglink tests inside a docker image.
2+
# Creates a writable mount on /usr/lib/debug.
3+
4+
set -ex
5+
6+
run() {
7+
cargo generate-lockfile --manifest-path crates/debuglink/Cargo.toml
8+
mkdir -p target crates/debuglink/target debug
9+
docker build -t backtrace -f ci/docker/$1/Dockerfile ci
10+
docker run \
11+
--user `id -u`:`id -g` \
12+
--rm \
13+
--init \
14+
--volume $(dirname $(dirname `which cargo`)):/cargo \
15+
--env CARGO_HOME=/cargo \
16+
--volume `rustc --print sysroot`:/rust:ro \
17+
--env TARGET=$1 \
18+
--volume `pwd`:/checkout:ro \
19+
--volume `pwd`/target:/checkout/crates/debuglink/target \
20+
--workdir /checkout \
21+
--volume `pwd`/debug:/usr/lib/debug \
22+
--privileged \
23+
--env RUSTFLAGS \
24+
backtrace \
25+
bash \
26+
-c 'PATH=$PATH:/rust/bin exec ci/debuglink.sh'
27+
}
28+
29+
run x86_64-unknown-linux-gnu

ci/debuglink.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/bin/bash
2+
3+
# Debuglink tests.
4+
# We build crates/debuglink and then move its debuginfo around
5+
# and test that it can still find the debuginfo.
6+
7+
set -ex
8+
9+
cratedir=`pwd`/crates/debuglink
10+
exefile=crates/debuglink/target/debug/debuglink
11+
12+
# Baseline; no separate debug
13+
cargo build --manifest-path crates/debuglink/Cargo.toml
14+
$exefile $cratedir
15+
16+
# Separate debug in same dir
17+
debugfile1=`dirname $exefile`/debuglink.debug
18+
objcopy --only-keep-debug $exefile $debugfile1
19+
strip -g $exefile
20+
(cd `dirname $exefile` && objcopy --add-gnu-debuglink=debuglink.debug debuglink)
21+
$exefile $cratedir
22+
23+
# Separate debug in .debug subdir
24+
debugfile2=`dirname $exefile`/.debug/debuglink.debug
25+
mkdir -p `dirname $debugfile2`
26+
mv $debugfile1 $debugfile2
27+
$exefile $cratedir
28+
29+
# Separate debug in /usr/lib/debug subdir
30+
debugfile3="/usr/lib/debug/$cratedir/target/debug/debuglink.debug"
31+
mkdir -p `dirname $debugfile3`
32+
mv $debugfile2 $debugfile3
33+
$exefile $cratedir
34+
35+
# Separate debug in /usr/lib/debug/.build-id subdir
36+
id=`readelf -n $exefile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
37+
idfile="/usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
38+
mkdir -p `dirname $idfile`
39+
mv $debugfile3 $idfile
40+
$exefile $cratedir
41+
42+
# Replace idfile with a symlink (this is the usual arrangement)
43+
mv $idfile $debugfile3
44+
ln -s $debugfile3 $idfile
45+
$exefile $cratedir
46+
47+
# Supplementary object file using relative path
48+
dwzfile="/usr/lib/debug/.dwz/debuglink.debug"
49+
mkdir -p `dirname $dwzfile`
50+
cp $debugfile3 $debugfile3.copy
51+
dwz -m $dwzfile -rh $debugfile3 $debugfile3.copy
52+
rm $debugfile3.copy
53+
$exefile $cratedir
54+
55+
# Supplementary object file using build ID
56+
dwzid=`readelf -n $dwzfile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
57+
dwzidfile="/usr/lib/debug/.build-id/${dwzid:0:2}/${dwzid:2}.debug"
58+
mkdir -p `dirname $dwzidfile`
59+
mv $dwzfile $dwzidfile
60+
$exefile $cratedir
61+
mv $dwzidfile $dwzfile
62+
63+
# Missing debug should fail
64+
mv $debugfile3 $debugfile3.tmp
65+
! $exefile $cratedir
66+
mv $debugfile3.tmp $debugfile3
67+
68+
# Missing dwz should fail
69+
mv $dwzfile $dwzfile.tmp
70+
! $exefile $cratedir
71+
mv $dwzfile.tmp $dwzfile
72+
73+
# Cleanup
74+
rm $idfile $debugfile3 $dwzfile
75+
echo Success

ci/docker/x86_64-unknown-linux-gnu/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ FROM ubuntu:20.04
22
RUN apt-get update && apt-get install -y --no-install-recommends \
33
gcc \
44
libc6-dev \
5-
ca-certificates
5+
ca-certificates \
6+
dwz

crates/debuglink/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "debuglink"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
[dependencies]
7+
backtrace = { path = "../.." }

crates/debuglink/src/main.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Test that the debuginfo is being found by checking that the
2+
// backtrace contains `main` and that the source filename uses
3+
// the path given in the command line arguments.
4+
//
5+
// For dwz tests, this assumes that the path string will be moved into
6+
// the dwz file.
7+
fn main() {
8+
let crate_dir = std::env::args().skip(1).next().unwrap();
9+
let expect = std::path::Path::new(&crate_dir).join("src/main.rs");
10+
11+
let bt = backtrace::Backtrace::new();
12+
println!("{:?}", bt);
13+
14+
let mut found_main = false;
15+
16+
for frame in bt.frames() {
17+
let symbols = frame.symbols();
18+
if symbols.is_empty() {
19+
continue;
20+
}
21+
22+
if let Some(name) = symbols[0].name() {
23+
let name = format!("{:#}", name);
24+
if name == "debuglink::main" {
25+
found_main = true;
26+
let filename = symbols[0].filename().unwrap();
27+
assert_eq!(filename, expect);
28+
break;
29+
}
30+
}
31+
}
32+
33+
assert!(found_main);
34+
}

src/symbolize/gimli.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ struct Mapping {
5858
// 'static lifetime is a lie to hack around lack of support for self-referential structs.
5959
cx: Context<'static>,
6060
_map: Mmap,
61+
_map_sup: Option<Mmap>,
6162
_stash: Stash,
6263
}
6364

6465
impl Mapping {
66+
#[allow(dead_code)]
6567
fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
6668
where
6769
F: for<'a> Fn(&'a [u8], &'a Stash) -> Option<Context<'a>>,
@@ -73,6 +75,7 @@ impl Mapping {
7375
// only borrow `map` and `stash` and we're preserving them below.
7476
cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
7577
_map: data,
78+
_map_sup: None,
7679
_stash: stash,
7780
})
7881
}
@@ -84,12 +87,25 @@ struct Context<'a> {
8487
}
8588

8689
impl<'data> Context<'data> {
87-
fn new(stash: &'data Stash, object: Object<'data>) -> Option<Context<'data>> {
88-
let sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
90+
fn new(
91+
stash: &'data Stash,
92+
object: Object<'data>,
93+
sup: Option<Object<'data>>,
94+
) -> Option<Context<'data>> {
95+
let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
8996
let data = object.section(stash, id.name()).unwrap_or(&[]);
9097
Ok(EndianSlice::new(data, Endian))
9198
})
9299
.ok()?;
100+
101+
if let Some(sup) = sup {
102+
sections
103+
.load_sup(|id| -> Result<_, ()> {
104+
let data = sup.section(stash, id.name()).unwrap_or(&[]);
105+
Ok(EndianSlice::new(data, Endian))
106+
})
107+
.ok()?;
108+
}
93109
let dwarf = addr2line::Context::from_dwarf(sections).ok()?;
94110

95111
Some(Context { dwarf, object })

src/symbolize/gimli/coff.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ type Pe = object::pe::ImageNtHeaders64;
1313
impl Mapping {
1414
pub fn new(path: &Path) -> Option<Mapping> {
1515
let map = super::mmap(path)?;
16-
Mapping::mk(map, |data, stash| Context::new(stash, Object::parse(data)?))
16+
Mapping::mk(map, |data, stash| {
17+
Context::new(stash, Object::parse(data)?, None)
18+
})
1719
}
1820
}
1921

0 commit comments

Comments
 (0)