Skip to content

Commit 1f48036

Browse files
committed
elf: locate debuginfo via build ID or GNU debuglink
1 parent e710d02 commit 1f48036

File tree

2 files changed

+130
-4
lines changed

2 files changed

+130
-4
lines changed

src/symbolize/gimli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ struct Mapping {
6262
}
6363

6464
impl Mapping {
65+
#[allow(dead_code)]
6566
fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
6667
where
6768
F: for<'a> Fn(&'a [u8], &'a Stash) -> Option<Context<'a>>,

src/symbolize/gimli/elf.rs

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
use super::{Context, Mapping, Path, Stash, Vec};
2-
use core::convert::TryFrom;
3-
use object::elf::{ELFCOMPRESS_ZLIB, SHF_COMPRESSED};
1+
use super::mystd::ffi::OsStr;
2+
use super::mystd::fs;
3+
use super::mystd::os::unix::ffi::OsStrExt;
4+
use super::mystd::path::{Path, PathBuf};
5+
use super::{Context, Mapping, Stash, Vec};
6+
use core::convert::{TryFrom, TryInto};
7+
use core::str;
8+
use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED};
49
use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym};
510
use object::read::StringTable;
611
use object::{BigEndian, Bytes, NativeEndian};
@@ -10,11 +15,118 @@ type Elf = object::elf::FileHeader32<NativeEndian>;
1015
#[cfg(target_pointer_width = "64")]
1116
type Elf = object::elf::FileHeader64<NativeEndian>;
1217

18+
const BUILD_ID_PATH: &[u8] = b"/usr/lib/debug/.build-id/";
19+
const BUILD_ID_SUFFIX: &[u8] = b".debug";
20+
1321
impl Mapping {
1422
pub fn new(path: &Path) -> Option<Mapping> {
1523
let map = super::mmap(path)?;
16-
Mapping::mk(map, |data, stash| Context::new(stash, Object::parse(data)?))
24+
let object = Object::parse(&map)?;
25+
26+
// Try to locate an external debug file using the build ID.
27+
let build_id = object.build_id().unwrap_or(&[]);
28+
if !build_id.is_empty() {
29+
fn hex(byte: u8) -> u8 {
30+
if byte < 10 {
31+
b'0' + byte
32+
} else {
33+
b'a' + byte - 10
34+
}
35+
}
36+
let mut path = Vec::with_capacity(
37+
BUILD_ID_PATH.len() + BUILD_ID_SUFFIX.len() + build_id.len() * 2 + 1,
38+
);
39+
path.extend(BUILD_ID_PATH);
40+
path.push(hex(build_id[0] >> 4));
41+
path.push(hex(build_id[0] & 0xf));
42+
path.push(b'/');
43+
for byte in &build_id[1..] {
44+
path.push(hex(byte >> 4));
45+
path.push(hex(byte & 0xf));
46+
}
47+
path.extend(BUILD_ID_SUFFIX);
48+
if let Some(mapping) = Mapping::new_debug(Path::new(OsStr::from_bytes(&path)), None) {
49+
return Some(mapping);
50+
}
51+
}
52+
53+
// Try to locate an external debug file using the GNU debug link section.
54+
let tmp_stash = Stash::new();
55+
if let Some(section) = object.section(&tmp_stash, ".gnu_debuglink") {
56+
if let Some(len) = section.iter().position(|x| *x == 0) {
57+
let filename = &section[..len];
58+
let offset = (len + 1 + 3) & !3;
59+
if let Some(crc_bytes) = section
60+
.get(offset..offset + 4)
61+
.and_then(|bytes| bytes.try_into().ok())
62+
{
63+
let crc = u32::from_ne_bytes(crc_bytes);
64+
if let Some(path_debug) = locate_debuglink(path, filename) {
65+
if let Some(mapping) = Mapping::new_debug(&path_debug, Some(crc)) {
66+
return Some(mapping);
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
let stash = Stash::new();
74+
let cx = Context::new(&stash, object)?;
75+
Some(Mapping {
76+
// Convert to 'static lifetimes since the symbols should
77+
// only borrow `map` and `stash` and we're preserving them below.
78+
cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
79+
_map: map,
80+
_stash: stash,
81+
})
82+
}
83+
84+
/// Load debuginfo from an external debug file.
85+
fn new_debug(path: &Path, crc: Option<u32>) -> Option<Mapping> {
86+
let map = super::mmap(path)?;
87+
let object = Object::parse(&map)?;
88+
89+
if let Some(_crc) = crc {
90+
// TODO: check crc
91+
}
92+
93+
let stash = Stash::new();
94+
let cx = Context::new(&stash, object)?;
95+
Some(Mapping {
96+
// Convert to 'static lifetimes since the symbols should
97+
// only borrow `map` and `stash` and we're preserving them below.
98+
cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
99+
_map: map,
100+
_stash: stash,
101+
})
102+
}
103+
}
104+
105+
fn locate_debuglink(path: &Path, filename: &[u8]) -> Option<PathBuf> {
106+
let path = fs::canonicalize(path).ok()?;
107+
let parent = path.parent()?;
108+
let filename = Path::new(OsStr::from_bytes(filename));
109+
110+
// Try "/parent/filename" if it differs from "path"
111+
let f = parent.join(filename);
112+
if f != path && f.is_file() {
113+
return Some(f);
114+
}
115+
116+
// Try "/parent/.debug/filename"
117+
let f = parent.join(".debug").join(filename);
118+
if f.is_file() {
119+
return Some(f);
17120
}
121+
122+
// Try "/usr/lib/debug/parent/filename"
123+
let parent = parent.strip_prefix("/").unwrap();
124+
let f = Path::new("/usr/lib/debug").join(parent).join(filename);
125+
if f.is_file() {
126+
return Some(f);
127+
}
128+
129+
None
18130
}
19131

20132
struct ParsedSym {
@@ -163,6 +275,19 @@ impl<'a> Object<'a> {
163275
pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
164276
None
165277
}
278+
279+
pub(super) fn build_id(&self) -> Option<&'a [u8]> {
280+
for section in self.sections.iter() {
281+
if let Ok(Some(mut notes)) = section.notes(self.endian, self.data) {
282+
while let Ok(Some(note)) = notes.next() {
283+
if note.name() == ELF_NOTE_GNU && note.n_type(self.endian) == NT_GNU_BUILD_ID {
284+
return Some(note.desc());
285+
}
286+
}
287+
}
288+
}
289+
None
290+
}
166291
}
167292

168293
fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {

0 commit comments

Comments
 (0)