1 use super::mystd::ffi::{OsStr, OsString};
2 use super::mystd::fs;
3 use super::mystd::os::unix::ffi::{OsStrExt, OsStringExt};
4 use super::mystd::path::{Path, PathBuf};
5 use super::Either;
6 use super::{Context, Mapping, Stash, Vec};
7 use core::convert::{TryFrom, TryInto};
8 use core::str;
9 use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED};
10 use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym};
11 use object::read::StringTable;
12 use object::{BigEndian, Bytes, NativeEndian};
13
14 #[cfg(target_pointer_width = "32")]
15 type Elf = object::elf::FileHeader32<NativeEndian>;
16 #[cfg(target_pointer_width = "64")]
17 type Elf = object::elf::FileHeader64<NativeEndian>;
18
19 impl Mapping {
new(path: &Path) -> Option<Mapping>20 pub fn new(path: &Path) -> Option<Mapping> {
21 let map = super::mmap(path)?;
22 Mapping::mk_or_other(map, |map, stash| {
23 let object = Object::parse(&map)?;
24
25 // Try to locate an external debug file using the build ID.
26 if let Some(path_debug) = object.build_id().and_then(locate_build_id) {
27 if let Some(mapping) = Mapping::new_debug(path_debug, None) {
28 return Some(Either::A(mapping));
29 }
30 }
31
32 // Try to locate an external debug file using the GNU debug link section.
33 if let Some((path_debug, crc)) = object.gnu_debuglink_path(path) {
34 if let Some(mapping) = Mapping::new_debug(path_debug, Some(crc)) {
35 return Some(Either::A(mapping));
36 }
37 }
38
39 Context::new(stash, object, None).map(Either::B)
40 })
41 }
42
43 /// Load debuginfo from an external debug file.
new_debug(path: PathBuf, crc: Option<u32>) -> Option<Mapping>44 fn new_debug(path: PathBuf, crc: Option<u32>) -> Option<Mapping> {
45 let map = super::mmap(&path)?;
46 Mapping::mk(map, |map, stash| {
47 let object = Object::parse(&map)?;
48
49 if let Some(_crc) = crc {
50 // TODO: check crc
51 }
52
53 // Try to locate a supplementary object file.
54 if let Some((path_sup, build_id_sup)) = object.gnu_debugaltlink_path(&path) {
55 if let Some(map_sup) = super::mmap(&path_sup) {
56 let map_sup = stash.set_mmap_aux(map_sup);
57 if let Some(sup) = Object::parse(map_sup) {
58 if sup.build_id() == Some(build_id_sup) {
59 return Context::new(stash, object, Some(sup));
60 }
61 }
62 }
63 }
64
65 Context::new(stash, object, None)
66 })
67 }
68 }
69
70 struct ParsedSym {
71 address: u64,
72 size: u64,
73 name: u32,
74 }
75
76 pub struct Object<'a> {
77 /// Zero-sized type representing the native endianness.
78 ///
79 /// We could use a literal instead, but this helps ensure correctness.
80 endian: NativeEndian,
81 /// The entire file data.
82 data: &'a [u8],
83 sections: SectionTable<'a, Elf>,
84 strings: StringTable<'a>,
85 /// List of pre-parsed and sorted symbols by base address.
86 syms: Vec<ParsedSym>,
87 }
88
89 impl<'a> Object<'a> {
parse(data: &'a [u8]) -> Option<Object<'a>>90 fn parse(data: &'a [u8]) -> Option<Object<'a>> {
91 let elf = Elf::parse(data).ok()?;
92 let endian = elf.endian().ok()?;
93 let sections = elf.sections(endian, data).ok()?;
94 let mut syms = sections
95 .symbols(endian, data, object::elf::SHT_SYMTAB)
96 .ok()?;
97 if syms.is_empty() {
98 syms = sections
99 .symbols(endian, data, object::elf::SHT_DYNSYM)
100 .ok()?;
101 }
102 let strings = syms.strings();
103
104 let mut syms = syms
105 .iter()
106 // Only look at function/object symbols. This mirrors what
107 // libbacktrace does and in general we're only symbolicating
108 // function addresses in theory. Object symbols correspond
109 // to data, and maybe someone's crazy enough to have a
110 // function go into static data?
111 .filter(|sym| {
112 let st_type = sym.st_type();
113 st_type == object::elf::STT_FUNC || st_type == object::elf::STT_OBJECT
114 })
115 // skip anything that's in an undefined section header,
116 // since it means it's an imported function and we're only
117 // symbolicating with locally defined functions.
118 .filter(|sym| sym.st_shndx(endian) != object::elf::SHN_UNDEF)
119 .map(|sym| {
120 let address = sym.st_value(endian).into();
121 let size = sym.st_size(endian).into();
122 let name = sym.st_name(endian);
123 ParsedSym {
124 address,
125 size,
126 name,
127 }
128 })
129 .collect::<Vec<_>>();
130 syms.sort_unstable_by_key(|s| s.address);
131 Some(Object {
132 endian,
133 data,
134 sections,
135 strings,
136 syms,
137 })
138 }
139
section(&self, stash: &'a Stash, name: &str) -> Option<&'a [u8]>140 pub fn section(&self, stash: &'a Stash, name: &str) -> Option<&'a [u8]> {
141 if let Some(section) = self.section_header(name) {
142 let mut data = Bytes(section.data(self.endian, self.data).ok()?);
143
144 // Check for DWARF-standard (gABI) compression, i.e., as generated
145 // by ld's `--compress-debug-sections=zlib-gabi` flag.
146 let flags: u64 = section.sh_flags(self.endian).into();
147 if (flags & u64::from(SHF_COMPRESSED)) == 0 {
148 // Not compressed.
149 return Some(data.0);
150 }
151
152 let header = data.read::<<Elf as FileHeader>::CompressionHeader>().ok()?;
153 if header.ch_type(self.endian) != ELFCOMPRESS_ZLIB {
154 // Zlib compression is the only known type.
155 return None;
156 }
157 let size = usize::try_from(header.ch_size(self.endian)).ok()?;
158 let buf = stash.allocate(size);
159 decompress_zlib(data.0, buf)?;
160 return Some(buf);
161 }
162
163 // Check for the nonstandard GNU compression format, i.e., as generated
164 // by ld's `--compress-debug-sections=zlib-gnu` flag. This means that if
165 // we're actually asking for `.debug_info` then we need to look up a
166 // section named `.zdebug_info`.
167 if !name.starts_with(".debug_") {
168 return None;
169 }
170 let debug_name = name[7..].as_bytes();
171 let compressed_section = self
172 .sections
173 .iter()
174 .filter_map(|header| {
175 let name = self.sections.section_name(self.endian, header).ok()?;
176 if name.starts_with(b".zdebug_") && &name[8..] == debug_name {
177 Some(header)
178 } else {
179 None
180 }
181 })
182 .next()?;
183 let mut data = Bytes(compressed_section.data(self.endian, self.data).ok()?);
184 if data.read_bytes(8).ok()?.0 != b"ZLIB\0\0\0\0" {
185 return None;
186 }
187 let size = usize::try_from(data.read::<object::U32Bytes<_>>().ok()?.get(BigEndian)).ok()?;
188 let buf = stash.allocate(size);
189 decompress_zlib(data.0, buf)?;
190 Some(buf)
191 }
192
section_header(&self, name: &str) -> Option<&<Elf as FileHeader>::SectionHeader>193 fn section_header(&self, name: &str) -> Option<&<Elf as FileHeader>::SectionHeader> {
194 self.sections
195 .section_by_name(self.endian, name.as_bytes())
196 .map(|(_index, section)| section)
197 }
198
search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]>199 pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
200 // Same sort of binary search as Windows above
201 let i = match self.syms.binary_search_by_key(&addr, |sym| sym.address) {
202 Ok(i) => i,
203 Err(i) => i.checked_sub(1)?,
204 };
205 let sym = self.syms.get(i)?;
206 if sym.address <= addr && addr <= sym.address + sym.size {
207 self.strings.get(sym.name).ok()
208 } else {
209 None
210 }
211 }
212
search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)>213 pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
214 None
215 }
216
build_id(&self) -> Option<&'a [u8]>217 fn build_id(&self) -> Option<&'a [u8]> {
218 for section in self.sections.iter() {
219 if let Ok(Some(mut notes)) = section.notes(self.endian, self.data) {
220 while let Ok(Some(note)) = notes.next() {
221 if note.name() == ELF_NOTE_GNU && note.n_type(self.endian) == NT_GNU_BUILD_ID {
222 return Some(note.desc());
223 }
224 }
225 }
226 }
227 None
228 }
229
230 // The contents of the ".gnu_debuglink" section is documented at:
231 // https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
gnu_debuglink_path(&self, path: &Path) -> Option<(PathBuf, u32)>232 fn gnu_debuglink_path(&self, path: &Path) -> Option<(PathBuf, u32)> {
233 let section = self.section_header(".gnu_debuglink")?;
234 let data = section.data(self.endian, self.data).ok()?;
235 let len = data.iter().position(|x| *x == 0)?;
236 let filename = &data[..len];
237 let offset = (len + 1 + 3) & !3;
238 let crc_bytes = data
239 .get(offset..offset + 4)
240 .and_then(|bytes| bytes.try_into().ok())?;
241 let crc = u32::from_ne_bytes(crc_bytes);
242 let path_debug = locate_debuglink(path, filename)?;
243 Some((path_debug, crc))
244 }
245
246 // The format of the ".gnu_debugaltlink" section is based on gdb.
gnu_debugaltlink_path(&self, path: &Path) -> Option<(PathBuf, &'a [u8])>247 fn gnu_debugaltlink_path(&self, path: &Path) -> Option<(PathBuf, &'a [u8])> {
248 let section = self.section_header(".gnu_debugaltlink")?;
249 let data = section.data(self.endian, self.data).ok()?;
250 let len = data.iter().position(|x| *x == 0)?;
251 let filename = &data[..len];
252 let build_id = &data[len + 1..];
253 let path_sup = locate_debugaltlink(path, filename, build_id)?;
254 Some((path_sup, build_id))
255 }
256 }
257
decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()>258 fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {
259 use miniz_oxide::inflate::core::inflate_flags::{
260 TINFL_FLAG_PARSE_ZLIB_HEADER, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF,
261 };
262 use miniz_oxide::inflate::core::{decompress, DecompressorOxide};
263 use miniz_oxide::inflate::TINFLStatus;
264
265 let (status, in_read, out_read) = decompress(
266 &mut DecompressorOxide::new(),
267 input,
268 output,
269 0,
270 TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | TINFL_FLAG_PARSE_ZLIB_HEADER,
271 );
272 if status == TINFLStatus::Done && in_read == input.len() && out_read == output.len() {
273 Some(())
274 } else {
275 None
276 }
277 }
278
279 const DEBUG_PATH: &[u8] = b"/usr/lib/debug";
280
debug_path_exists() -> bool281 fn debug_path_exists() -> bool {
282 cfg_if::cfg_if! {
283 if #[cfg(any(target_os = "freebsd", target_os = "linux"))] {
284 use core::sync::atomic::{AtomicU8, Ordering};
285 static DEBUG_PATH_EXISTS: AtomicU8 = AtomicU8::new(0);
286
287 let mut exists = DEBUG_PATH_EXISTS.load(Ordering::Relaxed);
288 if exists == 0 {
289 exists = if Path::new(OsStr::from_bytes(DEBUG_PATH)).is_dir() {
290 1
291 } else {
292 2
293 };
294 DEBUG_PATH_EXISTS.store(exists, Ordering::Relaxed);
295 }
296 exists == 1
297 } else {
298 false
299 }
300 }
301 }
302
303 /// Locate a debug file based on its build ID.
304 ///
305 /// The format of build id paths is documented at:
306 /// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
locate_build_id(build_id: &[u8]) -> Option<PathBuf>307 fn locate_build_id(build_id: &[u8]) -> Option<PathBuf> {
308 const BUILD_ID_PATH: &[u8] = b"/usr/lib/debug/.build-id/";
309 const BUILD_ID_SUFFIX: &[u8] = b".debug";
310
311 if build_id.len() < 2 {
312 return None;
313 }
314
315 if !debug_path_exists() {
316 return None;
317 }
318
319 let mut path =
320 Vec::with_capacity(BUILD_ID_PATH.len() + BUILD_ID_SUFFIX.len() + build_id.len() * 2 + 1);
321 path.extend(BUILD_ID_PATH);
322 path.push(hex(build_id[0] >> 4));
323 path.push(hex(build_id[0] & 0xf));
324 path.push(b'/');
325 for byte in &build_id[1..] {
326 path.push(hex(byte >> 4));
327 path.push(hex(byte & 0xf));
328 }
329 path.extend(BUILD_ID_SUFFIX);
330 Some(PathBuf::from(OsString::from_vec(path)))
331 }
332
hex(byte: u8) -> u8333 fn hex(byte: u8) -> u8 {
334 if byte < 10 {
335 b'0' + byte
336 } else {
337 b'a' + byte - 10
338 }
339 }
340
341 /// Locate a file specified in a `.gnu_debuglink` section.
342 ///
343 /// `path` is the file containing the section.
344 /// `filename` is from the contents of the section.
345 ///
346 /// Search order is based on gdb, documented at:
347 /// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
348 ///
349 /// gdb also allows the user to customize the debug search path, but we don't.
350 ///
351 /// gdb also supports debuginfod, but we don't yet.
locate_debuglink(path: &Path, filename: &[u8]) -> Option<PathBuf>352 fn locate_debuglink(path: &Path, filename: &[u8]) -> Option<PathBuf> {
353 let path = fs::canonicalize(path).ok()?;
354 let parent = path.parent()?;
355 let mut f = PathBuf::from(OsString::with_capacity(
356 DEBUG_PATH.len() + parent.as_os_str().len() + filename.len() + 2,
357 ));
358 let filename = Path::new(OsStr::from_bytes(filename));
359
360 // Try "/parent/filename" if it differs from "path"
361 f.push(parent);
362 f.push(filename);
363 if f != path && f.is_file() {
364 return Some(f);
365 }
366
367 // Try "/parent/.debug/filename"
368 let mut s = OsString::from(f);
369 s.clear();
370 f = PathBuf::from(s);
371 f.push(parent);
372 f.push(".debug");
373 f.push(filename);
374 if f.is_file() {
375 return Some(f);
376 }
377
378 if debug_path_exists() {
379 // Try "/usr/lib/debug/parent/filename"
380 let mut s = OsString::from(f);
381 s.clear();
382 f = PathBuf::from(s);
383 f.push(OsStr::from_bytes(DEBUG_PATH));
384 f.push(parent.strip_prefix("/").unwrap());
385 f.push(filename);
386 if f.is_file() {
387 return Some(f);
388 }
389 }
390
391 None
392 }
393
394 /// Locate a file specified in a `.gnu_debugaltlink` section.
395 ///
396 /// `path` is the file containing the section.
397 /// `filename` and `build_id` are the contents of the section.
398 ///
399 /// Search order is based on gdb:
400 /// - filename, which is either absolute or relative to `path`
401 /// - the build ID path under `BUILD_ID_PATH`
402 ///
403 /// gdb also allows the user to customize the debug search path, but we don't.
404 ///
405 /// gdb also supports debuginfod, but we don't yet.
locate_debugaltlink(path: &Path, filename: &[u8], build_id: &[u8]) -> Option<PathBuf>406 fn locate_debugaltlink(path: &Path, filename: &[u8], build_id: &[u8]) -> Option<PathBuf> {
407 let filename = Path::new(OsStr::from_bytes(filename));
408 if filename.is_absolute() {
409 if filename.is_file() {
410 return Some(filename.into());
411 }
412 } else {
413 let path = fs::canonicalize(path).ok()?;
414 let parent = path.parent()?;
415 let mut f = PathBuf::from(parent);
416 f.push(filename);
417 if f.is_file() {
418 return Some(f);
419 }
420 }
421
422 locate_build_id(build_id)
423 }
424