]>
Commit | Line | Data |
---|---|---|
dc9dc135 XL |
1 | //! Support for symbolication using the `gimli` crate on crates.io |
2 | //! | |
3 | //! This implementation is largely a work in progress and is off by default for | |
4 | //! all platforms, but it's hoped to be developed over time! Long-term this is | |
5 | //! intended to wholesale replace the `libbacktrace.rs` implementation. | |
6 | ||
416331ca XL |
7 | use self::gimli::read::EndianSlice; |
8 | use self::gimli::LittleEndian as Endian; | |
9 | use crate::symbolize::dladdr; | |
10 | use crate::symbolize::ResolveWhat; | |
11 | use crate::types::BytesOrWideString; | |
12 | use crate::SymbolName; | |
13 | use addr2line::gimli; | |
14 | use core::convert::TryFrom; | |
15 | use core::mem; | |
16 | use core::u32; | |
ea8adc8c | 17 | use findshlibs::{self, Segment, SharedLibrary}; |
dc9dc135 | 18 | use libc::c_void; |
83c7162d | 19 | use memmap::Mmap; |
ea8adc8c | 20 | use std::env; |
e1599b0c | 21 | use std::ffi::OsString; |
83c7162d | 22 | use std::fs::File; |
e1599b0c | 23 | use std::path::Path; |
0731742a | 24 | use std::prelude::v1::*; |
ea8adc8c XL |
25 | |
26 | const MAPPINGS_CACHE_SIZE: usize = 4; | |
27 | ||
416331ca XL |
28 | struct Context<'a> { |
29 | dwarf: addr2line::Context<EndianSlice<'a, Endian>>, | |
30 | object: Object<'a>, | |
31 | } | |
83c7162d XL |
32 | |
33 | struct Mapping { | |
34 | // 'static lifetime is a lie to hack around lack of support for self-referential structs. | |
416331ca | 35 | cx: Context<'static>, |
83c7162d XL |
36 | _map: Mmap, |
37 | } | |
38 | ||
416331ca XL |
39 | fn cx<'data>(object: Object<'data>) -> Option<Context<'data>> { |
40 | fn load_section<'data, S>(obj: &Object<'data>) -> S | |
41 | where | |
42 | S: gimli::Section<gimli::EndianSlice<'data, Endian>>, | |
43 | { | |
44 | let data = obj.section(S::section_name()).unwrap_or(&[]); | |
45 | S::from(EndianSlice::new(data, Endian)) | |
46 | } | |
47 | ||
48 | let dwarf = addr2line::Context::from_sections( | |
49 | load_section(&object), | |
50 | load_section(&object), | |
51 | load_section(&object), | |
52 | load_section(&object), | |
53 | load_section(&object), | |
54 | load_section(&object), | |
55 | load_section(&object), | |
56 | load_section(&object), | |
57 | load_section(&object), | |
58 | gimli::EndianSlice::new(&[], Endian), | |
59 | ) | |
60 | .ok()?; | |
61 | Some(Context { dwarf, object }) | |
62 | } | |
63 | ||
64 | fn assert_lifetimes<'a>(_: &'a Mmap, _: &Context<'a>) {} | |
65 | ||
66 | macro_rules! mk { | |
67 | (Mapping { $map:expr, $inner:expr }) => {{ | |
68 | assert_lifetimes(&$map, &$inner); | |
69 | Mapping { | |
70 | // Convert to 'static lifetimes since the symbols should | |
71 | // only borrow `map` and we're preserving `map` below. | |
72 | cx: unsafe { mem::transmute::<Context<'_>, Context<'static>>($inner) }, | |
73 | _map: $map, | |
74 | } | |
75 | }}; | |
76 | } | |
77 | ||
78 | fn mmap(path: &Path) -> Option<Mmap> { | |
79 | let file = File::open(path).ok()?; | |
80 | // TODO: not completely safe, see https://github.com/danburkert/memmap-rs/issues/25 | |
81 | unsafe { Mmap::map(&file).ok() } | |
82 | } | |
83 | ||
84 | cfg_if::cfg_if! { | |
85 | if #[cfg(windows)] { | |
86 | use std::cmp; | |
87 | use goblin::pe::{self, PE}; | |
88 | use goblin::strtab::Strtab; | |
89 | ||
90 | struct Object<'a> { | |
91 | pe: PE<'a>, | |
92 | data: &'a [u8], | |
93 | symbols: Vec<(usize, pe::symbol::Symbol)>, | |
94 | strtab: Strtab<'a>, | |
95 | } | |
96 | ||
97 | impl<'a> Object<'a> { | |
98 | fn parse(data: &'a [u8]) -> Option<Object<'a>> { | |
99 | let pe = PE::parse(data).ok()?; | |
100 | let syms = pe.header.coff_header.symbols(data).ok()?; | |
101 | let strtab = pe.header.coff_header.strings(data).ok()?; | |
102 | ||
103 | // Collect all the symbols into a local vector which is sorted | |
104 | // by address and contains enough data to learn about the symbol | |
105 | // name. Note that we only look at function symbols and also | |
106 | // note that the sections are 1-indexed because the zero section | |
107 | // is special (apparently). | |
108 | let mut symbols = Vec::new(); | |
109 | for (_, _, sym) in syms.iter() { | |
110 | if sym.derived_type() != pe::symbol::IMAGE_SYM_DTYPE_FUNCTION | |
111 | || sym.section_number == 0 | |
112 | { | |
113 | continue; | |
114 | } | |
115 | let addr = usize::try_from(sym.value).ok()?; | |
116 | let section = pe.sections.get(usize::try_from(sym.section_number).ok()? - 1)?; | |
117 | let va = usize::try_from(section.virtual_address).ok()?; | |
118 | symbols.push((addr + va + pe.image_base, sym)); | |
119 | } | |
120 | symbols.sort_unstable_by_key(|x| x.0); | |
121 | Some(Object { pe, data, symbols, strtab }) | |
122 | } | |
123 | ||
124 | fn section(&self, name: &str) -> Option<&'a [u8]> { | |
125 | let section = self.pe | |
126 | .sections | |
127 | .iter() | |
128 | .find(|section| section.name().ok() == Some(name)); | |
129 | section | |
130 | .and_then(|section| { | |
131 | let offset = section.pointer_to_raw_data as usize; | |
132 | let size = cmp::min(section.virtual_size, section.size_of_raw_data) as usize; | |
133 | self.data.get(offset..).and_then(|data| data.get(..size)) | |
134 | }) | |
135 | } | |
136 | ||
137 | fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> { | |
138 | // Note that unlike other formats COFF doesn't embed the size of | |
139 | // each symbol. As a last ditch effort search for the *closest* | |
140 | // symbol to a particular address and return that one. This gets | |
141 | // really wonky once symbols start getting removed because the | |
142 | // symbols returned here can be totally incorrect, but we have | |
143 | // no idea of knowing how to detect that. | |
144 | let addr = usize::try_from(addr).ok()?; | |
145 | let i = match self.symbols.binary_search_by_key(&addr, |p| p.0) { | |
146 | Ok(i) => i, | |
147 | // typically `addr` isn't in the array, but `i` is where | |
148 | // we'd insert it, so the previous position must be the | |
149 | // greatest less than `addr` | |
150 | Err(i) => i.checked_sub(1)?, | |
151 | }; | |
152 | Some(self.symbols[i].1.name(&self.strtab).ok()?.as_bytes()) | |
153 | } | |
154 | } | |
155 | } else if #[cfg(target_os = "macos")] { | |
156 | use goblin::mach::MachO; | |
157 | ||
158 | struct Object<'a> { | |
159 | macho: MachO<'a>, | |
160 | dwarf: Option<usize>, | |
161 | } | |
162 | ||
163 | impl<'a> Object<'a> { | |
164 | fn parse(macho: MachO<'a>) -> Option<Object<'a>> { | |
165 | if !macho.little_endian { | |
166 | return None; | |
167 | } | |
168 | let dwarf = macho | |
169 | .segments | |
170 | .iter() | |
171 | .enumerate() | |
172 | .find(|(_, segment)| segment.name().ok() == Some("__DWARF")) | |
173 | .map(|p| p.0); | |
174 | Some(Object { macho, dwarf }) | |
175 | } | |
176 | ||
177 | fn section(&self, name: &str) -> Option<&'a [u8]> { | |
178 | let dwarf = self.dwarf?; | |
179 | let dwarf = &self.macho.segments[dwarf]; | |
180 | dwarf | |
181 | .into_iter() | |
182 | .filter_map(|s| s.ok()) | |
183 | .find(|(section, _data)| { | |
184 | let section_name = match section.name() { | |
185 | Ok(s) => s, | |
186 | Err(_) => return false, | |
187 | }; | |
188 | §ion_name[..] == name || { | |
189 | section_name.starts_with("__") | |
190 | && name.starts_with(".") | |
191 | && §ion_name[2..] == &name[1..] | |
192 | } | |
193 | }) | |
194 | .map(|p| p.1) | |
195 | } | |
196 | ||
197 | fn search_symtab<'b>(&'b self, _addr: u64) -> Option<&'b [u8]> { | |
198 | // So far it seems that we don't need to implement this. Maybe | |
199 | // `dladdr` on OSX has us covered? Maybe there's not much in the | |
200 | // symbol table? In any case our relevant tests are passing | |
201 | // without this being implemented, so let's skip it for now. | |
202 | None | |
203 | } | |
204 | } | |
205 | } else { | |
206 | use goblin::elf::Elf; | |
207 | ||
208 | struct Object<'a> { | |
209 | elf: Elf<'a>, | |
210 | data: &'a [u8], | |
e1599b0c XL |
211 | // List of pre-parsed and sorted symbols by base address. The |
212 | // boolean indicates whether it comes from the dynamic symbol table | |
213 | // or the normal symbol table, affecting where it's symbolicated. | |
214 | syms: Vec<(goblin::elf::Sym, bool)>, | |
416331ca XL |
215 | } |
216 | ||
217 | impl<'a> Object<'a> { | |
218 | fn parse(data: &'a [u8]) -> Option<Object<'a>> { | |
219 | let elf = Elf::parse(data).ok()?; | |
220 | if !elf.little_endian { | |
221 | return None; | |
222 | } | |
e1599b0c XL |
223 | let mut syms = elf |
224 | .syms | |
225 | .iter() | |
226 | .map(|s| (s, false)) | |
227 | .chain(elf.dynsyms.iter().map(|s| (s, true))) | |
228 | // Only look at function/object symbols. This mirrors what | |
229 | // libbacktrace does and in general we're only symbolicating | |
230 | // function addresses in theory. Object symbols correspond | |
231 | // to data, and maybe someone's crazy enough to have a | |
232 | // function go into static data? | |
233 | .filter(|(s, _)| { | |
234 | s.is_function() || s.st_type() == goblin::elf::sym::STT_OBJECT | |
235 | }) | |
236 | // skip anything that's in an undefined section header, | |
237 | // since it means it's an imported function and we're only | |
238 | // symbolicating with locally defined functions. | |
239 | .filter(|(s, _)| { | |
240 | s.st_shndx != goblin::elf::section_header::SHN_UNDEF as usize | |
241 | }) | |
242 | .collect::<Vec<_>>(); | |
243 | syms.sort_unstable_by_key(|s| s.0.st_value); | |
244 | Some(Object { | |
245 | syms, | |
246 | elf, | |
247 | data, | |
248 | }) | |
416331ca XL |
249 | } |
250 | ||
251 | fn section(&self, name: &str) -> Option<&'a [u8]> { | |
252 | let section = self.elf.section_headers.iter().find(|section| { | |
253 | match self.elf.shdr_strtab.get(section.sh_name) { | |
254 | Some(Ok(section_name)) => section_name == name, | |
255 | _ => false, | |
256 | } | |
257 | }); | |
258 | section | |
259 | .and_then(|section| { | |
260 | self.data.get(section.sh_offset as usize..) | |
261 | .and_then(|data| data.get(..section.sh_size as usize)) | |
262 | }) | |
263 | } | |
264 | ||
265 | fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> { | |
e1599b0c XL |
266 | // Same sort of binary search as Windows above |
267 | let i = match self.syms.binary_search_by_key(&addr, |s| s.0.st_value) { | |
268 | Ok(i) => i, | |
269 | Err(i) => i.checked_sub(1)?, | |
270 | }; | |
271 | let (sym, dynamic) = self.syms.get(i)?; | |
272 | if sym.st_value <= addr && addr <= sym.st_value + sym.st_size { | |
273 | let strtab = if *dynamic { | |
274 | &self.elf.dynstrtab | |
275 | } else { | |
276 | &self.elf.strtab | |
277 | }; | |
278 | Some(strtab.get(sym.st_name)?.ok()?.as_bytes()) | |
279 | } else { | |
280 | None | |
281 | } | |
416331ca XL |
282 | } |
283 | } | |
284 | } | |
285 | } | |
286 | ||
83c7162d | 287 | impl Mapping { |
416331ca XL |
288 | #[cfg(not(target_os = "macos"))] |
289 | fn new(path: &Path) -> Option<Mapping> { | |
290 | let map = mmap(path)?; | |
291 | let cx = cx(Object::parse(&map)?)?; | |
292 | Some(mk!(Mapping { map, cx })) | |
293 | } | |
294 | ||
295 | // The loading path for OSX is is so different we just have a completely | |
296 | // different implementation of the function here. On OSX we need to go | |
297 | // probing the filesystem for a bunch of files. | |
298 | #[cfg(target_os = "macos")] | |
299 | fn new(path: &Path) -> Option<Mapping> { | |
300 | // First up we need to load the unique UUID which is stored in the macho | |
301 | // header of the file we're reading, specified at `path`. | |
302 | let map = mmap(path)?; | |
303 | let macho = MachO::parse(&map, 0).ok()?; | |
304 | let uuid = find_uuid(&macho)?; | |
305 | ||
306 | // Next we need to look for a `*.dSYM` file. For now we just probe the | |
307 | // containing directory and look around for something that matches | |
308 | // `*.dSYM`. Once it's found we root through the dwarf resources that it | |
309 | // contains and try to find a macho file which has a matching UUID as | |
310 | // the one of our own file. If we find a match that's the dwarf file we | |
311 | // want to return. | |
312 | let parent = path.parent()?; | |
313 | for entry in parent.read_dir().ok()? { | |
314 | let entry = entry.ok()?; | |
315 | let filename = match entry.file_name().into_string() { | |
316 | Ok(name) => name, | |
317 | Err(_) => continue, | |
318 | }; | |
319 | if !filename.ends_with(".dSYM") { | |
320 | continue; | |
321 | } | |
322 | let candidates = entry.path().join("Contents/Resources/DWARF"); | |
323 | if let Some(mapping) = load_dsym(&candidates, &uuid) { | |
324 | return Some(mapping); | |
325 | } | |
326 | } | |
327 | ||
328 | // Looks like nothing matched our UUID, so let's at least return our own | |
329 | // file. This should have the symbol table for at least some | |
330 | // symbolication purposes. | |
331 | let inner = cx(Object::parse(macho)?)?; | |
332 | return Some(mk!(Mapping { map, inner })); | |
333 | ||
334 | fn load_dsym(dir: &Path, uuid: &[u8; 16]) -> Option<Mapping> { | |
335 | for entry in dir.read_dir().ok()? { | |
336 | let entry = entry.ok()?; | |
337 | let map = mmap(&entry.path())?; | |
338 | let macho = MachO::parse(&map, 0).ok()?; | |
339 | let entry_uuid = find_uuid(&macho)?; | |
340 | if entry_uuid != uuid { | |
341 | continue; | |
342 | } | |
343 | if let Some(cx) = Object::parse(macho).and_then(cx) { | |
344 | return Some(mk!(Mapping { map, cx })); | |
345 | } | |
346 | } | |
347 | ||
348 | None | |
349 | } | |
350 | ||
351 | fn find_uuid<'a>(object: &'a MachO) -> Option<&'a [u8; 16]> { | |
352 | use goblin::mach::load_command::CommandVariant; | |
353 | ||
354 | object | |
355 | .load_commands | |
356 | .iter() | |
357 | .filter_map(|cmd| match &cmd.command { | |
358 | CommandVariant::Uuid(u) => Some(&u.uuid), | |
359 | _ => None, | |
360 | }) | |
361 | .next() | |
362 | } | |
83c7162d | 363 | } |
e1599b0c | 364 | } |
83c7162d | 365 | |
e1599b0c XL |
366 | #[derive(Default)] |
367 | struct Cache { | |
368 | /// All known shared libraries that have been loaded. | |
369 | libraries: Vec<Library>, | |
370 | ||
371 | /// Mappings cache where we retain parsed dwarf information. | |
372 | /// | |
373 | /// This list has a fixed capacity for its entire liftime which never | |
374 | /// increases. The `usize` element of each pair is an index into `libraries` | |
375 | /// above where `usize::max_value()` represents the current executable. The | |
376 | /// `Mapping` is corresponding parsed dwarf information. | |
377 | /// | |
378 | /// Note that this is basically an LRU cache and we'll be shifting things | |
379 | /// around in here as we symbolize addresses. | |
380 | mappings: Vec<(usize, Mapping)>, | |
83c7162d XL |
381 | } |
382 | ||
e1599b0c XL |
383 | struct Library { |
384 | name: OsString, | |
385 | segments: Vec<LibrarySegment>, | |
386 | bias: findshlibs::Bias, | |
387 | } | |
416331ca | 388 | |
e1599b0c XL |
389 | struct LibrarySegment { |
390 | len: usize, | |
391 | stated_virtual_memory_address: findshlibs::Svma, | |
ea8adc8c XL |
392 | } |
393 | ||
416331ca XL |
394 | // unsafe because this is required to be externally synchronized |
395 | pub unsafe fn clear_symbol_cache() { | |
e1599b0c | 396 | Cache::with_global(|cache| cache.mappings.clear()); |
416331ca XL |
397 | } |
398 | ||
e1599b0c XL |
399 | impl Cache { |
400 | fn new() -> Cache { | |
401 | let mut libraries = Vec::new(); | |
402 | // Iterate over all loaded shared libraries and cache information we | |
403 | // learn about them. This way we only load information here once instead | |
404 | // of once per symbol. | |
405 | findshlibs::TargetSharedLibrary::each(|so| { | |
406 | use findshlibs::IterationControl::*; | |
407 | libraries.push(Library { | |
408 | name: so.name().to_owned(), | |
409 | segments: so | |
410 | .segments() | |
411 | .map(|s| LibrarySegment { | |
412 | len: s.len(), | |
413 | stated_virtual_memory_address: s.stated_virtual_memory_address(), | |
414 | }) | |
415 | .collect(), | |
416 | bias: so.virtual_memory_bias(), | |
417 | }); | |
418 | Continue | |
419 | }); | |
420 | ||
421 | Cache { | |
422 | mappings: Vec::with_capacity(MAPPINGS_CACHE_SIZE), | |
423 | libraries, | |
424 | } | |
425 | } | |
426 | ||
427 | // unsafe because this is required to be externally synchronized | |
428 | unsafe fn with_global(f: impl FnOnce(&mut Self)) { | |
429 | // A very small, very simple LRU cache for debug info mappings. | |
430 | // | |
431 | // The hit rate should be very high, since the typical stack doesn't cross | |
432 | // between many shared libraries. | |
433 | // | |
434 | // The `addr2line::Context` structures are pretty expensive to create. Its | |
435 | // cost is expected to be amortized by subsequent `locate` queries, which | |
436 | // leverage the structures built when constructing `addr2line::Context`s to | |
437 | // get nice speedups. If we didn't have this cache, that amortization would | |
438 | // never happen, and symbolicating backtraces would be ssssllllooooowwww. | |
439 | static mut MAPPINGS_CACHE: Option<Cache> = None; | |
440 | ||
441 | f(MAPPINGS_CACHE.get_or_insert_with(|| Cache::new())) | |
442 | } | |
443 | ||
444 | fn avma_to_svma(&self, addr: *const u8) -> Option<(usize, findshlibs::Svma)> { | |
445 | // Note that for now `findshlibs` is unimplemented on Windows, so we | |
446 | // just unhelpfully assume that the address is an SVMA. Surprisingly it | |
447 | // seems to at least somewhat work on Wine on Linux though... | |
448 | // | |
449 | // This probably means ASLR on Windows is busted. | |
450 | if cfg!(windows) { | |
451 | let addr = findshlibs::Svma(addr); | |
452 | return Some((usize::max_value(), addr)); | |
453 | } | |
454 | ||
455 | self.libraries | |
456 | .iter() | |
457 | .enumerate() | |
458 | .filter_map(|(i, lib)| { | |
459 | // First up, test if this `lib` has any segment containing the | |
460 | // `addr` (handling relocation). If this check passes then we | |
461 | // can continue below and actually translate the address. | |
462 | // | |
463 | // Note that this is an inlining of `contains_avma` in the | |
464 | // `findshlibs` crate here. | |
465 | if !lib.segments.iter().any(|s| { | |
466 | let svma = s.stated_virtual_memory_address; | |
467 | let start = unsafe { svma.0.offset(lib.bias.0) as usize }; | |
468 | let end = start + s.len; | |
469 | let address = addr as usize; | |
470 | start <= address && address < end | |
471 | }) { | |
472 | return None; | |
473 | } | |
474 | ||
475 | // Now that we know `lib` contains `addr`, do the equiavlent of | |
476 | // `avma_to_svma` in the `findshlibs` crate. | |
477 | let reverse_bias = -lib.bias.0; | |
478 | let svma = findshlibs::Svma(unsafe { addr.offset(reverse_bias) }); | |
479 | Some((i, svma)) | |
480 | }) | |
481 | .next() | |
482 | } | |
483 | ||
484 | fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a Context<'a>> { | |
485 | let idx = self.mappings.iter().position(|(idx, _)| *idx == lib); | |
ea8adc8c XL |
486 | |
487 | // Invariant: after this conditional completes without early returning | |
488 | // from an error, the cache entry for this path is at index 0. | |
489 | ||
490 | if let Some(idx) = idx { | |
491 | // When the mapping is already in the cache, move it to the front. | |
492 | if idx != 0 { | |
e1599b0c XL |
493 | let entry = self.mappings.remove(idx); |
494 | self.mappings.insert(0, entry); | |
ea8adc8c XL |
495 | } |
496 | } else { | |
497 | // When the mapping is not in the cache, create a new mapping, | |
498 | // insert it into the front of the cache, and evict the oldest cache | |
499 | // entry if necessary. | |
e1599b0c XL |
500 | let storage; |
501 | let path = match self.libraries.get(lib) { | |
502 | Some(lib) => &lib.name, | |
503 | None => { | |
504 | storage = env::current_exe().ok()?.into(); | |
505 | &storage | |
506 | } | |
ea8adc8c | 507 | }; |
e1599b0c | 508 | let mapping = Mapping::new(path.as_ref())?; |
ea8adc8c | 509 | |
e1599b0c XL |
510 | if self.mappings.len() == MAPPINGS_CACHE_SIZE { |
511 | self.mappings.pop(); | |
ea8adc8c XL |
512 | } |
513 | ||
e1599b0c | 514 | self.mappings.insert(0, (lib, mapping)); |
ea8adc8c XL |
515 | } |
516 | ||
e1599b0c XL |
517 | let cx: &'a Context<'static> = &self.mappings[0].1.cx; |
518 | // don't leak the `'static` lifetime, make sure it's scoped to just | |
519 | // ourselves | |
520 | Some(unsafe { mem::transmute::<&'a Context<'static>, &'a Context<'a>>(cx) }) | |
521 | } | |
ea8adc8c XL |
522 | } |
523 | ||
416331ca | 524 | pub unsafe fn resolve(what: ResolveWhat, cb: &mut FnMut(&super::Symbol)) { |
dc9dc135 | 525 | let addr = what.address_or_ip(); |
416331ca XL |
526 | let mut cb = DladdrFallback { |
527 | cb, | |
528 | addr, | |
529 | called: false, | |
530 | }; | |
dc9dc135 | 531 | |
e1599b0c XL |
532 | Cache::with_global(|cache| { |
533 | let (lib, addr) = match cache.avma_to_svma(addr as *const u8) { | |
534 | Some(pair) => pair, | |
416331ca | 535 | None => return, |
e1599b0c | 536 | }; |
ea8adc8c | 537 | |
e1599b0c XL |
538 | // Finally, get a cached mapping or create a new mapping for this file, and |
539 | // evaluate the DWARF info to find the file/line/name for this address. | |
540 | let cx = match cache.mapping_for_lib(lib) { | |
541 | Some(cx) => cx, | |
542 | None => return, | |
543 | }; | |
416331ca XL |
544 | if let Ok(mut frames) = cx.dwarf.find_frames(addr.0 as u64) { |
545 | while let Ok(Some(mut frame)) = frames.next() { | |
546 | let function = frame.function.take(); | |
547 | let name = function.as_ref().and_then(|f| f.raw_name().ok()); | |
548 | let name = name.as_ref().map(|n| n.as_bytes()); | |
416331ca XL |
549 | cb.call(Symbol::Frame { |
550 | addr: addr.0 as *mut c_void, | |
551 | frame, | |
552 | name, | |
553 | }); | |
83c7162d XL |
554 | } |
555 | } | |
ea8adc8c | 556 | |
e1599b0c | 557 | if !cb.called { |
416331ca XL |
558 | if let Some(name) = cx.object.search_symtab(addr.0 as u64) { |
559 | cb.call(Symbol::Symtab { | |
560 | addr: addr.0 as *mut c_void, | |
561 | name, | |
562 | }); | |
83c7162d XL |
563 | } |
564 | } | |
ea8adc8c | 565 | }); |
416331ca XL |
566 | |
567 | drop(cb); | |
ea8adc8c XL |
568 | } |
569 | ||
416331ca XL |
570 | struct DladdrFallback<'a, 'b> { |
571 | addr: *mut c_void, | |
572 | called: bool, | |
573 | cb: &'a mut (FnMut(&super::Symbol) + 'b), | |
ea8adc8c XL |
574 | } |
575 | ||
416331ca XL |
576 | impl DladdrFallback<'_, '_> { |
577 | fn call(&mut self, sym: Symbol) { | |
578 | self.called = true; | |
579 | ||
580 | // Extend the lifetime of `sym` to `'static` since we are unfortunately | |
581 | // required to here, but it's ony ever going out as a reference so no | |
582 | // reference to it should be persisted beyond this frame anyway. | |
583 | let sym = unsafe { mem::transmute::<Symbol, Symbol<'static>>(sym) }; | |
584 | (self.cb)(&super::Symbol { inner: sym }); | |
585 | } | |
586 | } | |
587 | ||
588 | impl Drop for DladdrFallback<'_, '_> { | |
589 | fn drop(&mut self) { | |
590 | if self.called { | |
591 | return; | |
592 | } | |
593 | unsafe { | |
594 | dladdr::resolve(self.addr, &mut |sym| { | |
595 | (self.cb)(&super::Symbol { | |
596 | inner: Symbol::Dladdr(sym), | |
597 | }) | |
598 | }); | |
ea8adc8c XL |
599 | } |
600 | } | |
416331ca | 601 | } |
ea8adc8c | 602 | |
416331ca XL |
603 | pub enum Symbol<'a> { |
604 | /// We were able to locate frame information for this symbol, and | |
605 | /// `addr2line`'s frame internally has all the nitty gritty details. | |
606 | Frame { | |
607 | addr: *mut c_void, | |
608 | frame: addr2line::Frame<EndianSlice<'a, Endian>>, | |
609 | name: Option<&'a [u8]>, | |
610 | }, | |
611 | /// Couldn't find debug information, but we found it in the symbol table of | |
612 | /// the elf executable. | |
613 | Symtab { addr: *mut c_void, name: &'a [u8] }, | |
614 | /// We weren't able to find anything in the original file, so we had to fall | |
615 | /// back to using `dladdr` which had a hit. | |
616 | Dladdr(dladdr::Symbol<'a>), | |
617 | } | |
618 | ||
619 | impl Symbol<'_> { | |
ea8adc8c | 620 | pub fn name(&self) -> Option<SymbolName> { |
416331ca XL |
621 | match self { |
622 | Symbol::Dladdr(s) => s.name(), | |
623 | Symbol::Frame { name, .. } => { | |
624 | let name = name.as_ref()?; | |
625 | Some(SymbolName::new(name)) | |
626 | } | |
627 | Symbol::Symtab { name, .. } => Some(SymbolName::new(name)), | |
628 | } | |
ea8adc8c XL |
629 | } |
630 | ||
631 | pub fn addr(&self) -> Option<*mut c_void> { | |
416331ca XL |
632 | match self { |
633 | Symbol::Dladdr(s) => s.addr(), | |
634 | Symbol::Frame { addr, .. } => Some(*addr), | |
635 | Symbol::Symtab { .. } => None, | |
636 | } | |
ea8adc8c XL |
637 | } |
638 | ||
0731742a | 639 | pub fn filename_raw(&self) -> Option<BytesOrWideString> { |
416331ca XL |
640 | match self { |
641 | Symbol::Dladdr(s) => return s.filename_raw(), | |
642 | Symbol::Frame { frame, .. } => { | |
643 | let location = frame.location.as_ref()?; | |
644 | let file = location.file.as_ref()?; | |
645 | Some(BytesOrWideString::Bytes(file.as_bytes())) | |
646 | } | |
647 | Symbol::Symtab { .. } => None, | |
648 | } | |
dc9dc135 XL |
649 | } |
650 | ||
651 | pub fn filename(&self) -> Option<&Path> { | |
416331ca XL |
652 | match self { |
653 | Symbol::Dladdr(s) => return s.filename(), | |
654 | Symbol::Frame { frame, .. } => { | |
655 | let location = frame.location.as_ref()?; | |
656 | let file = location.file.as_ref()?; | |
657 | Some(Path::new(file)) | |
658 | } | |
659 | Symbol::Symtab { .. } => None, | |
660 | } | |
ea8adc8c XL |
661 | } |
662 | ||
663 | pub fn lineno(&self) -> Option<u32> { | |
416331ca XL |
664 | match self { |
665 | Symbol::Dladdr(s) => return s.lineno(), | |
666 | Symbol::Frame { frame, .. } => { | |
667 | let location = frame.location.as_ref()?; | |
668 | location.line.and_then(|l| u32::try_from(l).ok()) | |
dc9dc135 | 669 | } |
416331ca XL |
670 | Symbol::Symtab { .. } => None, |
671 | } | |
ea8adc8c XL |
672 | } |
673 | } |