1//! Support for symbolication using the `gimli` crate on crates.io
2//!
3//! This is the default symbolication implementation for Rust.
4
5use self::gimli::read::EndianSlice;
6use self::gimli::NativeEndian as Endian;
7use self::mmap::Mmap;
8use self::stash::Stash;
9use super::BytesOrWideString;
10use super::ResolveWhat;
11use super::SymbolName;
12use addr2line::gimli;
13use core::convert::TryInto;
14use core::mem;
15use core::u32;
16use libc::c_void;
17use mystd::ffi::OsString;
18use mystd::fs::File;
19use mystd::path::Path;
20use mystd::prelude::v1::*;
21
22#[cfg(backtrace_in_libstd)]
23mod mystd {
24 pub use crate::*;
25}
26#[cfg(not(backtrace_in_libstd))]
27extern crate std as mystd;
28
29cfg_if::cfg_if! {
30 if #[cfg(windows)] {
31 #[path = "gimli/mmap_windows.rs"]
32 mod mmap;
33 } else if #[cfg(any(
34 target_os = "android",
35 target_os = "freebsd",
36 target_os = "fuchsia",
37 target_os = "haiku",
38 target_os = "hurd",
39 target_os = "ios",
40 target_os = "linux",
41 target_os = "macos",
42 target_os = "openbsd",
43 target_os = "solaris",
44 target_os = "illumos",
45 target_os = "aix",
46 ))] {
47 #[path = "gimli/mmap_unix.rs"]
48 mod mmap;
49 } else {
50 #[path = "gimli/mmap_fake.rs"]
51 mod mmap;
52 }
53}
54
55mod stash;
56
57const MAPPINGS_CACHE_SIZE: usize = 4;
58
59struct Mapping {
60 // 'static lifetime is a lie to hack around lack of support for self-referential structs.
61 cx: Context<'static>,
62 _map: Mmap,
63 stash: Stash,
64}
65
66enum Either<A, B> {
67 #[allow(dead_code)]
68 A(A),
69 B(B),
70}
71
72impl Mapping {
73 /// Creates a `Mapping` by ensuring that the `data` specified is used to
74 /// create a `Context` and it can only borrow from that or the `Stash` of
75 /// decompressed sections or auxiliary data.
76 fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
77 where
78 F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Context<'a>>,
79 {
80 Mapping::mk_or_other(data, move |data, stash| {
81 let cx = mk(data, stash)?;
82 Some(Either::B(cx))
83 })
84 }
85
86 /// Creates a `Mapping` from `data`, or if the closure decides to, returns a
87 /// different mapping.
88 fn mk_or_other<F>(data: Mmap, mk: F) -> Option<Mapping>
89 where
90 F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Either<Mapping, Context<'a>>>,
91 {
92 let stash = Stash::new();
93 let cx = match mk(&data, &stash)? {
94 Either::A(mapping) => return Some(mapping),
95 Either::B(cx) => cx,
96 };
97 Some(Mapping {
98 // Convert to 'static lifetimes since the symbols should
99 // only borrow `map` and `stash` and we're preserving them below.
100 cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
101 _map: data,
102 stash: stash,
103 })
104 }
105}
106
107struct Context<'a> {
108 dwarf: addr2line::Context<EndianSlice<'a, Endian>>,
109 object: Object<'a>,
110 package: Option<gimli::DwarfPackage<EndianSlice<'a, Endian>>>,
111}
112
113impl<'data> Context<'data> {
114 fn new(
115 stash: &'data Stash,
116 object: Object<'data>,
117 sup: Option<Object<'data>>,
118 dwp: Option<Object<'data>>,
119 ) -> Option<Context<'data>> {
120 let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
121 if cfg!(not(target_os = "aix")) {
122 let data = object.section(stash, id.name()).unwrap_or(&[]);
123 Ok(EndianSlice::new(data, Endian))
124 } else {
125 if let Some(name) = id.xcoff_name() {
126 let data = object.section(stash, name).unwrap_or(&[]);
127 Ok(EndianSlice::new(data, Endian))
128 } else {
129 Ok(EndianSlice::new(&[], Endian))
130 }
131 }
132 })
133 .ok()?;
134
135 if let Some(sup) = sup {
136 sections
137 .load_sup(|id| -> Result<_, ()> {
138 let data = sup.section(stash, id.name()).unwrap_or(&[]);
139 Ok(EndianSlice::new(data, Endian))
140 })
141 .ok()?;
142 }
143 let dwarf = addr2line::Context::from_dwarf(sections).ok()?;
144
145 let mut package = None;
146 if let Some(dwp) = dwp {
147 package = Some(
148 gimli::DwarfPackage::load(
149 |id| -> Result<_, gimli::Error> {
150 let data = id
151 .dwo_name()
152 .and_then(|name| dwp.section(stash, name))
153 .unwrap_or(&[]);
154 Ok(EndianSlice::new(data, Endian))
155 },
156 EndianSlice::new(&[], Endian),
157 )
158 .ok()?,
159 );
160 }
161
162 Some(Context {
163 dwarf,
164 object,
165 package,
166 })
167 }
168
169 fn find_frames(
170 &'_ self,
171 stash: &'data Stash,
172 probe: u64,
173 ) -> gimli::Result<addr2line::FrameIter<'_, EndianSlice<'data, Endian>>> {
174 use addr2line::{LookupContinuation, LookupResult};
175
176 let mut l = self.dwarf.find_frames(probe);
177 loop {
178 let (load, continuation) = match l {
179 LookupResult::Output(output) => break output,
180 LookupResult::Load { load, continuation } => (load, continuation),
181 };
182
183 l = continuation.resume(handle_split_dwarf(self.package.as_ref(), stash, load));
184 }
185 }
186}
187
188fn mmap(path: &Path) -> Option<Mmap> {
189 let file: File = File::open(path).ok()?;
190 let len: usize = file.metadata().ok()?.len().try_into().ok()?;
191 unsafe { Mmap::map(&file, len) }
192}
193
194cfg_if::cfg_if! {
195 if #[cfg(windows)] {
196 mod coff;
197 use self::coff::{handle_split_dwarf, Object};
198 } else if #[cfg(any(
199 target_os = "macos",
200 target_os = "ios",
201 target_os = "tvos",
202 target_os = "watchos",
203 ))] {
204 mod macho;
205 use self::macho::{handle_split_dwarf, Object};
206 } else if #[cfg(target_os = "aix")] {
207 mod xcoff;
208 use self::xcoff::{handle_split_dwarf, Object};
209 } else {
210 mod elf;
211 use self::elf::{handle_split_dwarf, Object};
212 }
213}
214
215cfg_if::cfg_if! {
216 if #[cfg(windows)] {
217 mod libs_windows;
218 use libs_windows::native_libraries;
219 } else if #[cfg(any(
220 target_os = "macos",
221 target_os = "ios",
222 target_os = "tvos",
223 target_os = "watchos",
224 ))] {
225 mod libs_macos;
226 use libs_macos::native_libraries;
227 } else if #[cfg(target_os = "illumos")] {
228 mod libs_illumos;
229 use libs_illumos::native_libraries;
230 } else if #[cfg(all(
231 any(
232 target_os = "linux",
233 target_os = "fuchsia",
234 target_os = "freebsd",
235 target_os = "hurd",
236 target_os = "openbsd",
237 target_os = "netbsd",
238 all(target_os = "android", feature = "dl_iterate_phdr"),
239 ),
240 not(target_env = "uclibc"),
241 ))] {
242 mod libs_dl_iterate_phdr;
243 use libs_dl_iterate_phdr::native_libraries;
244 #[path = "gimli/parse_running_mmaps_unix.rs"]
245 mod parse_running_mmaps;
246 } else if #[cfg(target_env = "libnx")] {
247 mod libs_libnx;
248 use libs_libnx::native_libraries;
249 } else if #[cfg(target_os = "haiku")] {
250 mod libs_haiku;
251 use libs_haiku::native_libraries;
252 } else if #[cfg(target_os = "aix")] {
253 mod libs_aix;
254 use libs_aix::native_libraries;
255 } else {
256 // Everything else should doesn't know how to load native libraries.
257 fn native_libraries() -> Vec<Library> {
258 Vec::new()
259 }
260 }
261}
262
263#[derive(Default)]
264struct Cache {
265 /// All known shared libraries that have been loaded.
266 libraries: Vec<Library>,
267
268 /// Mappings cache where we retain parsed dwarf information.
269 ///
270 /// This list has a fixed capacity for its entire lifetime which never
271 /// increases. The `usize` element of each pair is an index into `libraries`
272 /// above where `usize::max_value()` represents the current executable. The
273 /// `Mapping` is corresponding parsed dwarf information.
274 ///
275 /// Note that this is basically an LRU cache and we'll be shifting things
276 /// around in here as we symbolize addresses.
277 mappings: Vec<(usize, Mapping)>,
278}
279
280struct Library {
281 name: OsString,
282 #[cfg(target_os = "aix")]
283 /// On AIX, the library mmapped can be a member of a big-archive file.
284 /// For example, with a big-archive named libfoo.a containing libbar.so,
285 /// one can use `dlopen("libfoo.a(libbar.so)", RTLD_MEMBER | RTLD_LAZY)`
286 /// to use the `libbar.so` library. In this case, only `libbar.so` is
287 /// mmapped, not the whole `libfoo.a`.
288 member_name: OsString,
289 /// Segments of this library loaded into memory, and where they're loaded.
290 segments: Vec<LibrarySegment>,
291 /// The "bias" of this library, typically where it's loaded into memory.
292 /// This value is added to each segment's stated address to get the actual
293 /// virtual memory address that the segment is loaded into. Additionally
294 /// this bias is subtracted from real virtual memory addresses to index into
295 /// debuginfo and the symbol table.
296 bias: usize,
297}
298
299struct LibrarySegment {
300 /// The stated address of this segment in the object file. This is not
301 /// actually where the segment is loaded, but rather this address plus the
302 /// containing library's `bias` is where to find it.
303 stated_virtual_memory_address: usize,
304 /// The size of this segment in memory.
305 len: usize,
306}
307
308#[cfg(target_os = "aix")]
309fn create_mapping(lib: &Library) -> Option<Mapping> {
310 let name = &lib.name;
311 let member_name = &lib.member_name;
312 Mapping::new(name.as_ref(), member_name)
313}
314
315#[cfg(not(target_os = "aix"))]
316fn create_mapping(lib: &Library) -> Option<Mapping> {
317 let name: &OsString = &lib.name;
318 Mapping::new(path:name.as_ref())
319}
320
321// unsafe because this is required to be externally synchronized
322pub unsafe fn clear_symbol_cache() {
323 Cache::with_global(|cache: &mut Cache| cache.mappings.clear());
324}
325
326impl Cache {
327 fn new() -> Cache {
328 Cache {
329 mappings: Vec::with_capacity(MAPPINGS_CACHE_SIZE),
330 libraries: native_libraries(),
331 }
332 }
333
334 // unsafe because this is required to be externally synchronized
335 unsafe fn with_global(f: impl FnOnce(&mut Self)) {
336 // A very small, very simple LRU cache for debug info mappings.
337 //
338 // The hit rate should be very high, since the typical stack doesn't cross
339 // between many shared libraries.
340 //
341 // The `addr2line::Context` structures are pretty expensive to create. Its
342 // cost is expected to be amortized by subsequent `locate` queries, which
343 // leverage the structures built when constructing `addr2line::Context`s to
344 // get nice speedups. If we didn't have this cache, that amortization would
345 // never happen, and symbolicating backtraces would be ssssllllooooowwww.
346 static mut MAPPINGS_CACHE: Option<Cache> = None;
347
348 f(MAPPINGS_CACHE.get_or_insert_with(|| Cache::new()))
349 }
350
351 fn avma_to_svma(&self, addr: *const u8) -> Option<(usize, *const u8)> {
352 self.libraries
353 .iter()
354 .enumerate()
355 .filter_map(|(i, lib)| {
356 // First up, test if this `lib` has any segment containing the
357 // `addr` (handling relocation). If this check passes then we
358 // can continue below and actually translate the address.
359 //
360 // Note that we're using `wrapping_add` here to avoid overflow
361 // checks. It's been seen in the wild that the SVMA + bias
362 // computation overflows. It seems a bit odd that would happen
363 // but there's not a huge amount we can do about it other than
364 // probably just ignore those segments since they're likely
365 // pointing off into space. This originally came up in
366 // rust-lang/backtrace-rs#329.
367 if !lib.segments.iter().any(|s| {
368 let svma = s.stated_virtual_memory_address;
369 let start = svma.wrapping_add(lib.bias);
370 let end = start.wrapping_add(s.len);
371 let address = addr as usize;
372 start <= address && address < end
373 }) {
374 return None;
375 }
376
377 // Now that we know `lib` contains `addr`, we can offset with
378 // the bias to find the stated virtual memory address.
379 let svma = (addr as usize).wrapping_sub(lib.bias);
380 Some((i, svma as *const u8))
381 })
382 .next()
383 }
384
385 fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<(&'a mut Context<'a>, &'a Stash)> {
386 let idx = self.mappings.iter().position(|(idx, _)| *idx == lib);
387
388 // Invariant: after this conditional completes without early returning
389 // from an error, the cache entry for this path is at index 0.
390
391 if let Some(idx) = idx {
392 // When the mapping is already in the cache, move it to the front.
393 if idx != 0 {
394 let entry = self.mappings.remove(idx);
395 self.mappings.insert(0, entry);
396 }
397 } else {
398 // When the mapping is not in the cache, create a new mapping,
399 // insert it into the front of the cache, and evict the oldest cache
400 // entry if necessary.
401 let mapping = create_mapping(&self.libraries[lib])?;
402
403 if self.mappings.len() == MAPPINGS_CACHE_SIZE {
404 self.mappings.pop();
405 }
406
407 self.mappings.insert(0, (lib, mapping));
408 }
409
410 let mapping = &mut self.mappings[0].1;
411 let cx: &'a mut Context<'static> = &mut mapping.cx;
412 let stash: &'a Stash = &mapping.stash;
413 // don't leak the `'static` lifetime, make sure it's scoped to just
414 // ourselves
415 Some((
416 unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) },
417 stash,
418 ))
419 }
420}
421
422pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
423 let addr = what.address_or_ip();
424 let mut call = |sym: Symbol<'_>| {
425 // Extend the lifetime of `sym` to `'static` since we are unfortunately
426 // required to here, but it's only ever going out as a reference so no
427 // reference to it should be persisted beyond this frame anyway.
428 let sym = mem::transmute::<Symbol<'_>, Symbol<'static>>(sym);
429 (cb)(&super::Symbol { inner: sym });
430 };
431
432 Cache::with_global(|cache| {
433 let (lib, addr) = match cache.avma_to_svma(addr.cast_const().cast::<u8>()) {
434 Some(pair) => pair,
435 None => return,
436 };
437
438 // Finally, get a cached mapping or create a new mapping for this file, and
439 // evaluate the DWARF info to find the file/line/name for this address.
440 let (cx, stash) = match cache.mapping_for_lib(lib) {
441 Some((cx, stash)) => (cx, stash),
442 None => return,
443 };
444 let mut any_frames = false;
445 if let Ok(mut frames) = cx.find_frames(stash, addr as u64) {
446 while let Ok(Some(frame)) = frames.next() {
447 any_frames = true;
448 let name = match frame.function {
449 Some(f) => Some(f.name.slice()),
450 None => cx.object.search_symtab(addr as u64),
451 };
452 call(Symbol::Frame {
453 addr: addr as *mut c_void,
454 location: frame.location,
455 name,
456 });
457 }
458 }
459 if !any_frames {
460 if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
461 if let Ok(mut frames) = object_cx.find_frames(stash, object_addr) {
462 while let Ok(Some(frame)) = frames.next() {
463 any_frames = true;
464 call(Symbol::Frame {
465 addr: addr as *mut c_void,
466 location: frame.location,
467 name: frame.function.map(|f| f.name.slice()),
468 });
469 }
470 }
471 }
472 }
473 if !any_frames {
474 if let Some(name) = cx.object.search_symtab(addr as u64) {
475 call(Symbol::Symtab {
476 addr: addr as *mut c_void,
477 name,
478 });
479 }
480 }
481 });
482}
483
484pub enum Symbol<'a> {
485 /// We were able to locate frame information for this symbol, and
486 /// `addr2line`'s frame internally has all the nitty gritty details.
487 Frame {
488 addr: *mut c_void,
489 location: Option<addr2line::Location<'a>>,
490 name: Option<&'a [u8]>,
491 },
492 /// Couldn't find debug information, but we found it in the symbol table of
493 /// the elf executable.
494 Symtab { addr: *mut c_void, name: &'a [u8] },
495}
496
497impl Symbol<'_> {
498 pub fn name(&self) -> Option<SymbolName<'_>> {
499 match self {
500 Symbol::Frame { name, .. } => {
501 let name = name.as_ref()?;
502 Some(SymbolName::new(name))
503 }
504 Symbol::Symtab { name, .. } => Some(SymbolName::new(name)),
505 }
506 }
507
508 pub fn addr(&self) -> Option<*mut c_void> {
509 match self {
510 Symbol::Frame { addr, .. } => Some(*addr),
511 Symbol::Symtab { .. } => None,
512 }
513 }
514
515 pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
516 match self {
517 Symbol::Frame { location, .. } => {
518 let file = location.as_ref()?.file?;
519 Some(BytesOrWideString::Bytes(file.as_bytes()))
520 }
521 Symbol::Symtab { .. } => None,
522 }
523 }
524
525 pub fn filename(&self) -> Option<&Path> {
526 match self {
527 Symbol::Frame { location, .. } => {
528 let file = location.as_ref()?.file?;
529 Some(Path::new(file))
530 }
531 Symbol::Symtab { .. } => None,
532 }
533 }
534
535 pub fn lineno(&self) -> Option<u32> {
536 match self {
537 Symbol::Frame { location, .. } => location.as_ref()?.line,
538 Symbol::Symtab { .. } => None,
539 }
540 }
541
542 pub fn colno(&self) -> Option<u32> {
543 match self {
544 Symbol::Frame { location, .. } => location.as_ref()?.column,
545 Symbol::Symtab { .. } => None,
546 }
547 }
548}
549