Skip to content

elf: support external and supplementary debuginfo #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ jobs:
env:
RUSTFLAGS: "-C split-debuginfo=packed -Zunstable-options"

# Test that separate debug info works
- run: ./ci/debuglink-docker.sh
if: contains(matrix.os, 'ubuntu')

# Test that including as a submodule will still work
- run: cargo build --manifest-path crates/as-if-std/Cargo.toml

Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ edition = "2018"

[workspace]
members = ['crates/cpp_smoke_test', 'crates/as-if-std']
exclude = ['crates/without_debuginfo', 'crates/macos_frames_test', 'crates/line-tables-only']
exclude = [
'crates/without_debuginfo',
'crates/macos_frames_test',
'crates/line-tables-only',
'crates/debuglink',
]

[dependencies]
cfg-if = "1.0"
Expand Down
29 changes: 29 additions & 0 deletions ci/debuglink-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Small script to run debuglink tests inside a docker image.
# Creates a writable mount on /usr/lib/debug.

set -ex

run() {
cargo generate-lockfile --manifest-path crates/debuglink/Cargo.toml
mkdir -p target crates/debuglink/target debug
docker build -t backtrace -f ci/docker/$1/Dockerfile ci
docker run \
--user `id -u`:`id -g` \
--rm \
--init \
--volume $(dirname $(dirname `which cargo`)):/cargo \
--env CARGO_HOME=/cargo \
--volume `rustc --print sysroot`:/rust:ro \
--env TARGET=$1 \
--volume `pwd`:/checkout:ro \
--volume `pwd`/target:/checkout/crates/debuglink/target \
--workdir /checkout \
--volume `pwd`/debug:/usr/lib/debug \
--privileged \
--env RUSTFLAGS \
backtrace \
bash \
-c 'PATH=$PATH:/rust/bin exec ci/debuglink.sh'
}

run x86_64-unknown-linux-gnu
75 changes: 75 additions & 0 deletions ci/debuglink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash

# Debuglink tests.
# We build crates/debuglink and then move its debuginfo around
# and test that it can still find the debuginfo.

set -ex

cratedir=`pwd`/crates/debuglink
exefile=crates/debuglink/target/debug/debuglink

# Baseline; no separate debug
cargo build --manifest-path crates/debuglink/Cargo.toml
$exefile $cratedir

# Separate debug in same dir
debugfile1=`dirname $exefile`/debuglink.debug
objcopy --only-keep-debug $exefile $debugfile1
strip -g $exefile
(cd `dirname $exefile` && objcopy --add-gnu-debuglink=debuglink.debug debuglink)
$exefile $cratedir

# Separate debug in .debug subdir
debugfile2=`dirname $exefile`/.debug/debuglink.debug
mkdir -p `dirname $debugfile2`
mv $debugfile1 $debugfile2
$exefile $cratedir

# Separate debug in /usr/lib/debug subdir
debugfile3="/usr/lib/debug/$cratedir/target/debug/debuglink.debug"
mkdir -p `dirname $debugfile3`
mv $debugfile2 $debugfile3
$exefile $cratedir

# Separate debug in /usr/lib/debug/.build-id subdir
id=`readelf -n $exefile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
idfile="/usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
mkdir -p `dirname $idfile`
mv $debugfile3 $idfile
$exefile $cratedir

# Replace idfile with a symlink (this is the usual arrangement)
mv $idfile $debugfile3
ln -s $debugfile3 $idfile
$exefile $cratedir

# Supplementary object file using relative path
dwzfile="/usr/lib/debug/.dwz/debuglink.debug"
mkdir -p `dirname $dwzfile`
cp $debugfile3 $debugfile3.copy
dwz -m $dwzfile -rh $debugfile3 $debugfile3.copy
rm $debugfile3.copy
$exefile $cratedir

# Supplementary object file using build ID
dwzid=`readelf -n $dwzfile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
dwzidfile="/usr/lib/debug/.build-id/${dwzid:0:2}/${dwzid:2}.debug"
mkdir -p `dirname $dwzidfile`
mv $dwzfile $dwzidfile
$exefile $cratedir
mv $dwzidfile $dwzfile

# Missing debug should fail
mv $debugfile3 $debugfile3.tmp
! $exefile $cratedir
mv $debugfile3.tmp $debugfile3

# Missing dwz should fail
mv $dwzfile $dwzfile.tmp
! $exefile $cratedir
mv $dwzfile.tmp $dwzfile

# Cleanup
rm $idfile $debugfile3 $dwzfile
echo Success
3 changes: 2 additions & 1 deletion ci/docker/x86_64-unknown-linux-gnu/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ FROM ubuntu:20.04
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libc6-dev \
ca-certificates
ca-certificates \
dwz
7 changes: 7 additions & 0 deletions crates/debuglink/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "debuglink"
version = "0.1.0"
edition = "2018"

[dependencies]
backtrace = { path = "../.." }
34 changes: 34 additions & 0 deletions crates/debuglink/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Test that the debuginfo is being found by checking that the
// backtrace contains `main` and that the source filename uses
// the path given in the command line arguments.
//
// For dwz tests, this assumes that the path string will be moved into
// the dwz file.
fn main() {
let crate_dir = std::env::args().skip(1).next().unwrap();
let expect = std::path::Path::new(&crate_dir).join("src/main.rs");

let bt = backtrace::Backtrace::new();
println!("{:?}", bt);

let mut found_main = false;

for frame in bt.frames() {
let symbols = frame.symbols();
if symbols.is_empty() {
continue;
}

if let Some(name) = symbols[0].name() {
let name = format!("{:#}", name);
if name == "debuglink::main" {
found_main = true;
let filename = symbols[0].filename().unwrap();
assert_eq!(filename, expect);
break;
}
}
}

assert!(found_main);
}
20 changes: 18 additions & 2 deletions src/symbolize/gimli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ struct Mapping {
// 'static lifetime is a lie to hack around lack of support for self-referential structs.
cx: Context<'static>,
_map: Mmap,
_map_sup: Option<Mmap>,
_stash: Stash,
}

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

impl<'data> Context<'data> {
fn new(stash: &'data Stash, object: Object<'data>) -> Option<Context<'data>> {
let sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
fn new(
stash: &'data Stash,
object: Object<'data>,
sup: Option<Object<'data>>,
) -> Option<Context<'data>> {
let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
let data = object.section(stash, id.name()).unwrap_or(&[]);
Ok(EndianSlice::new(data, Endian))
})
.ok()?;

if let Some(sup) = sup {
sections
.load_sup(|id| -> Result<_, ()> {
let data = sup.section(stash, id.name()).unwrap_or(&[]);
Ok(EndianSlice::new(data, Endian))
})
.ok()?;
}
let dwarf = addr2line::Context::from_dwarf(sections).ok()?;

Some(Context { dwarf, object })
Expand Down
4 changes: 3 additions & 1 deletion src/symbolize/gimli/coff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ type Pe = object::pe::ImageNtHeaders64;
impl Mapping {
pub fn new(path: &Path) -> Option<Mapping> {
let map = super::mmap(path)?;
Mapping::mk(map, |data, stash| Context::new(stash, Object::parse(data)?))
Mapping::mk(map, |data, stash| {
Context::new(stash, Object::parse(data)?, None)
})
}
}

Expand Down
Loading