summaryrefslogtreecommitdiffstats
path: root/library/backtrace/src/symbolize
diff options
context:
space:
mode:
Diffstat (limited to 'library/backtrace/src/symbolize')
-rw-r--r--library/backtrace/src/symbolize/dbghelp.rs218
-rw-r--r--library/backtrace/src/symbolize/gimli.rs462
-rw-r--r--library/backtrace/src/symbolize/gimli/coff.rs108
-rw-r--r--library/backtrace/src/symbolize/gimli/elf.rs423
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs53
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_haiku.rs48
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_illumos.rs99
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_libnx.rs27
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_macos.rs146
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_windows.rs89
-rw-r--r--library/backtrace/src/symbolize/gimli/macho.rs324
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_fake.rs25
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_unix.rs44
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_windows.rs57
-rw-r--r--library/backtrace/src/symbolize/gimli/stash.rs52
-rw-r--r--library/backtrace/src/symbolize/miri.rs56
-rw-r--r--library/backtrace/src/symbolize/mod.rs485
-rw-r--r--library/backtrace/src/symbolize/noop.rs41
18 files changed, 2757 insertions, 0 deletions
diff --git a/library/backtrace/src/symbolize/dbghelp.rs b/library/backtrace/src/symbolize/dbghelp.rs
new file mode 100644
index 000000000..181dba731
--- /dev/null
+++ b/library/backtrace/src/symbolize/dbghelp.rs
@@ -0,0 +1,218 @@
+//! Symbolication strategy using `dbghelp.dll` on Windows, only used for MSVC
+//!
+//! This symbolication strategy, like with backtraces, uses dynamically loaded
+//! information from `dbghelp.dll`. (see `src/dbghelp.rs` for info about why
+//! it's dynamically loaded).
+//!
+//! This API selects its resolution strategy based on the frame provided or the
+//! information we have at hand. If a frame from `StackWalkEx` is given to us
+//! then we use similar APIs to generate correct information about inlined
+//! functions. Otherwise if all we have is an address or an older stack frame
+//! from `StackWalk64` we use the older APIs for symbolication.
+//!
+//! There's a good deal of support in this module, but a good chunk of it is
+//! converting back and forth between Windows types and Rust types. For example
+//! symbols come to us as wide strings which we then convert to utf-8 strings if
+//! we can.
+
+#![allow(bad_style)]
+
+use super::super::{backtrace::StackFrame, dbghelp, windows::*};
+use super::{BytesOrWideString, ResolveWhat, SymbolName};
+use core::char;
+use core::ffi::c_void;
+use core::marker;
+use core::mem;
+use core::slice;
+
+// Store an OsString on std so we can provide the symbol name and filename.
+pub struct Symbol<'a> {
+ name: *const [u8],
+ addr: *mut c_void,
+ line: Option<u32>,
+ filename: Option<*const [u16]>,
+ #[cfg(feature = "std")]
+ _filename_cache: Option<::std::ffi::OsString>,
+ #[cfg(not(feature = "std"))]
+ _filename_cache: (),
+ _marker: marker::PhantomData<&'a i32>,
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ Some(SymbolName::new(unsafe { &*self.name }))
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ Some(self.addr as *mut _)
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ self.filename
+ .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ None
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ self.line
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&::std::path::Path> {
+ use std::path::Path;
+
+ self._filename_cache.as_ref().map(Path::new)
+ }
+}
+
+#[repr(C, align(8))]
+struct Aligned8<T>(T);
+
+pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
+ // Ensure this process's symbols are initialized
+ let dbghelp = match dbghelp::init() {
+ Ok(dbghelp) => dbghelp,
+ Err(()) => return, // oh well...
+ };
+
+ match what {
+ ResolveWhat::Address(_) => resolve_without_inline(&dbghelp, what.address_or_ip(), cb),
+ ResolveWhat::Frame(frame) => match &frame.inner.stack_frame {
+ StackFrame::New(frame) => resolve_with_inline(&dbghelp, frame, cb),
+ StackFrame::Old(_) => resolve_without_inline(&dbghelp, frame.ip(), cb),
+ },
+ }
+}
+
+unsafe fn resolve_with_inline(
+ dbghelp: &dbghelp::Init,
+ frame: &STACKFRAME_EX,
+ cb: &mut dyn FnMut(&super::Symbol),
+) {
+ do_resolve(
+ |info| {
+ dbghelp.SymFromInlineContextW()(
+ GetCurrentProcess(),
+ super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
+ frame.InlineFrameContext,
+ &mut 0,
+ info,
+ )
+ },
+ |line| {
+ dbghelp.SymGetLineFromInlineContextW()(
+ GetCurrentProcess(),
+ super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64,
+ frame.InlineFrameContext,
+ 0,
+ &mut 0,
+ line,
+ )
+ },
+ cb,
+ )
+}
+
+unsafe fn resolve_without_inline(
+ dbghelp: &dbghelp::Init,
+ addr: *mut c_void,
+ cb: &mut dyn FnMut(&super::Symbol),
+) {
+ do_resolve(
+ |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr as DWORD64, &mut 0, info),
+ |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr as DWORD64, &mut 0, line),
+ cb,
+ )
+}
+
+unsafe fn do_resolve(
+ sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL,
+ get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL,
+ cb: &mut dyn FnMut(&super::Symbol),
+) {
+ const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::<SYMBOL_INFOW>();
+ let mut data = Aligned8([0u8; SIZE]);
+ let data = &mut data.0;
+ let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW);
+ info.MaxNameLen = MAX_SYM_NAME as ULONG;
+ // the struct size in C. the value is different to
+ // `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
+ // due to struct alignment.
+ info.SizeOfStruct = 88;
+
+ if sym_from_addr(info) != TRUE {
+ return;
+ }
+
+ // If the symbol name is greater than MaxNameLen, SymFromAddrW will
+ // give a buffer of (MaxNameLen - 1) characters and set NameLen to
+ // the real value.
+ let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1);
+ let name_ptr = info.Name.as_ptr() as *const u16;
+ let name = slice::from_raw_parts(name_ptr, name_len);
+
+ // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
+ // all other platforms
+ let mut name_len = 0;
+ let mut name_buffer = [0; 256];
+ {
+ let mut remaining = &mut name_buffer[..];
+ for c in char::decode_utf16(name.iter().cloned()) {
+ let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
+ let len = c.len_utf8();
+ if len < remaining.len() {
+ c.encode_utf8(remaining);
+ let tmp = remaining;
+ remaining = &mut tmp[len..];
+ name_len += len;
+ } else {
+ break;
+ }
+ }
+ }
+ let name = &name_buffer[..name_len] as *const [u8];
+
+ let mut line = mem::zeroed::<IMAGEHLP_LINEW64>();
+ line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as DWORD;
+
+ let mut filename = None;
+ let mut lineno = None;
+ if get_line_from_addr(&mut line) == TRUE {
+ lineno = Some(line.LineNumber as u32);
+
+ let base = line.FileName;
+ let mut len = 0;
+ while *base.offset(len) != 0 {
+ len += 1;
+ }
+
+ let len = len as usize;
+
+ filename = Some(slice::from_raw_parts(base, len) as *const [u16]);
+ }
+
+ cb(&super::Symbol {
+ inner: Symbol {
+ name,
+ addr: info.Address as *mut _,
+ line: lineno,
+ filename,
+ _filename_cache: cache(filename),
+ _marker: marker::PhantomData,
+ },
+ })
+}
+
+#[cfg(feature = "std")]
+unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> {
+ use std::os::windows::ffi::OsStringExt;
+ filename.map(|f| ::std::ffi::OsString::from_wide(&*f))
+}
+
+#[cfg(not(feature = "std"))]
+unsafe fn cache(_filename: Option<*const [u16]>) {}
+
+pub unsafe fn clear_symbol_cache() {}
diff --git a/library/backtrace/src/symbolize/gimli.rs b/library/backtrace/src/symbolize/gimli.rs
new file mode 100644
index 000000000..5f10122dd
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli.rs
@@ -0,0 +1,462 @@
+//! Support for symbolication using the `gimli` crate on crates.io
+//!
+//! This is the default symbolication implementation for Rust.
+
+use self::gimli::read::EndianSlice;
+use self::gimli::NativeEndian as Endian;
+use self::mmap::Mmap;
+use self::stash::Stash;
+use super::BytesOrWideString;
+use super::ResolveWhat;
+use super::SymbolName;
+use addr2line::gimli;
+use core::convert::TryInto;
+use core::mem;
+use core::u32;
+use libc::c_void;
+use mystd::ffi::OsString;
+use mystd::fs::File;
+use mystd::path::Path;
+use mystd::prelude::v1::*;
+
+#[cfg(backtrace_in_libstd)]
+mod mystd {
+ pub use crate::*;
+}
+#[cfg(not(backtrace_in_libstd))]
+extern crate std as mystd;
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ #[path = "gimli/mmap_windows.rs"]
+ mod mmap;
+ } else if #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "openbsd",
+ target_os = "solaris",
+ target_os = "illumos",
+ ))] {
+ #[path = "gimli/mmap_unix.rs"]
+ mod mmap;
+ } else {
+ #[path = "gimli/mmap_fake.rs"]
+ mod mmap;
+ }
+}
+
+mod stash;
+
+const MAPPINGS_CACHE_SIZE: usize = 4;
+
+struct Mapping {
+ // 'static lifetime is a lie to hack around lack of support for self-referential structs.
+ cx: Context<'static>,
+ _map: Mmap,
+ _stash: Stash,
+}
+
+enum Either<A, B> {
+ #[allow(dead_code)]
+ A(A),
+ B(B),
+}
+
+impl Mapping {
+ /// Creates a `Mapping` by ensuring that the `data` specified is used to
+ /// create a `Context` and it can only borrow from that or the `Stash` of
+ /// decompressed sections or auxiliary data.
+ fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
+ where
+ F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Context<'a>>,
+ {
+ Mapping::mk_or_other(data, move |data, stash| {
+ let cx = mk(data, stash)?;
+ Some(Either::B(cx))
+ })
+ }
+
+ /// Creates a `Mapping` from `data`, or if the closure decides to, returns a
+ /// different mapping.
+ fn mk_or_other<F>(data: Mmap, mk: F) -> Option<Mapping>
+ where
+ F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Either<Mapping, Context<'a>>>,
+ {
+ let stash = Stash::new();
+ let cx = match mk(&data, &stash)? {
+ Either::A(mapping) => return Some(mapping),
+ Either::B(cx) => cx,
+ };
+ Some(Mapping {
+ // Convert to 'static lifetimes since the symbols should
+ // only borrow `map` and `stash` and we're preserving them below.
+ cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
+ _map: data,
+ _stash: stash,
+ })
+ }
+}
+
+struct Context<'a> {
+ dwarf: addr2line::Context<EndianSlice<'a, Endian>>,
+ object: Object<'a>,
+}
+
+impl<'data> Context<'data> {
+ fn new(
+ stash: &'data Stash,
+ object: Object<'data>,
+ sup: Option<Object<'data>>,
+ ) -> Option<Context<'data>> {
+ let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
+ let data = object.section(stash, id.name()).unwrap_or(&[]);
+ Ok(EndianSlice::new(data, Endian))
+ })
+ .ok()?;
+
+ if let Some(sup) = sup {
+ sections
+ .load_sup(|id| -> Result<_, ()> {
+ let data = sup.section(stash, id.name()).unwrap_or(&[]);
+ Ok(EndianSlice::new(data, Endian))
+ })
+ .ok()?;
+ }
+ let dwarf = addr2line::Context::from_dwarf(sections).ok()?;
+
+ Some(Context { dwarf, object })
+ }
+}
+
+fn mmap(path: &Path) -> Option<Mmap> {
+ let file = File::open(path).ok()?;
+ let len = file.metadata().ok()?.len().try_into().ok()?;
+ unsafe { Mmap::map(&file, len) }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ mod coff;
+ use self::coff::Object;
+ } else if #[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "tvos",
+ target_os = "watchos",
+ ))] {
+ mod macho;
+ use self::macho::Object;
+ } else {
+ mod elf;
+ use self::elf::Object;
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ mod libs_windows;
+ use libs_windows::native_libraries;
+ } else if #[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "tvos",
+ target_os = "watchos",
+ ))] {
+ mod libs_macos;
+ use libs_macos::native_libraries;
+ } else if #[cfg(target_os = "illumos")] {
+ mod libs_illumos;
+ use libs_illumos::native_libraries;
+ } else if #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "fuchsia",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ all(target_os = "android", feature = "dl_iterate_phdr"),
+ ),
+ not(target_env = "uclibc"),
+ ))] {
+ mod libs_dl_iterate_phdr;
+ use libs_dl_iterate_phdr::native_libraries;
+ } else if #[cfg(target_env = "libnx")] {
+ mod libs_libnx;
+ use libs_libnx::native_libraries;
+ } else if #[cfg(target_os = "haiku")] {
+ mod libs_haiku;
+ use libs_haiku::native_libraries;
+ } else {
+ // Everything else should doesn't know how to load native libraries.
+ fn native_libraries() -> Vec<Library> {
+ Vec::new()
+ }
+ }
+}
+
+#[derive(Default)]
+struct Cache {
+ /// All known shared libraries that have been loaded.
+ libraries: Vec<Library>,
+
+ /// Mappings cache where we retain parsed dwarf information.
+ ///
+ /// This list has a fixed capacity for its entire lifetime which never
+ /// increases. The `usize` element of each pair is an index into `libraries`
+ /// above where `usize::max_value()` represents the current executable. The
+ /// `Mapping` is corresponding parsed dwarf information.
+ ///
+ /// Note that this is basically an LRU cache and we'll be shifting things
+ /// around in here as we symbolize addresses.
+ mappings: Vec<(usize, Mapping)>,
+}
+
+struct Library {
+ name: OsString,
+ /// Segments of this library loaded into memory, and where they're loaded.
+ segments: Vec<LibrarySegment>,
+ /// The "bias" of this library, typically where it's loaded into memory.
+ /// This value is added to each segment's stated address to get the actual
+ /// virtual memory address that the segment is loaded into. Additionally
+ /// this bias is subtracted from real virtual memory addresses to index into
+ /// debuginfo and the symbol table.
+ bias: usize,
+}
+
+struct LibrarySegment {
+ /// The stated address of this segment in the object file. This is not
+ /// actually where the segment is loaded, but rather this address plus the
+ /// containing library's `bias` is where to find it.
+ stated_virtual_memory_address: usize,
+ /// The size of this segment in memory.
+ len: usize,
+}
+
+// unsafe because this is required to be externally synchronized
+pub unsafe fn clear_symbol_cache() {
+ Cache::with_global(|cache| cache.mappings.clear());
+}
+
+impl Cache {
+ fn new() -> Cache {
+ Cache {
+ mappings: Vec::with_capacity(MAPPINGS_CACHE_SIZE),
+ libraries: native_libraries(),
+ }
+ }
+
+ // unsafe because this is required to be externally synchronized
+ unsafe fn with_global(f: impl FnOnce(&mut Self)) {
+ // A very small, very simple LRU cache for debug info mappings.
+ //
+ // The hit rate should be very high, since the typical stack doesn't cross
+ // between many shared libraries.
+ //
+ // The `addr2line::Context` structures are pretty expensive to create. Its
+ // cost is expected to be amortized by subsequent `locate` queries, which
+ // leverage the structures built when constructing `addr2line::Context`s to
+ // get nice speedups. If we didn't have this cache, that amortization would
+ // never happen, and symbolicating backtraces would be ssssllllooooowwww.
+ static mut MAPPINGS_CACHE: Option<Cache> = None;
+
+ f(MAPPINGS_CACHE.get_or_insert_with(|| Cache::new()))
+ }
+
+ fn avma_to_svma(&self, addr: *const u8) -> Option<(usize, *const u8)> {
+ self.libraries
+ .iter()
+ .enumerate()
+ .filter_map(|(i, lib)| {
+ // First up, test if this `lib` has any segment containing the
+ // `addr` (handling relocation). If this check passes then we
+ // can continue below and actually translate the address.
+ //
+ // Note that we're using `wrapping_add` here to avoid overflow
+ // checks. It's been seen in the wild that the SVMA + bias
+ // computation overflows. It seems a bit odd that would happen
+ // but there's not a huge amount we can do about it other than
+ // probably just ignore those segments since they're likely
+ // pointing off into space. This originally came up in
+ // rust-lang/backtrace-rs#329.
+ if !lib.segments.iter().any(|s| {
+ let svma = s.stated_virtual_memory_address;
+ let start = svma.wrapping_add(lib.bias);
+ let end = start.wrapping_add(s.len);
+ let address = addr as usize;
+ start <= address && address < end
+ }) {
+ return None;
+ }
+
+ // Now that we know `lib` contains `addr`, we can offset with
+ // the bias to find the stated virtual memory address.
+ let svma = (addr as usize).wrapping_sub(lib.bias);
+ Some((i, svma as *const u8))
+ })
+ .next()
+ }
+
+ fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a mut Context<'a>> {
+ let idx = self.mappings.iter().position(|(idx, _)| *idx == lib);
+
+ // Invariant: after this conditional completes without early returning
+ // from an error, the cache entry for this path is at index 0.
+
+ if let Some(idx) = idx {
+ // When the mapping is already in the cache, move it to the front.
+ if idx != 0 {
+ let entry = self.mappings.remove(idx);
+ self.mappings.insert(0, entry);
+ }
+ } else {
+ // When the mapping is not in the cache, create a new mapping,
+ // insert it into the front of the cache, and evict the oldest cache
+ // entry if necessary.
+ let name = &self.libraries[lib].name;
+ let mapping = Mapping::new(name.as_ref())?;
+
+ if self.mappings.len() == MAPPINGS_CACHE_SIZE {
+ self.mappings.pop();
+ }
+
+ self.mappings.insert(0, (lib, mapping));
+ }
+
+ let cx: &'a mut Context<'static> = &mut self.mappings[0].1.cx;
+ // don't leak the `'static` lifetime, make sure it's scoped to just
+ // ourselves
+ Some(unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) })
+ }
+}
+
+pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
+ let addr = what.address_or_ip();
+ let mut call = |sym: Symbol<'_>| {
+ // Extend the lifetime of `sym` to `'static` since we are unfortunately
+ // required to here, but it's only ever going out as a reference so no
+ // reference to it should be persisted beyond this frame anyway.
+ let sym = mem::transmute::<Symbol<'_>, Symbol<'static>>(sym);
+ (cb)(&super::Symbol { inner: sym });
+ };
+
+ Cache::with_global(|cache| {
+ let (lib, addr) = match cache.avma_to_svma(addr as *const u8) {
+ Some(pair) => pair,
+ None => return,
+ };
+
+ // Finally, get a cached mapping or create a new mapping for this file, and
+ // evaluate the DWARF info to find the file/line/name for this address.
+ let cx = match cache.mapping_for_lib(lib) {
+ Some(cx) => cx,
+ None => return,
+ };
+ let mut any_frames = false;
+ if let Ok(mut frames) = cx.dwarf.find_frames(addr as u64) {
+ while let Ok(Some(frame)) = frames.next() {
+ any_frames = true;
+ let name = match frame.function {
+ Some(f) => Some(f.name.slice()),
+ None => cx.object.search_symtab(addr as u64),
+ };
+ call(Symbol::Frame {
+ addr: addr as *mut c_void,
+ location: frame.location,
+ name,
+ });
+ }
+ }
+ if !any_frames {
+ if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
+ if let Ok(mut frames) = object_cx.dwarf.find_frames(object_addr) {
+ while let Ok(Some(frame)) = frames.next() {
+ any_frames = true;
+ call(Symbol::Frame {
+ addr: addr as *mut c_void,
+ location: frame.location,
+ name: frame.function.map(|f| f.name.slice()),
+ });
+ }
+ }
+ }
+ }
+ if !any_frames {
+ if let Some(name) = cx.object.search_symtab(addr as u64) {
+ call(Symbol::Symtab {
+ addr: addr as *mut c_void,
+ name,
+ });
+ }
+ }
+ });
+}
+
+pub enum Symbol<'a> {
+ /// We were able to locate frame information for this symbol, and
+ /// `addr2line`'s frame internally has all the nitty gritty details.
+ Frame {
+ addr: *mut c_void,
+ location: Option<addr2line::Location<'a>>,
+ name: Option<&'a [u8]>,
+ },
+ /// Couldn't find debug information, but we found it in the symbol table of
+ /// the elf executable.
+ Symtab { addr: *mut c_void, name: &'a [u8] },
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ match self {
+ Symbol::Frame { name, .. } => {
+ let name = name.as_ref()?;
+ Some(SymbolName::new(name))
+ }
+ Symbol::Symtab { name, .. } => Some(SymbolName::new(name)),
+ }
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ match self {
+ Symbol::Frame { addr, .. } => Some(*addr),
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ match self {
+ Symbol::Frame { location, .. } => {
+ let file = location.as_ref()?.file?;
+ Some(BytesOrWideString::Bytes(file.as_bytes()))
+ }
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn filename(&self) -> Option<&Path> {
+ match self {
+ Symbol::Frame { location, .. } => {
+ let file = location.as_ref()?.file?;
+ Some(Path::new(file))
+ }
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ match self {
+ Symbol::Frame { location, .. } => location.as_ref()?.line,
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ match self {
+ Symbol::Frame { location, .. } => location.as_ref()?.column,
+ Symbol::Symtab { .. } => None,
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/coff.rs b/library/backtrace/src/symbolize/gimli/coff.rs
new file mode 100644
index 000000000..84d334207
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/coff.rs
@@ -0,0 +1,108 @@
+use super::{Context, Mapping, Path, Stash, Vec};
+use core::convert::TryFrom;
+use object::pe::{ImageDosHeader, ImageSymbol};
+use object::read::pe::{ImageNtHeaders, ImageOptionalHeader, SectionTable};
+use object::read::StringTable;
+use object::LittleEndian as LE;
+
+#[cfg(target_pointer_width = "32")]
+type Pe = object::pe::ImageNtHeaders32;
+#[cfg(target_pointer_width = "64")]
+type Pe = object::pe::ImageNtHeaders64;
+
+impl Mapping {
+ pub fn new(path: &Path) -> Option<Mapping> {
+ let map = super::mmap(path)?;
+ Mapping::mk(map, |data, stash| {
+ Context::new(stash, Object::parse(data)?, None)
+ })
+ }
+}
+
+pub struct Object<'a> {
+ data: &'a [u8],
+ sections: SectionTable<'a>,
+ symbols: Vec<(usize, &'a ImageSymbol)>,
+ strings: StringTable<'a>,
+}
+
+pub fn get_image_base(data: &[u8]) -> Option<usize> {
+ let dos_header = ImageDosHeader::parse(data).ok()?;
+ let mut offset = dos_header.nt_headers_offset().into();
+ let (nt_headers, _) = Pe::parse(data, &mut offset).ok()?;
+ usize::try_from(nt_headers.optional_header().image_base()).ok()
+}
+
+impl<'a> Object<'a> {
+ fn parse(data: &'a [u8]) -> Option<Object<'a>> {
+ let dos_header = ImageDosHeader::parse(data).ok()?;
+ let mut offset = dos_header.nt_headers_offset().into();
+ let (nt_headers, _) = Pe::parse(data, &mut offset).ok()?;
+ let sections = nt_headers.sections(data, offset).ok()?;
+ let symtab = nt_headers.symbols(data).ok()?;
+ let strings = symtab.strings();
+ let image_base = usize::try_from(nt_headers.optional_header().image_base()).ok()?;
+
+ // Collect all the symbols into a local vector which is sorted
+ // by address and contains enough data to learn about the symbol
+ // name. Note that we only look at function symbols and also
+ // note that the sections are 1-indexed because the zero section
+ // is special (apparently).
+ let mut symbols = Vec::new();
+ let mut i = 0;
+ let len = symtab.len();
+ while i < len {
+ let sym = symtab.symbol(i).ok()?;
+ i += 1 + sym.number_of_aux_symbols as usize;
+ let section_number = sym.section_number.get(LE);
+ if sym.derived_type() != object::pe::IMAGE_SYM_DTYPE_FUNCTION || section_number == 0 {
+ continue;
+ }
+ let addr = usize::try_from(sym.value.get(LE)).ok()?;
+ let section = sections
+ .section(usize::try_from(section_number).ok()?)
+ .ok()?;
+ let va = usize::try_from(section.virtual_address.get(LE)).ok()?;
+ symbols.push((addr + va + image_base, sym));
+ }
+ symbols.sort_unstable_by_key(|x| x.0);
+ Some(Object {
+ data,
+ sections,
+ strings,
+ symbols,
+ })
+ }
+
+ pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
+ Some(
+ self.sections
+ .section_by_name(self.strings, name.as_bytes())?
+ .1
+ .pe_data(self.data)
+ .ok()?,
+ )
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ // Note that unlike other formats COFF doesn't embed the size of
+ // each symbol. As a last ditch effort search for the *closest*
+ // symbol to a particular address and return that one. This gets
+ // really wonky once symbols start getting removed because the
+ // symbols returned here can be totally incorrect, but we have
+ // no idea of knowing how to detect that.
+ let addr = usize::try_from(addr).ok()?;
+ let i = match self.symbols.binary_search_by_key(&addr, |p| p.0) {
+ Ok(i) => i,
+ // typically `addr` isn't in the array, but `i` is where
+ // we'd insert it, so the previous position must be the
+ // greatest less than `addr`
+ Err(i) => i.checked_sub(1)?,
+ };
+ self.symbols[i].1.name(self.strings).ok()
+ }
+
+ pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
+ None
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/elf.rs b/library/backtrace/src/symbolize/gimli/elf.rs
new file mode 100644
index 000000000..bc71ee2c9
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/elf.rs
@@ -0,0 +1,423 @@
+use super::mystd::ffi::{OsStr, OsString};
+use super::mystd::fs;
+use super::mystd::os::unix::ffi::{OsStrExt, OsStringExt};
+use super::mystd::path::{Path, PathBuf};
+use super::Either;
+use super::{Context, Mapping, Stash, Vec};
+use core::convert::{TryFrom, TryInto};
+use core::str;
+use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED};
+use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym};
+use object::read::StringTable;
+use object::{BigEndian, Bytes, NativeEndian};
+
+#[cfg(target_pointer_width = "32")]
+type Elf = object::elf::FileHeader32<NativeEndian>;
+#[cfg(target_pointer_width = "64")]
+type Elf = object::elf::FileHeader64<NativeEndian>;
+
+impl Mapping {
+ pub fn new(path: &Path) -> Option<Mapping> {
+ let map = super::mmap(path)?;
+ Mapping::mk_or_other(map, |map, stash| {
+ let object = Object::parse(&map)?;
+
+ // Try to locate an external debug file using the build ID.
+ if let Some(path_debug) = object.build_id().and_then(locate_build_id) {
+ if let Some(mapping) = Mapping::new_debug(path_debug, None) {
+ return Some(Either::A(mapping));
+ }
+ }
+
+ // Try to locate an external debug file using the GNU debug link section.
+ if let Some((path_debug, crc)) = object.gnu_debuglink_path(path) {
+ if let Some(mapping) = Mapping::new_debug(path_debug, Some(crc)) {
+ return Some(Either::A(mapping));
+ }
+ }
+
+ Context::new(stash, object, None).map(Either::B)
+ })
+ }
+
+ /// Load debuginfo from an external debug file.
+ fn new_debug(path: PathBuf, crc: Option<u32>) -> Option<Mapping> {
+ let map = super::mmap(&path)?;
+ Mapping::mk(map, |map, stash| {
+ let object = Object::parse(&map)?;
+
+ if let Some(_crc) = crc {
+ // TODO: check crc
+ }
+
+ // Try to locate a supplementary object file.
+ if let Some((path_sup, build_id_sup)) = object.gnu_debugaltlink_path(&path) {
+ if let Some(map_sup) = super::mmap(&path_sup) {
+ let map_sup = stash.set_mmap_aux(map_sup);
+ if let Some(sup) = Object::parse(map_sup) {
+ if sup.build_id() == Some(build_id_sup) {
+ return Context::new(stash, object, Some(sup));
+ }
+ }
+ }
+ }
+
+ Context::new(stash, object, None)
+ })
+ }
+}
+
+struct ParsedSym {
+ address: u64,
+ size: u64,
+ name: u32,
+}
+
+pub struct Object<'a> {
+ /// Zero-sized type representing the native endianness.
+ ///
+ /// We could use a literal instead, but this helps ensure correctness.
+ endian: NativeEndian,
+ /// The entire file data.
+ data: &'a [u8],
+ sections: SectionTable<'a, Elf>,
+ strings: StringTable<'a>,
+ /// List of pre-parsed and sorted symbols by base address.
+ syms: Vec<ParsedSym>,
+}
+
+impl<'a> Object<'a> {
+ fn parse(data: &'a [u8]) -> Option<Object<'a>> {
+ let elf = Elf::parse(data).ok()?;
+ let endian = elf.endian().ok()?;
+ let sections = elf.sections(endian, data).ok()?;
+ let mut syms = sections
+ .symbols(endian, data, object::elf::SHT_SYMTAB)
+ .ok()?;
+ if syms.is_empty() {
+ syms = sections
+ .symbols(endian, data, object::elf::SHT_DYNSYM)
+ .ok()?;
+ }
+ let strings = syms.strings();
+
+ let mut syms = syms
+ .iter()
+ // Only look at function/object symbols. This mirrors what
+ // libbacktrace does and in general we're only symbolicating
+ // function addresses in theory. Object symbols correspond
+ // to data, and maybe someone's crazy enough to have a
+ // function go into static data?
+ .filter(|sym| {
+ let st_type = sym.st_type();
+ st_type == object::elf::STT_FUNC || st_type == object::elf::STT_OBJECT
+ })
+ // skip anything that's in an undefined section header,
+ // since it means it's an imported function and we're only
+ // symbolicating with locally defined functions.
+ .filter(|sym| sym.st_shndx(endian) != object::elf::SHN_UNDEF)
+ .map(|sym| {
+ let address = sym.st_value(endian).into();
+ let size = sym.st_size(endian).into();
+ let name = sym.st_name(endian);
+ ParsedSym {
+ address,
+ size,
+ name,
+ }
+ })
+ .collect::<Vec<_>>();
+ syms.sort_unstable_by_key(|s| s.address);
+ Some(Object {
+ endian,
+ data,
+ sections,
+ strings,
+ syms,
+ })
+ }
+
+ pub fn section(&self, stash: &'a Stash, name: &str) -> Option<&'a [u8]> {
+ if let Some(section) = self.section_header(name) {
+ let mut data = Bytes(section.data(self.endian, self.data).ok()?);
+
+ // Check for DWARF-standard (gABI) compression, i.e., as generated
+ // by ld's `--compress-debug-sections=zlib-gabi` flag.
+ let flags: u64 = section.sh_flags(self.endian).into();
+ if (flags & u64::from(SHF_COMPRESSED)) == 0 {
+ // Not compressed.
+ return Some(data.0);
+ }
+
+ let header = data.read::<<Elf as FileHeader>::CompressionHeader>().ok()?;
+ if header.ch_type(self.endian) != ELFCOMPRESS_ZLIB {
+ // Zlib compression is the only known type.
+ return None;
+ }
+ let size = usize::try_from(header.ch_size(self.endian)).ok()?;
+ let buf = stash.allocate(size);
+ decompress_zlib(data.0, buf)?;
+ return Some(buf);
+ }
+
+ // Check for the nonstandard GNU compression format, i.e., as generated
+ // by ld's `--compress-debug-sections=zlib-gnu` flag. This means that if
+ // we're actually asking for `.debug_info` then we need to look up a
+ // section named `.zdebug_info`.
+ if !name.starts_with(".debug_") {
+ return None;
+ }
+ let debug_name = name[7..].as_bytes();
+ let compressed_section = self
+ .sections
+ .iter()
+ .filter_map(|header| {
+ let name = self.sections.section_name(self.endian, header).ok()?;
+ if name.starts_with(b".zdebug_") && &name[8..] == debug_name {
+ Some(header)
+ } else {
+ None
+ }
+ })
+ .next()?;
+ let mut data = Bytes(compressed_section.data(self.endian, self.data).ok()?);
+ if data.read_bytes(8).ok()?.0 != b"ZLIB\0\0\0\0" {
+ return None;
+ }
+ let size = usize::try_from(data.read::<object::U32Bytes<_>>().ok()?.get(BigEndian)).ok()?;
+ let buf = stash.allocate(size);
+ decompress_zlib(data.0, buf)?;
+ Some(buf)
+ }
+
+ fn section_header(&self, name: &str) -> Option<&<Elf as FileHeader>::SectionHeader> {
+ self.sections
+ .section_by_name(self.endian, name.as_bytes())
+ .map(|(_index, section)| section)
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ // Same sort of binary search as Windows above
+ let i = match self.syms.binary_search_by_key(&addr, |sym| sym.address) {
+ Ok(i) => i,
+ Err(i) => i.checked_sub(1)?,
+ };
+ let sym = self.syms.get(i)?;
+ if sym.address <= addr && addr <= sym.address + sym.size {
+ self.strings.get(sym.name).ok()
+ } else {
+ None
+ }
+ }
+
+ pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
+ None
+ }
+
+ fn build_id(&self) -> Option<&'a [u8]> {
+ for section in self.sections.iter() {
+ if let Ok(Some(mut notes)) = section.notes(self.endian, self.data) {
+ while let Ok(Some(note)) = notes.next() {
+ if note.name() == ELF_NOTE_GNU && note.n_type(self.endian) == NT_GNU_BUILD_ID {
+ return Some(note.desc());
+ }
+ }
+ }
+ }
+ None
+ }
+
+ // The contents of the ".gnu_debuglink" section is documented at:
+ // https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+ fn gnu_debuglink_path(&self, path: &Path) -> Option<(PathBuf, u32)> {
+ let section = self.section_header(".gnu_debuglink")?;
+ let data = section.data(self.endian, self.data).ok()?;
+ let len = data.iter().position(|x| *x == 0)?;
+ let filename = &data[..len];
+ let offset = (len + 1 + 3) & !3;
+ let crc_bytes = data
+ .get(offset..offset + 4)
+ .and_then(|bytes| bytes.try_into().ok())?;
+ let crc = u32::from_ne_bytes(crc_bytes);
+ let path_debug = locate_debuglink(path, filename)?;
+ Some((path_debug, crc))
+ }
+
+ // The format of the ".gnu_debugaltlink" section is based on gdb.
+ fn gnu_debugaltlink_path(&self, path: &Path) -> Option<(PathBuf, &'a [u8])> {
+ let section = self.section_header(".gnu_debugaltlink")?;
+ let data = section.data(self.endian, self.data).ok()?;
+ let len = data.iter().position(|x| *x == 0)?;
+ let filename = &data[..len];
+ let build_id = &data[len + 1..];
+ let path_sup = locate_debugaltlink(path, filename, build_id)?;
+ Some((path_sup, build_id))
+ }
+}
+
+fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {
+ use miniz_oxide::inflate::core::inflate_flags::{
+ TINFL_FLAG_PARSE_ZLIB_HEADER, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF,
+ };
+ use miniz_oxide::inflate::core::{decompress, DecompressorOxide};
+ use miniz_oxide::inflate::TINFLStatus;
+
+ let (status, in_read, out_read) = decompress(
+ &mut DecompressorOxide::new(),
+ input,
+ output,
+ 0,
+ TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | TINFL_FLAG_PARSE_ZLIB_HEADER,
+ );
+ if status == TINFLStatus::Done && in_read == input.len() && out_read == output.len() {
+ Some(())
+ } else {
+ None
+ }
+}
+
+const DEBUG_PATH: &[u8] = b"/usr/lib/debug";
+
+fn debug_path_exists() -> bool {
+ cfg_if::cfg_if! {
+ if #[cfg(any(target_os = "freebsd", target_os = "linux"))] {
+ use core::sync::atomic::{AtomicU8, Ordering};
+ static DEBUG_PATH_EXISTS: AtomicU8 = AtomicU8::new(0);
+
+ let mut exists = DEBUG_PATH_EXISTS.load(Ordering::Relaxed);
+ if exists == 0 {
+ exists = if Path::new(OsStr::from_bytes(DEBUG_PATH)).is_dir() {
+ 1
+ } else {
+ 2
+ };
+ DEBUG_PATH_EXISTS.store(exists, Ordering::Relaxed);
+ }
+ exists == 1
+ } else {
+ false
+ }
+ }
+}
+
+/// Locate a debug file based on its build ID.
+///
+/// The format of build id paths is documented at:
+/// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+fn locate_build_id(build_id: &[u8]) -> Option<PathBuf> {
+ const BUILD_ID_PATH: &[u8] = b"/usr/lib/debug/.build-id/";
+ const BUILD_ID_SUFFIX: &[u8] = b".debug";
+
+ if build_id.len() < 2 {
+ return None;
+ }
+
+ if !debug_path_exists() {
+ return None;
+ }
+
+ let mut path =
+ Vec::with_capacity(BUILD_ID_PATH.len() + BUILD_ID_SUFFIX.len() + build_id.len() * 2 + 1);
+ path.extend(BUILD_ID_PATH);
+ path.push(hex(build_id[0] >> 4));
+ path.push(hex(build_id[0] & 0xf));
+ path.push(b'/');
+ for byte in &build_id[1..] {
+ path.push(hex(byte >> 4));
+ path.push(hex(byte & 0xf));
+ }
+ path.extend(BUILD_ID_SUFFIX);
+ Some(PathBuf::from(OsString::from_vec(path)))
+}
+
+fn hex(byte: u8) -> u8 {
+ if byte < 10 {
+ b'0' + byte
+ } else {
+ b'a' + byte - 10
+ }
+}
+
+/// Locate a file specified in a `.gnu_debuglink` section.
+///
+/// `path` is the file containing the section.
+/// `filename` is from the contents of the section.
+///
+/// Search order is based on gdb, documented at:
+/// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+///
+/// gdb also allows the user to customize the debug search path, but we don't.
+///
+/// gdb also supports debuginfod, but we don't yet.
+fn locate_debuglink(path: &Path, filename: &[u8]) -> Option<PathBuf> {
+ let path = fs::canonicalize(path).ok()?;
+ let parent = path.parent()?;
+ let mut f = PathBuf::from(OsString::with_capacity(
+ DEBUG_PATH.len() + parent.as_os_str().len() + filename.len() + 2,
+ ));
+ let filename = Path::new(OsStr::from_bytes(filename));
+
+ // Try "/parent/filename" if it differs from "path"
+ f.push(parent);
+ f.push(filename);
+ if f != path && f.is_file() {
+ return Some(f);
+ }
+
+ // Try "/parent/.debug/filename"
+ let mut s = OsString::from(f);
+ s.clear();
+ f = PathBuf::from(s);
+ f.push(parent);
+ f.push(".debug");
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+
+ if debug_path_exists() {
+ // Try "/usr/lib/debug/parent/filename"
+ let mut s = OsString::from(f);
+ s.clear();
+ f = PathBuf::from(s);
+ f.push(OsStr::from_bytes(DEBUG_PATH));
+ f.push(parent.strip_prefix("/").unwrap());
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+ }
+
+ None
+}
+
+/// Locate a file specified in a `.gnu_debugaltlink` section.
+///
+/// `path` is the file containing the section.
+/// `filename` and `build_id` are the contents of the section.
+///
+/// Search order is based on gdb:
+/// - filename, which is either absolute or relative to `path`
+/// - the build ID path under `BUILD_ID_PATH`
+///
+/// gdb also allows the user to customize the debug search path, but we don't.
+///
+/// gdb also supports debuginfod, but we don't yet.
+fn locate_debugaltlink(path: &Path, filename: &[u8], build_id: &[u8]) -> Option<PathBuf> {
+ let filename = Path::new(OsStr::from_bytes(filename));
+ if filename.is_absolute() {
+ if filename.is_file() {
+ return Some(filename.into());
+ }
+ } else {
+ let path = fs::canonicalize(path).ok()?;
+ let parent = path.parent()?;
+ let mut f = PathBuf::from(parent);
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+ }
+
+ locate_build_id(build_id)
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs
new file mode 100644
index 000000000..a011e6080
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs
@@ -0,0 +1,53 @@
+// Other Unix (e.g. Linux) platforms use ELF as an object file format
+// and typically implement an API called `dl_iterate_phdr` to load
+// native libraries.
+
+use super::mystd::borrow::ToOwned;
+use super::mystd::env;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, OsString, Vec};
+use core::slice;
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ unsafe {
+ libc::dl_iterate_phdr(Some(callback), &mut ret as *mut Vec<_> as *mut _);
+ }
+ return ret;
+}
+
+// `info` should be a valid pointers.
+// `vec` should be a valid pointer to a `std::Vec`.
+unsafe extern "C" fn callback(
+ info: *mut libc::dl_phdr_info,
+ _size: libc::size_t,
+ vec: *mut libc::c_void,
+) -> libc::c_int {
+ let info = &*info;
+ let libs = &mut *(vec as *mut Vec<Library>);
+ let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0;
+ let name = if is_main_prog {
+ if libs.is_empty() {
+ env::current_exe().map(|e| e.into()).unwrap_or_default()
+ } else {
+ OsString::new()
+ }
+ } else {
+ let bytes = CStr::from_ptr(info.dlpi_name).to_bytes();
+ OsStr::from_bytes(bytes).to_owned()
+ };
+ let headers = slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize);
+ libs.push(Library {
+ name,
+ segments: headers
+ .iter()
+ .map(|header| LibrarySegment {
+ len: (*header).p_memsz as usize,
+ stated_virtual_memory_address: (*header).p_vaddr as usize,
+ })
+ .collect(),
+ bias: info.dlpi_addr as usize,
+ });
+ 0
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_haiku.rs b/library/backtrace/src/symbolize/gimli/libs_haiku.rs
new file mode 100644
index 000000000..87e023e69
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_haiku.rs
@@ -0,0 +1,48 @@
+// Haiku implements the image_info struct and the get_next_image_info()
+// functions to iterate through the loaded executable images. The
+// image_info struct contains a pointer to the start of the .text
+// section within the virtual address space, as well as the size of
+// that section. All the read-only segments of the ELF-binary are in
+// that part of the address space.
+
+use super::mystd::borrow::ToOwned;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::mem::MaybeUninit;
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, Vec};
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut libraries: Vec<Library> = Vec::new();
+
+ unsafe {
+ let mut info = MaybeUninit::<libc::image_info>::zeroed();
+ let mut cookie: i32 = 0;
+ // Load the first image to get a valid info struct
+ let mut status =
+ libc::get_next_image_info(libc::B_CURRENT_TEAM, &mut cookie, info.as_mut_ptr());
+ if status != libc::B_OK {
+ return libraries;
+ }
+ let mut info = info.assume_init();
+
+ while status == libc::B_OK {
+ let mut segments = Vec::new();
+ segments.push(LibrarySegment {
+ stated_virtual_memory_address: 0,
+ len: info.text_size as usize,
+ });
+
+ let bytes = CStr::from_ptr(info.name.as_ptr()).to_bytes();
+ let name = OsStr::from_bytes(bytes).to_owned();
+ libraries.push(Library {
+ name: name,
+ segments: segments,
+ bias: info.text as usize,
+ });
+
+ status = libc::get_next_image_info(libc::B_CURRENT_TEAM, &mut cookie, &mut info);
+ }
+ }
+
+ libraries
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_illumos.rs b/library/backtrace/src/symbolize/gimli/libs_illumos.rs
new file mode 100644
index 000000000..e64975e0c
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_illumos.rs
@@ -0,0 +1,99 @@
+use super::mystd::borrow::ToOwned;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, Vec};
+use core::mem;
+use object::NativeEndian;
+
+#[cfg(target_pointer_width = "64")]
+use object::elf::{FileHeader64 as FileHeader, ProgramHeader64 as ProgramHeader};
+
+type EHdr = FileHeader<NativeEndian>;
+type PHdr = ProgramHeader<NativeEndian>;
+
+#[repr(C)]
+struct LinkMap {
+ l_addr: libc::c_ulong,
+ l_name: *const libc::c_char,
+ l_ld: *const libc::c_void,
+ l_next: *const LinkMap,
+ l_prev: *const LinkMap,
+ l_refname: *const libc::c_char,
+}
+
+const RTLD_SELF: *const libc::c_void = -3isize as *const libc::c_void;
+const RTLD_DI_LINKMAP: libc::c_int = 2;
+
+extern "C" {
+ fn dlinfo(
+ handle: *const libc::c_void,
+ request: libc::c_int,
+ p: *mut libc::c_void,
+ ) -> libc::c_int;
+}
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut libs = Vec::new();
+
+ // Request the current link map from the runtime linker:
+ let map = unsafe {
+ let mut map: *const LinkMap = mem::zeroed();
+ if dlinfo(
+ RTLD_SELF,
+ RTLD_DI_LINKMAP,
+ (&mut map) as *mut *const LinkMap as *mut libc::c_void,
+ ) != 0
+ {
+ return libs;
+ }
+ map
+ };
+
+ // Each entry in the link map represents a loaded object:
+ let mut l = map;
+ while !l.is_null() {
+ // Fetch the fully qualified path of the loaded object:
+ let bytes = unsafe { CStr::from_ptr((*l).l_name) }.to_bytes();
+ let name = OsStr::from_bytes(bytes).to_owned();
+
+ // The base address of the object loaded into memory:
+ let addr = unsafe { (*l).l_addr };
+
+ // Use the ELF header for this object to locate the program
+ // header:
+ let e: *const EHdr = unsafe { (*l).l_addr as *const EHdr };
+ let phoff = unsafe { (*e).e_phoff }.get(NativeEndian);
+ let phnum = unsafe { (*e).e_phnum }.get(NativeEndian);
+ let etype = unsafe { (*e).e_type }.get(NativeEndian);
+
+ let phdr: *const PHdr = (addr + phoff) as *const PHdr;
+ let phdr = unsafe { core::slice::from_raw_parts(phdr, phnum as usize) };
+
+ libs.push(Library {
+ name,
+ segments: phdr
+ .iter()
+ .map(|p| {
+ let memsz = p.p_memsz.get(NativeEndian);
+ let vaddr = p.p_vaddr.get(NativeEndian);
+ LibrarySegment {
+ len: memsz as usize,
+ stated_virtual_memory_address: vaddr as usize,
+ }
+ })
+ .collect(),
+ bias: if etype == object::elf::ET_EXEC {
+ // Program header addresses for the base executable are
+ // already absolute.
+ 0
+ } else {
+ // Other addresses are relative to the object base.
+ addr as usize
+ },
+ });
+
+ l = unsafe { (*l).l_next };
+ }
+
+ libs
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_libnx.rs b/library/backtrace/src/symbolize/gimli/libs_libnx.rs
new file mode 100644
index 000000000..93b5ba17e
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_libnx.rs
@@ -0,0 +1,27 @@
+use super::{Library, LibrarySegment, Vec};
+
+// DevkitA64 doesn't natively support debug info, but the build system will
+// place debug info at the path `romfs:/debug_info.elf`.
+pub(super) fn native_libraries() -> Vec<Library> {
+ extern "C" {
+ static __start__: u8;
+ }
+
+ let bias = unsafe { &__start__ } as *const u8 as usize;
+
+ let mut ret = Vec::new();
+ let mut segments = Vec::new();
+ segments.push(LibrarySegment {
+ stated_virtual_memory_address: 0,
+ len: usize::max_value() - bias,
+ });
+
+ let path = "romfs:/debug_info.elf";
+ ret.push(Library {
+ name: path.into(),
+ segments,
+ bias,
+ });
+
+ ret
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_macos.rs b/library/backtrace/src/symbolize/gimli/libs_macos.rs
new file mode 100644
index 000000000..17703b88a
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_macos.rs
@@ -0,0 +1,146 @@
+#![allow(deprecated)]
+
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::mystd::prelude::v1::*;
+use super::{Library, LibrarySegment};
+use core::convert::TryInto;
+use core::mem;
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ let images = unsafe { libc::_dyld_image_count() };
+ for i in 0..images {
+ ret.extend(native_library(i));
+ }
+ return ret;
+}
+
+fn native_library(i: u32) -> Option<Library> {
+ use object::macho;
+ use object::read::macho::{MachHeader, Segment};
+ use object::NativeEndian;
+
+ // Fetch the name of this library which corresponds to the path of
+ // where to load it as well.
+ let name = unsafe {
+ let name = libc::_dyld_get_image_name(i);
+ if name.is_null() {
+ return None;
+ }
+ CStr::from_ptr(name)
+ };
+
+ // Load the image header of this library and delegate to `object` to
+ // parse all the load commands so we can figure out all the segments
+ // involved here.
+ let (mut load_commands, endian) = unsafe {
+ let header = libc::_dyld_get_image_header(i);
+ if header.is_null() {
+ return None;
+ }
+ match (*header).magic {
+ macho::MH_MAGIC => {
+ let endian = NativeEndian;
+ let header = &*(header as *const macho::MachHeader32<NativeEndian>);
+ let data = core::slice::from_raw_parts(
+ header as *const _ as *const u8,
+ mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
+ );
+ (header.load_commands(endian, data, 0).ok()?, endian)
+ }
+ macho::MH_MAGIC_64 => {
+ let endian = NativeEndian;
+ let header = &*(header as *const macho::MachHeader64<NativeEndian>);
+ let data = core::slice::from_raw_parts(
+ header as *const _ as *const u8,
+ mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
+ );
+ (header.load_commands(endian, data, 0).ok()?, endian)
+ }
+ _ => return None,
+ }
+ };
+
+ // Iterate over the segments and register known regions for segments
+ // that we find. Additionally record information bout text segments
+ // for processing later, see comments below.
+ let mut segments = Vec::new();
+ let mut first_text = 0;
+ let mut text_fileoff_zero = false;
+ while let Some(cmd) = load_commands.next().ok()? {
+ if let Some((seg, _)) = cmd.segment_32().ok()? {
+ if seg.name() == b"__TEXT" {
+ first_text = segments.len();
+ if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
+ text_fileoff_zero = true;
+ }
+ }
+ segments.push(LibrarySegment {
+ len: seg.vmsize(endian).try_into().ok()?,
+ stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
+ });
+ }
+ if let Some((seg, _)) = cmd.segment_64().ok()? {
+ if seg.name() == b"__TEXT" {
+ first_text = segments.len();
+ if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
+ text_fileoff_zero = true;
+ }
+ }
+ segments.push(LibrarySegment {
+ len: seg.vmsize(endian).try_into().ok()?,
+ stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
+ });
+ }
+ }
+
+ // Determine the "slide" for this library which ends up being the
+ // bias we use to figure out where in memory objects are loaded.
+ // This is a bit of a weird computation though and is the result of
+ // trying a few things in the wild and seeing what sticks.
+ //
+ // The general idea is that the `bias` plus a segment's
+ // `stated_virtual_memory_address` is going to be where in the
+ // actual address space the segment resides. The other thing we rely
+ // on though is that a real address minus the `bias` is the index to
+ // look up in the symbol table and debuginfo.
+ //
+ // It turns out, though, that for system loaded libraries these
+ // calculations are incorrect. For native executables, however, it
+ // appears correct. Lifting some logic from LLDB's source it has
+ // some special-casing for the first `__TEXT` section loaded from
+ // file offset 0 with a nonzero size. For whatever reason when this
+ // is present it appears to mean that the symbol table is relative
+ // to just the vmaddr slide for the library. If it's *not* present
+ // then the symbol table is relative to the the vmaddr slide plus
+ // the segment's stated address.
+ //
+ // To handle this situation if we *don't* find a text section at
+ // file offset zero then we increase the bias by the first text
+ // sections's stated address and decrease all stated addresses by
+ // that amount as well. That way the symbol table is always appears
+ // relative to the library's bias amount. This appears to have the
+ // right results for symbolizing via the symbol table.
+ //
+ // Honestly I'm not entirely sure whether this is right or if
+ // there's something else that should indicate how to do this. For
+ // now though this seems to work well enough (?) and we should
+ // always be able to tweak this over time if necessary.
+ //
+ // For some more information see #318
+ let mut slide = unsafe { libc::_dyld_get_image_vmaddr_slide(i) as usize };
+ if !text_fileoff_zero {
+ let adjust = segments[first_text].stated_virtual_memory_address;
+ for segment in segments.iter_mut() {
+ segment.stated_virtual_memory_address -= adjust;
+ }
+ slide += adjust;
+ }
+
+ Some(Library {
+ name: OsStr::from_bytes(name.to_bytes()).to_owned(),
+ segments,
+ bias: slide,
+ })
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_windows.rs b/library/backtrace/src/symbolize/gimli/libs_windows.rs
new file mode 100644
index 000000000..b47ed4245
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_windows.rs
@@ -0,0 +1,89 @@
+use super::super::super::windows::*;
+use super::mystd::os::windows::prelude::*;
+use super::{coff, mmap, Library, LibrarySegment, OsString};
+use alloc::vec;
+use alloc::vec::Vec;
+use core::mem;
+use core::mem::MaybeUninit;
+
+// For loading native libraries on Windows, see some discussion on
+// rust-lang/rust#71060 for the various strategies here.
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ unsafe {
+ add_loaded_images(&mut ret);
+ }
+ return ret;
+}
+
+unsafe fn add_loaded_images(ret: &mut Vec<Library>) {
+ let snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
+ if snap == INVALID_HANDLE_VALUE {
+ return;
+ }
+
+ let mut me = MaybeUninit::<MODULEENTRY32W>::zeroed().assume_init();
+ me.dwSize = mem::size_of_val(&me) as DWORD;
+ if Module32FirstW(snap, &mut me) == TRUE {
+ loop {
+ if let Some(lib) = load_library(&me) {
+ ret.push(lib);
+ }
+
+ if Module32NextW(snap, &mut me) != TRUE {
+ break;
+ }
+ }
+ }
+
+ CloseHandle(snap);
+}
+
+unsafe fn load_library(me: &MODULEENTRY32W) -> Option<Library> {
+ let pos = me
+ .szExePath
+ .iter()
+ .position(|i| *i == 0)
+ .unwrap_or(me.szExePath.len());
+ let name = OsString::from_wide(&me.szExePath[..pos]);
+
+ // MinGW libraries currently don't support ASLR
+ // (rust-lang/rust#16514), but DLLs can still be relocated around in
+ // the address space. It appears that addresses in debug info are
+ // all as-if this library was loaded at its "image base", which is a
+ // field in its COFF file headers. Since this is what debuginfo
+ // seems to list we parse the symbol table and store addresses as if
+ // the library was loaded at "image base" as well.
+ //
+ // The library may not be loaded at "image base", however.
+ // (presumably something else may be loaded there?) This is where
+ // the `bias` field comes into play, and we need to figure out the
+ // value of `bias` here. Unfortunately though it's not clear how to
+ // acquire this from a loaded module. What we do have, however, is
+ // the actual load address (`modBaseAddr`).
+ //
+ // As a bit of a cop-out for now we mmap the file, read the file
+ // header information, then drop the mmap. This is wasteful because
+ // we'll probably reopen the mmap later, but this should work well
+ // enough for now.
+ //
+ // Once we have the `image_base` (desired load location) and the
+ // `base_addr` (actual load location) we can fill in the `bias`
+ // (difference between the actual and desired) and then the stated
+ // address of each segment is the `image_base` since that's what the
+ // file says.
+ //
+ // For now it appears that unlike ELF/MachO we can make do with one
+ // segment per library, using `modBaseSize` as the whole size.
+ let mmap = mmap(name.as_ref())?;
+ let image_base = coff::get_image_base(&mmap)?;
+ let base_addr = me.modBaseAddr as usize;
+ Some(Library {
+ name,
+ bias: base_addr.wrapping_sub(image_base),
+ segments: vec![LibrarySegment {
+ stated_virtual_memory_address: image_base,
+ len: me.modBaseSize as usize,
+ }],
+ })
+}
diff --git a/library/backtrace/src/symbolize/gimli/macho.rs b/library/backtrace/src/symbolize/gimli/macho.rs
new file mode 100644
index 000000000..ec5673843
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/macho.rs
@@ -0,0 +1,324 @@
+use super::{Box, Context, Mapping, Path, Stash, Vec};
+use core::convert::TryInto;
+use object::macho;
+use object::read::macho::{MachHeader, Nlist, Section, Segment as _};
+use object::{Bytes, NativeEndian};
+
+#[cfg(target_pointer_width = "32")]
+type Mach = object::macho::MachHeader32<NativeEndian>;
+#[cfg(target_pointer_width = "64")]
+type Mach = object::macho::MachHeader64<NativeEndian>;
+type MachSegment = <Mach as MachHeader>::Segment;
+type MachSection = <Mach as MachHeader>::Section;
+type MachNlist = <Mach as MachHeader>::Nlist;
+
+impl Mapping {
+ // The loading path for OSX is is so different we just have a completely
+ // different implementation of the function here. On OSX we need to go
+ // probing the filesystem for a bunch of files.
+ pub fn new(path: &Path) -> Option<Mapping> {
+ // First up we need to load the unique UUID which is stored in the macho
+ // header of the file we're reading, specified at `path`.
+ let map = super::mmap(path)?;
+ let (macho, data) = find_header(&map)?;
+ let endian = macho.endian().ok()?;
+ let uuid = macho.uuid(endian, data, 0).ok()?;
+
+ // Next we need to look for a `*.dSYM` file. For now we just probe the
+ // containing directory and look around for something that matches
+ // `*.dSYM`. Once it's found we root through the dwarf resources that it
+ // contains and try to find a macho file which has a matching UUID as
+ // the one of our own file. If we find a match that's the dwarf file we
+ // want to return.
+ if let Some(uuid) = uuid {
+ if let Some(parent) = path.parent() {
+ if let Some(mapping) = Mapping::load_dsym(parent, uuid) {
+ return Some(mapping);
+ }
+ }
+ }
+
+ // Looks like nothing matched our UUID, so let's at least return our own
+ // file. This should have the symbol table for at least some
+ // symbolication purposes.
+ Mapping::mk(map, |data, stash| {
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ })
+ }
+
+ fn load_dsym(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
+ for entry in dir.read_dir().ok()? {
+ let entry = entry.ok()?;
+ let filename = match entry.file_name().into_string() {
+ Ok(name) => name,
+ Err(_) => continue,
+ };
+ if !filename.ends_with(".dSYM") {
+ continue;
+ }
+ let candidates = entry.path().join("Contents/Resources/DWARF");
+ if let Some(mapping) = Mapping::try_dsym_candidate(&candidates, uuid) {
+ return Some(mapping);
+ }
+ }
+ None
+ }
+
+ fn try_dsym_candidate(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
+ // Look for files in the `DWARF` directory which have a matching uuid to
+ // the original object file. If we find one then we found the debug
+ // information.
+ for entry in dir.read_dir().ok()? {
+ let entry = entry.ok()?;
+ let map = super::mmap(&entry.path())?;
+ let candidate = Mapping::mk(map, |data, stash| {
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let entry_uuid = macho.uuid(endian, data, 0).ok()??;
+ if entry_uuid != uuid {
+ return None;
+ }
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ });
+ if let Some(candidate) = candidate {
+ return Some(candidate);
+ }
+ }
+
+ None
+ }
+}
+
+fn find_header(data: &'_ [u8]) -> Option<(&'_ Mach, &'_ [u8])> {
+ use object::endian::BigEndian;
+
+ let desired_cpu = || {
+ if cfg!(target_arch = "x86") {
+ Some(macho::CPU_TYPE_X86)
+ } else if cfg!(target_arch = "x86_64") {
+ Some(macho::CPU_TYPE_X86_64)
+ } else if cfg!(target_arch = "arm") {
+ Some(macho::CPU_TYPE_ARM)
+ } else if cfg!(target_arch = "aarch64") {
+ Some(macho::CPU_TYPE_ARM64)
+ } else {
+ None
+ }
+ };
+
+ let mut data = Bytes(data);
+ match data
+ .clone()
+ .read::<object::endian::U32<NativeEndian>>()
+ .ok()?
+ .get(NativeEndian)
+ {
+ macho::MH_MAGIC_64 | macho::MH_CIGAM_64 | macho::MH_MAGIC | macho::MH_CIGAM => {}
+
+ macho::FAT_MAGIC | macho::FAT_CIGAM => {
+ let mut header_data = data;
+ let endian = BigEndian;
+ let header = header_data.read::<macho::FatHeader>().ok()?;
+ let nfat = header.nfat_arch.get(endian);
+ let arch = (0..nfat)
+ .filter_map(|_| header_data.read::<macho::FatArch32>().ok())
+ .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
+ let offset = arch.offset.get(endian);
+ let size = arch.size.get(endian);
+ data = data
+ .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
+ .ok()?;
+ }
+
+ macho::FAT_MAGIC_64 | macho::FAT_CIGAM_64 => {
+ let mut header_data = data;
+ let endian = BigEndian;
+ let header = header_data.read::<macho::FatHeader>().ok()?;
+ let nfat = header.nfat_arch.get(endian);
+ let arch = (0..nfat)
+ .filter_map(|_| header_data.read::<macho::FatArch64>().ok())
+ .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
+ let offset = arch.offset.get(endian);
+ let size = arch.size.get(endian);
+ data = data
+ .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
+ .ok()?;
+ }
+
+ _ => return None,
+ }
+
+ Mach::parse(data.0, 0).ok().map(|h| (h, data.0))
+}
+
+// This is used both for executables/libraries and source object files.
+pub struct Object<'a> {
+ endian: NativeEndian,
+ data: &'a [u8],
+ dwarf: Option<&'a [MachSection]>,
+ syms: Vec<(&'a [u8], u64)>,
+ syms_sort_by_name: bool,
+ // Only set for executables/libraries, and not the source object files.
+ object_map: Option<object::ObjectMap<'a>>,
+ // The outer Option is for lazy loading, and the inner Option allows load errors to be cached.
+ object_mappings: Box<[Option<Option<Mapping>>]>,
+}
+
+impl<'a> Object<'a> {
+ fn parse(mach: &'a Mach, endian: NativeEndian, data: &'a [u8]) -> Option<Object<'a>> {
+ let is_object = mach.filetype(endian) == object::macho::MH_OBJECT;
+ let mut dwarf = None;
+ let mut syms = Vec::new();
+ let mut syms_sort_by_name = false;
+ let mut commands = mach.load_commands(endian, data, 0).ok()?;
+ let mut object_map = None;
+ let mut object_mappings = Vec::new();
+ while let Ok(Some(command)) = commands.next() {
+ if let Some((segment, section_data)) = MachSegment::from_command(command).ok()? {
+ // Object files should have all sections in a single unnamed segment load command.
+ if segment.name() == b"__DWARF" || (is_object && segment.name() == b"") {
+ dwarf = segment.sections(endian, section_data).ok();
+ }
+ } else if let Some(symtab) = command.symtab().ok()? {
+ let symbols = symtab.symbols::<Mach, _>(endian, data).ok()?;
+ syms = symbols
+ .iter()
+ .filter_map(|nlist: &MachNlist| {
+ let name = nlist.name(endian, symbols.strings()).ok()?;
+ if name.len() > 0 && nlist.is_definition() {
+ Some((name, u64::from(nlist.n_value(endian))))
+ } else {
+ None
+ }
+ })
+ .collect();
+ if is_object {
+ // We never search object file symbols by address.
+ // Instead, we already know the symbol name from the executable, and we
+ // need to search by name to find the matching symbol in the object file.
+ syms.sort_unstable_by_key(|(name, _)| *name);
+ syms_sort_by_name = true;
+ } else {
+ syms.sort_unstable_by_key(|(_, addr)| *addr);
+ let map = symbols.object_map(endian);
+ object_mappings.resize_with(map.objects().len(), || None);
+ object_map = Some(map);
+ }
+ }
+ }
+
+ Some(Object {
+ endian,
+ data,
+ dwarf,
+ syms,
+ syms_sort_by_name,
+ object_map,
+ object_mappings: object_mappings.into_boxed_slice(),
+ })
+ }
+
+ pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
+ let name = name.as_bytes();
+ let dwarf = self.dwarf?;
+ let section = dwarf.into_iter().find(|section| {
+ let section_name = section.name();
+ section_name == name || {
+ section_name.starts_with(b"__")
+ && name.starts_with(b".")
+ && &section_name[2..] == &name[1..]
+ }
+ })?;
+ Some(section.data(self.endian, self.data).ok()?)
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ debug_assert!(!self.syms_sort_by_name);
+ let i = match self.syms.binary_search_by_key(&addr, |(_, addr)| *addr) {
+ Ok(i) => i,
+ Err(i) => i.checked_sub(1)?,
+ };
+ let (sym, _addr) = self.syms.get(i)?;
+ Some(sym)
+ }
+
+ /// Try to load a context for an object file.
+ ///
+ /// If dsymutil was not run, then the DWARF may be found in the source object files.
+ pub(super) fn search_object_map<'b>(&'b mut self, addr: u64) -> Option<(&Context<'b>, u64)> {
+ // `object_map` contains a map from addresses to symbols and object paths.
+ // Look up the address and get a mapping for the object.
+ let object_map = self.object_map.as_ref()?;
+ let symbol = object_map.get(addr)?;
+ let object_index = symbol.object_index();
+ let mapping = self.object_mappings.get_mut(object_index)?;
+ if mapping.is_none() {
+ // No cached mapping, so create it.
+ *mapping = Some(object_mapping(object_map.objects().get(object_index)?));
+ }
+ let cx: &'b Context<'static> = &mapping.as_ref()?.as_ref()?.cx;
+ // Don't leak the `'static` lifetime, make sure it's scoped to just ourselves.
+ let cx = unsafe { core::mem::transmute::<&'b Context<'static>, &'b Context<'b>>(cx) };
+
+ // We must translate the address in order to be able to look it up
+ // in the DWARF in the object file.
+ debug_assert!(cx.object.syms.is_empty() || cx.object.syms_sort_by_name);
+ let i = cx
+ .object
+ .syms
+ .binary_search_by_key(&symbol.name(), |(name, _)| *name)
+ .ok()?;
+ let object_symbol = cx.object.syms.get(i)?;
+ let object_addr = addr
+ .wrapping_sub(symbol.address())
+ .wrapping_add(object_symbol.1);
+ Some((cx, object_addr))
+ }
+}
+
+fn object_mapping(path: &[u8]) -> Option<Mapping> {
+ use super::mystd::ffi::OsStr;
+ use super::mystd::os::unix::prelude::*;
+
+ let map;
+
+ // `N_OSO` symbol names can be either `/path/to/object.o` or `/path/to/archive.a(object.o)`.
+ let member_name = if let Some((archive_path, member_name)) = split_archive_path(path) {
+ map = super::mmap(Path::new(OsStr::from_bytes(archive_path)))?;
+ Some(member_name)
+ } else {
+ map = super::mmap(Path::new(OsStr::from_bytes(path)))?;
+ None
+ };
+ Mapping::mk(map, |data, stash| {
+ let data = match member_name {
+ Some(member_name) => {
+ let archive = object::read::archive::ArchiveFile::parse(data).ok()?;
+ let member = archive
+ .members()
+ .filter_map(Result::ok)
+ .find(|m| m.name() == member_name)?;
+ member.data(data).ok()?
+ }
+ None => data,
+ };
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ })
+}
+
+fn split_archive_path(path: &[u8]) -> Option<(&[u8], &[u8])> {
+ let (last, path) = path.split_last()?;
+ if *last != b')' {
+ return None;
+ }
+ let index = path.iter().position(|&x| x == b'(')?;
+ let (archive, rest) = path.split_at(index);
+ Some((archive, &rest[1..]))
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_fake.rs b/library/backtrace/src/symbolize/gimli/mmap_fake.rs
new file mode 100644
index 000000000..ce5096415
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_fake.rs
@@ -0,0 +1,25 @@
+use super::{mystd::io::Read, File};
+use alloc::vec::Vec;
+use core::ops::Deref;
+
+pub struct Mmap {
+ vec: Vec<u8>,
+}
+
+impl Mmap {
+ pub unsafe fn map(mut file: &File, len: usize) -> Option<Mmap> {
+ let mut mmap = Mmap {
+ vec: Vec::with_capacity(len),
+ };
+ file.read_to_end(&mut mmap.vec).ok()?;
+ Some(mmap)
+ }
+}
+
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ &self.vec[..]
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_unix.rs b/library/backtrace/src/symbolize/gimli/mmap_unix.rs
new file mode 100644
index 000000000..5806c9f7e
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_unix.rs
@@ -0,0 +1,44 @@
+use super::mystd::fs::File;
+use super::mystd::os::unix::prelude::*;
+use core::ops::Deref;
+use core::ptr;
+use core::slice;
+
+pub struct Mmap {
+ ptr: *mut libc::c_void,
+ len: usize,
+}
+
+impl Mmap {
+ pub unsafe fn map(file: &File, len: usize) -> Option<Mmap> {
+ let ptr = libc::mmap(
+ ptr::null_mut(),
+ len,
+ libc::PROT_READ,
+ libc::MAP_PRIVATE,
+ file.as_raw_fd(),
+ 0,
+ );
+ if ptr == libc::MAP_FAILED {
+ return None;
+ }
+ Some(Mmap { ptr, len })
+ }
+}
+
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
+ }
+}
+
+impl Drop for Mmap {
+ fn drop(&mut self) {
+ unsafe {
+ let r = libc::munmap(self.ptr, self.len);
+ debug_assert_eq!(r, 0);
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_windows.rs b/library/backtrace/src/symbolize/gimli/mmap_windows.rs
new file mode 100644
index 000000000..22f53fe03
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_windows.rs
@@ -0,0 +1,57 @@
+use super::super::super::windows::*;
+use super::mystd::fs::File;
+use super::mystd::os::windows::prelude::*;
+use core::ops::Deref;
+use core::ptr;
+use core::slice;
+
+pub struct Mmap {
+ // keep the file alive to prevent it from ebeing deleted which would cause
+ // us to read bad data.
+ _file: File,
+ ptr: *mut c_void,
+ len: usize,
+}
+
+impl Mmap {
+ pub unsafe fn map(file: &File, len: usize) -> Option<Mmap> {
+ let file = file.try_clone().ok()?;
+ let mapping = CreateFileMappingA(
+ file.as_raw_handle() as *mut _,
+ ptr::null_mut(),
+ PAGE_READONLY,
+ 0,
+ 0,
+ ptr::null(),
+ );
+ if mapping.is_null() {
+ return None;
+ }
+ let ptr = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, len);
+ CloseHandle(mapping);
+ if ptr.is_null() {
+ return None;
+ }
+ Some(Mmap {
+ _file: file,
+ ptr,
+ len,
+ })
+ }
+}
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
+ }
+}
+
+impl Drop for Mmap {
+ fn drop(&mut self) {
+ unsafe {
+ let r = UnmapViewOfFile(self.ptr);
+ debug_assert!(r != 0);
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/stash.rs b/library/backtrace/src/symbolize/gimli/stash.rs
new file mode 100644
index 000000000..3adfc598a
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/stash.rs
@@ -0,0 +1,52 @@
+// only used on Linux right now, so allow dead code elsewhere
+#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
+
+use super::Mmap;
+use alloc::vec;
+use alloc::vec::Vec;
+use core::cell::UnsafeCell;
+
+/// A simple arena allocator for byte buffers.
+pub struct Stash {
+ buffers: UnsafeCell<Vec<Vec<u8>>>,
+ mmap_aux: UnsafeCell<Option<Mmap>>,
+}
+
+impl Stash {
+ pub fn new() -> Stash {
+ Stash {
+ buffers: UnsafeCell::new(Vec::new()),
+ mmap_aux: UnsafeCell::new(None),
+ }
+ }
+
+ /// Allocates a buffer of the specified size and returns a mutable reference
+ /// to it.
+ pub fn allocate(&self, size: usize) -> &mut [u8] {
+ // SAFETY: this is the only function that ever constructs a mutable
+ // reference to `self.buffers`.
+ let buffers = unsafe { &mut *self.buffers.get() };
+ let i = buffers.len();
+ buffers.push(vec![0; size]);
+ // SAFETY: we never remove elements from `self.buffers`, so a reference
+ // to the data inside any buffer will live as long as `self` does.
+ &mut buffers[i]
+ }
+
+ /// Stores a `Mmap` for the lifetime of this `Stash`, returning a pointer
+ /// which is scoped to just this lifetime.
+ pub fn set_mmap_aux(&self, map: Mmap) -> &[u8] {
+ // SAFETY: this is the only location for a mutable pointer to
+ // `mmap_aux`, and this structure isn't threadsafe to shared across
+ // threads either. This also is careful to store at most one `mmap_aux`
+ // since overwriting a previous one would invalidate the previous
+ // pointer. Given that though we can safely return a pointer to our
+ // interior-owned contents.
+ unsafe {
+ let mmap_aux = &mut *self.mmap_aux.get();
+ assert!(mmap_aux.is_none());
+ *mmap_aux = Some(map);
+ mmap_aux.as_ref().unwrap()
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/miri.rs b/library/backtrace/src/symbolize/miri.rs
new file mode 100644
index 000000000..5b0dc3084
--- /dev/null
+++ b/library/backtrace/src/symbolize/miri.rs
@@ -0,0 +1,56 @@
+use core::ffi::c_void;
+use core::marker::PhantomData;
+
+use super::super::backtrace::miri::{resolve_addr, Frame};
+use super::BytesOrWideString;
+use super::{ResolveWhat, SymbolName};
+
+pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
+ let sym = match what {
+ ResolveWhat::Address(addr) => Symbol {
+ inner: resolve_addr(addr),
+ _unused: PhantomData,
+ },
+ ResolveWhat::Frame(frame) => Symbol {
+ inner: frame.inner.clone(),
+ _unused: PhantomData,
+ },
+ };
+ cb(&super::Symbol { inner: sym })
+}
+
+pub struct Symbol<'a> {
+ inner: Frame,
+ _unused: PhantomData<&'a ()>,
+}
+
+impl<'a> Symbol<'a> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ Some(SymbolName::new(&self.inner.inner.name))
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ Some(self.inner.addr)
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ Some(BytesOrWideString::Bytes(&self.inner.inner.filename))
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ Some(self.inner.inner.lineno)
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ Some(self.inner.inner.colno)
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&std::path::Path> {
+ Some(std::path::Path::new(
+ core::str::from_utf8(&self.inner.inner.filename).unwrap(),
+ ))
+ }
+}
+
+pub unsafe fn clear_symbol_cache() {}
diff --git a/library/backtrace/src/symbolize/mod.rs b/library/backtrace/src/symbolize/mod.rs
new file mode 100644
index 000000000..dbc346522
--- /dev/null
+++ b/library/backtrace/src/symbolize/mod.rs
@@ -0,0 +1,485 @@
+use core::{fmt, str};
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "std")] {
+ use std::path::Path;
+ use std::prelude::v1::*;
+ }
+}
+
+use super::backtrace::Frame;
+use super::types::BytesOrWideString;
+use core::ffi::c_void;
+use rustc_demangle::{try_demangle, Demangle};
+
+/// Resolve an address to a symbol, passing the symbol to the specified
+/// closure.
+///
+/// This function will look up the given address in areas such as the local
+/// symbol table, dynamic symbol table, or DWARF debug info (depending on the
+/// activated implementation) to find symbols to yield.
+///
+/// The closure may not be called if resolution could not be performed, and it
+/// also may be called more than once in the case of inlined functions.
+///
+/// Symbols yielded represent the execution at the specified `addr`, returning
+/// file/line pairs for that address (if available).
+///
+/// Note that if you have a `Frame` then it's recommended to use the
+/// `resolve_frame` function instead of this one.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+///
+/// # Panics
+///
+/// This function strives to never panic, but if the `cb` provided panics then
+/// some platforms will force a double panic to abort the process. Some
+/// platforms use a C library which internally uses callbacks which cannot be
+/// unwound through, so panicking from `cb` may trigger a process abort.
+///
+/// # Example
+///
+/// ```
+/// extern crate backtrace;
+///
+/// fn main() {
+/// backtrace::trace(|frame| {
+/// let ip = frame.ip();
+///
+/// backtrace::resolve(ip, |symbol| {
+/// // ...
+/// });
+///
+/// false // only look at the top frame
+/// });
+/// }
+/// ```
+#[cfg(feature = "std")]
+pub fn resolve<F: FnMut(&Symbol)>(addr: *mut c_void, cb: F) {
+ let _guard = crate::lock::lock();
+ unsafe { resolve_unsynchronized(addr, cb) }
+}
+
+/// Resolve a previously capture frame to a symbol, passing the symbol to the
+/// specified closure.
+///
+/// This function performs the same function as `resolve` except that it takes a
+/// `Frame` as an argument instead of an address. This can allow some platform
+/// implementations of backtracing to provide more accurate symbol information
+/// or information about inline frames for example. It's recommended to use this
+/// if you can.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+///
+/// # Panics
+///
+/// This function strives to never panic, but if the `cb` provided panics then
+/// some platforms will force a double panic to abort the process. Some
+/// platforms use a C library which internally uses callbacks which cannot be
+/// unwound through, so panicking from `cb` may trigger a process abort.
+///
+/// # Example
+///
+/// ```
+/// extern crate backtrace;
+///
+/// fn main() {
+/// backtrace::trace(|frame| {
+/// backtrace::resolve_frame(frame, |symbol| {
+/// // ...
+/// });
+///
+/// false // only look at the top frame
+/// });
+/// }
+/// ```
+#[cfg(feature = "std")]
+pub fn resolve_frame<F: FnMut(&Symbol)>(frame: &Frame, cb: F) {
+ let _guard = crate::lock::lock();
+ unsafe { resolve_frame_unsynchronized(frame, cb) }
+}
+
+pub enum ResolveWhat<'a> {
+ Address(*mut c_void),
+ Frame(&'a Frame),
+}
+
+impl<'a> ResolveWhat<'a> {
+ #[allow(dead_code)]
+ fn address_or_ip(&self) -> *mut c_void {
+ match self {
+ ResolveWhat::Address(a) => adjust_ip(*a),
+ ResolveWhat::Frame(f) => adjust_ip(f.ip()),
+ }
+ }
+}
+
+// IP values from stack frames are typically (always?) the instruction
+// *after* the call that's the actual stack trace. Symbolizing this on
+// causes the filename/line number to be one ahead and perhaps into
+// the void if it's near the end of the function.
+//
+// This appears to basically always be the case on all platforms, so we always
+// subtract one from a resolved ip to resolve it to the previous call
+// instruction instead of the instruction being returned to.
+//
+// Ideally we would not do this. Ideally we would require callers of the
+// `resolve` APIs here to manually do the -1 and account that they want location
+// information for the *previous* instruction, not the current. Ideally we'd
+// also expose on `Frame` if we are indeed the address of the next instruction
+// or the current.
+//
+// For now though this is a pretty niche concern so we just internally always
+// subtract one. Consumers should keep working and getting pretty good results,
+// so we should be good enough.
+fn adjust_ip(a: *mut c_void) -> *mut c_void {
+ if a.is_null() {
+ a
+ } else {
+ (a as usize - 1) as *mut c_void
+ }
+}
+
+/// Same as `resolve`, only unsafe as it's unsynchronized.
+///
+/// This function does not have synchronization guarantees but is available when
+/// the `std` feature of this crate isn't compiled in. See the `resolve`
+/// function for more documentation and examples.
+///
+/// # Panics
+///
+/// See information on `resolve` for caveats on `cb` panicking.
+pub unsafe fn resolve_unsynchronized<F>(addr: *mut c_void, mut cb: F)
+where
+ F: FnMut(&Symbol),
+{
+ imp::resolve(ResolveWhat::Address(addr), &mut cb)
+}
+
+/// Same as `resolve_frame`, only unsafe as it's unsynchronized.
+///
+/// This function does not have synchronization guarantees but is available
+/// when the `std` feature of this crate isn't compiled in. See the
+/// `resolve_frame` function for more documentation and examples.
+///
+/// # Panics
+///
+/// See information on `resolve_frame` for caveats on `cb` panicking.
+pub unsafe fn resolve_frame_unsynchronized<F>(frame: &Frame, mut cb: F)
+where
+ F: FnMut(&Symbol),
+{
+ imp::resolve(ResolveWhat::Frame(frame), &mut cb)
+}
+
+/// A trait representing the resolution of a symbol in a file.
+///
+/// This trait is yielded as a trait object to the closure given to the
+/// `backtrace::resolve` function, and it is virtually dispatched as it's
+/// unknown which implementation is behind it.
+///
+/// A symbol can give contextual information about a function, for example the
+/// name, filename, line number, precise address, etc. Not all information is
+/// always available in a symbol, however, so all methods return an `Option`.
+pub struct Symbol {
+ // TODO: this lifetime bound needs to be persisted eventually to `Symbol`,
+ // but that's currently a breaking change. For now this is safe since
+ // `Symbol` is only ever handed out by reference and can't be cloned.
+ inner: imp::Symbol<'static>,
+}
+
+impl Symbol {
+ /// Returns the name of this function.
+ ///
+ /// The returned structure can be used to query various properties about the
+ /// symbol name:
+ ///
+ /// * The `Display` implementation will print out the demangled symbol.
+ /// * The raw `str` value of the symbol can be accessed (if it's valid
+ /// utf-8).
+ /// * The raw bytes for the symbol name can be accessed.
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ self.inner.name()
+ }
+
+ /// Returns the starting address of this function.
+ pub fn addr(&self) -> Option<*mut c_void> {
+ self.inner.addr().map(|p| p as *mut _)
+ }
+
+ /// Returns the raw filename as a slice. This is mainly useful for `no_std`
+ /// environments.
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ self.inner.filename_raw()
+ }
+
+ /// Returns the column number for where this symbol is currently executing.
+ ///
+ /// Only gimli currently provides a value here and even then only if `filename`
+ /// returns `Some`, and so it is then consequently subject to similar caveats.
+ pub fn colno(&self) -> Option<u32> {
+ self.inner.colno()
+ }
+
+ /// Returns the line number for where this symbol is currently executing.
+ ///
+ /// This return value is typically `Some` if `filename` returns `Some`, and
+ /// is consequently subject to similar caveats.
+ pub fn lineno(&self) -> Option<u32> {
+ self.inner.lineno()
+ }
+
+ /// Returns the file name where this function was defined.
+ ///
+ /// This is currently only available when libbacktrace or gimli is being
+ /// used (e.g. unix platforms other) and when a binary is compiled with
+ /// debuginfo. If neither of these conditions is met then this will likely
+ /// return `None`.
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[cfg(feature = "std")]
+ #[allow(unreachable_code)]
+ pub fn filename(&self) -> Option<&Path> {
+ self.inner.filename()
+ }
+}
+
+impl fmt::Debug for Symbol {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut d = f.debug_struct("Symbol");
+ if let Some(name) = self.name() {
+ d.field("name", &name);
+ }
+ if let Some(addr) = self.addr() {
+ d.field("addr", &addr);
+ }
+
+ #[cfg(feature = "std")]
+ {
+ if let Some(filename) = self.filename() {
+ d.field("filename", &filename);
+ }
+ }
+
+ if let Some(lineno) = self.lineno() {
+ d.field("lineno", &lineno);
+ }
+ d.finish()
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "cpp_demangle")] {
+ // Maybe a parsed C++ symbol, if parsing the mangled symbol as Rust
+ // failed.
+ struct OptionCppSymbol<'a>(Option<::cpp_demangle::BorrowedSymbol<'a>>);
+
+ impl<'a> OptionCppSymbol<'a> {
+ fn parse(input: &'a [u8]) -> OptionCppSymbol<'a> {
+ OptionCppSymbol(::cpp_demangle::BorrowedSymbol::new(input).ok())
+ }
+
+ fn none() -> OptionCppSymbol<'a> {
+ OptionCppSymbol(None)
+ }
+ }
+ } else {
+ use core::marker::PhantomData;
+
+ // Make sure to keep this zero-sized, so that the `cpp_demangle` feature
+ // has no cost when disabled.
+ struct OptionCppSymbol<'a>(PhantomData<&'a ()>);
+
+ impl<'a> OptionCppSymbol<'a> {
+ fn parse(_: &'a [u8]) -> OptionCppSymbol<'a> {
+ OptionCppSymbol(PhantomData)
+ }
+
+ fn none() -> OptionCppSymbol<'a> {
+ OptionCppSymbol(PhantomData)
+ }
+ }
+ }
+}
+
+/// A wrapper around a symbol name to provide ergonomic accessors to the
+/// demangled name, the raw bytes, the raw string, etc.
+// Allow dead code for when the `cpp_demangle` feature is not enabled.
+#[allow(dead_code)]
+pub struct SymbolName<'a> {
+ bytes: &'a [u8],
+ demangled: Option<Demangle<'a>>,
+ cpp_demangled: OptionCppSymbol<'a>,
+}
+
+impl<'a> SymbolName<'a> {
+ /// Creates a new symbol name from the raw underlying bytes.
+ pub fn new(bytes: &'a [u8]) -> SymbolName<'a> {
+ let str_bytes = str::from_utf8(bytes).ok();
+ let demangled = str_bytes.and_then(|s| try_demangle(s).ok());
+
+ let cpp = if demangled.is_none() {
+ OptionCppSymbol::parse(bytes)
+ } else {
+ OptionCppSymbol::none()
+ };
+
+ SymbolName {
+ bytes: bytes,
+ demangled: demangled,
+ cpp_demangled: cpp,
+ }
+ }
+
+ /// Returns the raw (mangled) symbol name as a `str` if the symbol is valid utf-8.
+ ///
+ /// Use the `Display` implementation if you want the demangled version.
+ pub fn as_str(&self) -> Option<&'a str> {
+ self.demangled
+ .as_ref()
+ .map(|s| s.as_str())
+ .or_else(|| str::from_utf8(self.bytes).ok())
+ }
+
+ /// Returns the raw symbol name as a list of bytes
+ pub fn as_bytes(&self) -> &'a [u8] {
+ self.bytes
+ }
+}
+
+fn format_symbol_name(
+ fmt: fn(&str, &mut fmt::Formatter<'_>) -> fmt::Result,
+ mut bytes: &[u8],
+ f: &mut fmt::Formatter<'_>,
+) -> fmt::Result {
+ while bytes.len() > 0 {
+ match str::from_utf8(bytes) {
+ Ok(name) => {
+ fmt(name, f)?;
+ break;
+ }
+ Err(err) => {
+ fmt("\u{FFFD}", f)?;
+
+ match err.error_len() {
+ Some(len) => bytes = &bytes[err.valid_up_to() + len..],
+ None => break,
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "cpp_demangle")] {
+ impl<'a> fmt::Display for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else if let Some(ref cpp) = self.cpp_demangled.0 {
+ cpp.fmt(f)
+ } else {
+ format_symbol_name(fmt::Display::fmt, self.bytes, f)
+ }
+ }
+ }
+ } else {
+ impl<'a> fmt::Display for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else {
+ format_symbol_name(fmt::Display::fmt, self.bytes, f)
+ }
+ }
+ }
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(all(feature = "std", feature = "cpp_demangle"))] {
+ impl<'a> fmt::Debug for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use std::fmt::Write;
+
+ if let Some(ref s) = self.demangled {
+ return s.fmt(f)
+ }
+
+ // This may to print if the demangled symbol isn't actually
+ // valid, so handle the error here gracefully by not propagating
+ // it outwards.
+ if let Some(ref cpp) = self.cpp_demangled.0 {
+ let mut s = String::new();
+ if write!(s, "{}", cpp).is_ok() {
+ return s.fmt(f)
+ }
+ }
+
+ format_symbol_name(fmt::Debug::fmt, self.bytes, f)
+ }
+ }
+ } else {
+ impl<'a> fmt::Debug for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else {
+ format_symbol_name(fmt::Debug::fmt, self.bytes, f)
+ }
+ }
+ }
+ }
+}
+
+/// Attempt to reclaim that cached memory used to symbolicate addresses.
+///
+/// This method will attempt to release any global data structures that have
+/// otherwise been cached globally or in the thread which typically represent
+/// parsed DWARF information or similar.
+///
+/// # Caveats
+///
+/// While this function is always available it doesn't actually do anything on
+/// most implementations. Libraries like dbghelp or libbacktrace do not provide
+/// facilities to deallocate state and manage the allocated memory. For now the
+/// `gimli-symbolize` feature of this crate is the only feature where this
+/// function has any effect.
+#[cfg(feature = "std")]
+pub fn clear_symbol_cache() {
+ let _guard = crate::lock::lock();
+ unsafe {
+ imp::clear_symbol_cache();
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(miri)] {
+ mod miri;
+ use miri as imp;
+ } else if #[cfg(all(windows, target_env = "msvc", not(target_vendor = "uwp")))] {
+ mod dbghelp;
+ use dbghelp as imp;
+ } else if #[cfg(all(
+ any(unix, windows),
+ not(target_vendor = "uwp"),
+ not(target_os = "emscripten"),
+ any(not(backtrace_in_libstd), feature = "backtrace"),
+ ))] {
+ mod gimli;
+ use gimli as imp;
+ } else {
+ mod noop;
+ use noop as imp;
+ }
+}
diff --git a/library/backtrace/src/symbolize/noop.rs b/library/backtrace/src/symbolize/noop.rs
new file mode 100644
index 000000000..c53336531
--- /dev/null
+++ b/library/backtrace/src/symbolize/noop.rs
@@ -0,0 +1,41 @@
+//! Empty symbolication strategy used to compile for platforms that have no
+//! support.
+
+use super::{BytesOrWideString, ResolveWhat, SymbolName};
+use core::ffi::c_void;
+use core::marker;
+
+pub unsafe fn resolve(_addr: ResolveWhat<'_>, _cb: &mut dyn FnMut(&super::Symbol)) {}
+
+pub struct Symbol<'a> {
+ _marker: marker::PhantomData<&'a i32>,
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ None
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ None
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ None
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&::std::path::Path> {
+ None
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ None
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ None
+ }
+}
+
+pub unsafe fn clear_symbol_cache() {}