]> git.proxmox.com Git - rustc.git/blame - vendor/backtrace/src/symbolize/gimli.rs
New upstream version 1.42.0+dfsg0+pve1
[rustc.git] / vendor / backtrace / src / symbolize / gimli.rs
CommitLineData
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
7use self::gimli::read::EndianSlice;
8use self::gimli::LittleEndian as Endian;
9use crate::symbolize::dladdr;
10use crate::symbolize::ResolveWhat;
11use crate::types::BytesOrWideString;
12use crate::SymbolName;
13use addr2line::gimli;
14use core::convert::TryFrom;
15use core::mem;
16use core::u32;
ea8adc8c 17use findshlibs::{self, Segment, SharedLibrary};
dc9dc135 18use libc::c_void;
83c7162d 19use memmap::Mmap;
ea8adc8c 20use std::env;
e1599b0c 21use std::ffi::OsString;
83c7162d 22use std::fs::File;
e1599b0c 23use std::path::Path;
0731742a 24use std::prelude::v1::*;
ea8adc8c
XL
25
26const MAPPINGS_CACHE_SIZE: usize = 4;
27
416331ca
XL
28struct Context<'a> {
29 dwarf: addr2line::Context<EndianSlice<'a, Endian>>,
30 object: Object<'a>,
31}
83c7162d
XL
32
33struct 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
39fn 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
64fn assert_lifetimes<'a>(_: &'a Mmap, _: &Context<'a>) {}
65
66macro_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
78fn 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
84cfg_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 &section_name[..] == name || {
189 section_name.starts_with("__")
190 && name.starts_with(".")
191 && &section_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 287impl 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)]
367struct 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
383struct Library {
384 name: OsString,
385 segments: Vec<LibrarySegment>,
386 bias: findshlibs::Bias,
387}
416331ca 388
e1599b0c
XL
389struct 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
395pub unsafe fn clear_symbol_cache() {
e1599b0c 396 Cache::with_global(|cache| cache.mappings.clear());
416331ca
XL
397}
398
e1599b0c
XL
399impl 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 524pub 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
570struct 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
576impl 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
588impl 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
603pub 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
619impl 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}