diff options
Diffstat (limited to 'vendor/backtrace/src/symbolize')
19 files changed, 3213 insertions, 0 deletions
diff --git a/vendor/backtrace/src/symbolize/dbghelp.rs b/vendor/backtrace/src/symbolize/dbghelp.rs new file mode 100644 index 0000000..181dba7 --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli.rs b/vendor/backtrace/src/symbolize/gimli.rs new file mode 100644 index 0000000..7f1c6a5 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli.rs @@ -0,0 +1,511 @@ +//! 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>, + package: Option<gimli::DwarfPackage<EndianSlice<'a, Endian>>>, +} + +impl<'data> Context<'data> { + fn new( + stash: &'data Stash, + object: Object<'data>, + sup: Option<Object<'data>>, + dwp: 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()?; + + let mut package = None; + if let Some(dwp) = dwp { + package = Some( + gimli::DwarfPackage::load( + |id| -> Result<_, gimli::Error> { + let data = id + .dwo_name() + .and_then(|name| dwp.section(stash, name)) + .unwrap_or(&[]); + Ok(EndianSlice::new(data, Endian)) + }, + EndianSlice::new(&[], Endian), + ) + .ok()?, + ); + } + + Some(Context { + dwarf, + object, + package, + }) + } + + fn find_frames( + &'_ self, + stash: &'data Stash, + probe: u64, + ) -> gimli::Result<addr2line::FrameIter<'_, EndianSlice<'data, Endian>>> { + use addr2line::{LookupContinuation, LookupResult}; + + let mut l = self.dwarf.find_frames(probe); + loop { + let (load, continuation) = match l { + LookupResult::Output(output) => break output, + LookupResult::Load { load, continuation } => (load, continuation), + }; + + l = continuation.resume(handle_split_dwarf(self.package.as_ref(), stash, load)); + } + } +} + +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::{handle_split_dwarf, Object}; + } else if #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + ))] { + mod macho; + use self::macho::{handle_split_dwarf, Object}; + } else { + mod elf; + use self::elf::{handle_split_dwarf, 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", + target_os = "netbsd", + all(target_os = "android", feature = "dl_iterate_phdr"), + ), + not(target_env = "uclibc"), + ))] { + mod libs_dl_iterate_phdr; + use libs_dl_iterate_phdr::native_libraries; + #[path = "gimli/parse_running_mmaps_unix.rs"] + mod parse_running_mmaps; + } 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>, &'a Stash)> { + 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 mapping = &mut self.mappings[0].1; + let cx: &'a mut Context<'static> = &mut mapping.cx; + let stash: &'a Stash = &mapping.stash; + // 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) }, + stash, + )) + } +} + +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, stash) = match cache.mapping_for_lib(lib) { + Some((cx, stash)) => (cx, stash), + None => return, + }; + let mut any_frames = false; + if let Ok(mut frames) = cx.find_frames(stash, 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.find_frames(stash, 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/vendor/backtrace/src/symbolize/gimli/coff.rs b/vendor/backtrace/src/symbolize/gimli/coff.rs new file mode 100644 index 0000000..759c827 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/coff.rs @@ -0,0 +1,118 @@ +use super::{gimli, Context, Endian, EndianSlice, Mapping, Path, Stash, Vec}; +use alloc::sync::Arc; +use core::convert::TryFrom; +use object::pe::{ImageDosHeader, ImageSymbol}; +use object::read::coff::ImageSymbol as _; +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, 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 + } +} + +pub(super) fn handle_split_dwarf<'data>( + _package: Option<&gimli::DwarfPackage<EndianSlice<'data, Endian>>>, + _stash: &'data Stash, + _load: addr2line::SplitDwarfLoad<EndianSlice<'data, Endian>>, +) -> Option<Arc<gimli::Dwarf<EndianSlice<'data, Endian>>>> { + None +} diff --git a/vendor/backtrace/src/symbolize/gimli/elf.rs b/vendor/backtrace/src/symbolize/gimli/elf.rs new file mode 100644 index 0000000..b0eec07 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/elf.rs @@ -0,0 +1,495 @@ +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::{gimli, Context, Endian, EndianSlice, Mapping, Stash, Vec}; +use alloc::sync::Arc; +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, 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, path_debug, Some(crc)) { + return Some(Either::A(mapping)); + } + } + + let dwp = Mapping::load_dwarf_package(path, stash); + + Context::new(stash, object, None, dwp).map(Either::B) + }) + } + + /// Load debuginfo from an external debug file. + fn new_debug(original_path: &Path, 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. + let mut sup = None; + 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.cache_mmap(map_sup); + if let Some(sup_) = Object::parse(map_sup) { + if sup_.build_id() == Some(build_id_sup) { + sup = Some(sup_); + } + } + } + } + + let dwp = Mapping::load_dwarf_package(original_path, stash); + + Context::new(stash, object, sup, dwp) + }) + } + + /// Try to locate a DWARF package file. + fn load_dwarf_package<'data>(path: &Path, stash: &'data Stash) -> Option<Object<'data>> { + let mut path_dwp = path.to_path_buf(); + let dwp_extension = path + .extension() + .map(|previous_extension| { + let mut previous_extension = previous_extension.to_os_string(); + previous_extension.push(".dwp"); + previous_extension + }) + .unwrap_or_else(|| "dwp".into()); + path_dwp.set_extension(dwp_extension); + if let Some(map_dwp) = super::mmap(&path_dwp) { + let map_dwp = stash.cache_mmap(map_dwp); + if let Some(dwp_) = Object::parse(map_dwp) { + return Some(dwp_); + } + } + + 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) +} + +fn convert_path<R: gimli::Reader>(r: &R) -> Result<PathBuf, gimli::Error> { + let bytes = r.to_slice()?; + Ok(PathBuf::from(OsStr::from_bytes(&bytes))) +} + +pub(super) fn handle_split_dwarf<'data>( + package: Option<&gimli::DwarfPackage<EndianSlice<'data, Endian>>>, + stash: &'data Stash, + load: addr2line::SplitDwarfLoad<EndianSlice<'data, Endian>>, +) -> Option<Arc<gimli::Dwarf<EndianSlice<'data, Endian>>>> { + if let Some(dwp) = package.as_ref() { + if let Ok(Some(cu)) = dwp.find_cu(load.dwo_id, &load.parent) { + return Some(Arc::new(cu)); + } + } + + let mut path = PathBuf::new(); + if let Some(p) = load.comp_dir.as_ref() { + path.push(convert_path(p).ok()?); + } + + path.push(convert_path(load.path.as_ref()?).ok()?); + + if let Some(map_dwo) = super::mmap(&path) { + let map_dwo = stash.cache_mmap(map_dwo); + if let Some(dwo) = Object::parse(map_dwo) { + return gimli::Dwarf::load(|id| -> Result<_, ()> { + let data = id + .dwo_name() + .and_then(|name| dwo.section(stash, name)) + .unwrap_or(&[]); + Ok(EndianSlice::new(data, Endian)) + }) + .ok() + .map(|mut dwo_dwarf| { + dwo_dwarf.make_dwo(&load.parent); + Arc::new(dwo_dwarf) + }); + } + } + + None +} diff --git a/vendor/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs b/vendor/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs new file mode 100644 index 0000000..9f0304c --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs @@ -0,0 +1,71 @@ +// 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; +} + +fn infer_current_exe(base_addr: usize) -> OsString { + if let Ok(entries) = super::parse_running_mmaps::parse_maps() { + let opt_path = entries + .iter() + .find(|e| e.ip_matches(base_addr) && e.pathname().len() > 0) + .map(|e| e.pathname()) + .cloned(); + if let Some(path) = opt_path { + return path; + } + } + env::current_exe().map(|e| e.into()).unwrap_or_default() +} + +// `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 { + // The man page for dl_iterate_phdr says that the first object visited by + // callback is the main program; so the first time we encounter a + // nameless entry, we can assume its the main program and try to infer its path. + // After that, we cannot continue that assumption, and we use an empty string. + if libs.is_empty() { + infer_current_exe(info.dlpi_addr as usize) + } 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/vendor/backtrace/src/symbolize/gimli/libs_haiku.rs b/vendor/backtrace/src/symbolize/gimli/libs_haiku.rs new file mode 100644 index 0000000..87e023e --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli/libs_illumos.rs b/vendor/backtrace/src/symbolize/gimli/libs_illumos.rs new file mode 100644 index 0000000..e64975e --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli/libs_libnx.rs b/vendor/backtrace/src/symbolize/gimli/libs_libnx.rs new file mode 100644 index 0000000..93b5ba1 --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli/libs_macos.rs b/vendor/backtrace/src/symbolize/gimli/libs_macos.rs new file mode 100644 index 0000000..438bbff --- /dev/null +++ b/vendor/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 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/vendor/backtrace/src/symbolize/gimli/libs_windows.rs b/vendor/backtrace/src/symbolize/gimli/libs_windows.rs new file mode 100644 index 0000000..b47ed42 --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli/macho.rs b/vendor/backtrace/src/symbolize/gimli/macho.rs new file mode 100644 index 0000000..74ed809 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/macho.rs @@ -0,0 +1,333 @@ +use super::{gimli, Box, Context, Endian, EndianSlice, Mapping, Path, Stash, Vec}; +use alloc::sync::Arc; +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 macOS is so different we just have a completely + // different implementation of the function here. On macOS 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, 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, 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".") + && §ion_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, 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..])) +} + +pub(super) fn handle_split_dwarf<'data>( + _package: Option<&gimli::DwarfPackage<EndianSlice<'data, Endian>>>, + _stash: &'data Stash, + _load: addr2line::SplitDwarfLoad<EndianSlice<'data, Endian>>, +) -> Option<Arc<gimli::Dwarf<EndianSlice<'data, Endian>>>> { + None +} diff --git a/vendor/backtrace/src/symbolize/gimli/mmap_fake.rs b/vendor/backtrace/src/symbolize/gimli/mmap_fake.rs new file mode 100644 index 0000000..ce50964 --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/gimli/mmap_unix.rs b/vendor/backtrace/src/symbolize/gimli/mmap_unix.rs new file mode 100644 index 0000000..261ffc1 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/mmap_unix.rs @@ -0,0 +1,49 @@ +use super::mystd::fs::File; +use super::mystd::os::unix::prelude::*; +use core::ops::Deref; +use core::ptr; +use core::slice; + +#[cfg(not(all(target_os = "linux", target_env = "gnu")))] +use libc::mmap as mmap64; +#[cfg(all(target_os = "linux", target_env = "gnu"))] +use libc::mmap64; + +pub struct Mmap { + ptr: *mut libc::c_void, + len: usize, +} + +impl Mmap { + pub unsafe fn map(file: &File, len: usize) -> Option<Mmap> { + let ptr = mmap64( + 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/vendor/backtrace/src/symbolize/gimli/mmap_windows.rs b/vendor/backtrace/src/symbolize/gimli/mmap_windows.rs new file mode 100644 index 0000000..b39509d --- /dev/null +++ b/vendor/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 being 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/vendor/backtrace/src/symbolize/gimli/parse_running_mmaps_unix.rs b/vendor/backtrace/src/symbolize/gimli/parse_running_mmaps_unix.rs new file mode 100644 index 0000000..5d4b346 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/parse_running_mmaps_unix.rs @@ -0,0 +1,295 @@ +// Note: This file is only currently used on targets that call out to the code +// in `mod libs_dl_iterate_phdr` (e.g. linux, freebsd, ...); it may be more +// general purpose, but it hasn't been tested elsewhere. + +use super::mystd::fs::File; +use super::mystd::io::Read; +use super::mystd::str::FromStr; +use super::{OsString, String, Vec}; + +#[derive(PartialEq, Eq, Debug)] +pub(super) struct MapsEntry { + /// start (inclusive) and limit (exclusive) of address range. + address: (usize, usize), + /// The perms field are the permissions for the entry + /// + /// r = read + /// w = write + /// x = execute + /// s = shared + /// p = private (copy on write) + perms: [char; 4], + /// Offset into the file (or "whatever"). + offset: usize, + /// device (major, minor) + dev: (usize, usize), + /// inode on the device. 0 indicates that no inode is associated with the memory region (e.g. uninitalized data aka BSS). + inode: usize, + /// Usually the file backing the mapping. + /// + /// Note: The man page for proc includes a note about "coordination" by + /// using readelf to see the Offset field in ELF program headers. pnkfelix + /// is not yet sure if that is intended to be a comment on pathname, or what + /// form/purpose such coordination is meant to have. + /// + /// There are also some pseudo-paths: + /// "[stack]": The initial process's (aka main thread's) stack. + /// "[stack:<tid>]": a specific thread's stack. (This was only present for a limited range of Linux verisons; it was determined to be too expensive to provide.) + /// "[vdso]": Virtual dynamically linked shared object + /// "[heap]": The process's heap + /// + /// The pathname can be blank, which means it is an anonymous mapping + /// obtained via mmap. + /// + /// Newlines in pathname are replaced with an octal escape sequence. + /// + /// The pathname may have "(deleted)" appended onto it if the file-backed + /// path has been deleted. + /// + /// Note that modifications like the latter two indicated above imply that + /// in general the pathname may be ambiguous. (I.e. you cannot tell if the + /// denoted filename actually ended with the text "(deleted)", or if that + /// was added by the maps rendering. + pathname: OsString, +} + +pub(super) fn parse_maps() -> Result<Vec<MapsEntry>, &'static str> { + let mut v = Vec::new(); + let mut proc_self_maps = + File::open("/proc/self/maps").map_err(|_| "Couldn't open /proc/self/maps")?; + let mut buf = String::new(); + let _bytes_read = proc_self_maps + .read_to_string(&mut buf) + .map_err(|_| "Couldn't read /proc/self/maps")?; + for line in buf.lines() { + v.push(line.parse()?); + } + + Ok(v) +} + +impl MapsEntry { + pub(super) fn pathname(&self) -> &OsString { + &self.pathname + } + + pub(super) fn ip_matches(&self, ip: usize) -> bool { + self.address.0 <= ip && ip < self.address.1 + } +} + +impl FromStr for MapsEntry { + type Err = &'static str; + + // Format: address perms offset dev inode pathname + // e.g.: "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" + // e.g.: "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + // e.g.: "35b1a21000-35b1a22000 rw-p 00000000 00:00 0" + // + // Note that paths may contain spaces, so we can't use `str::split` for parsing (until + // Split::remainder is stabilized #77998). + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (range_str, s) = s.trim_start().split_once(' ').unwrap_or((s, "")); + if range_str.is_empty() { + return Err("Couldn't find address"); + } + + let (perms_str, s) = s.trim_start().split_once(' ').unwrap_or((s, "")); + if perms_str.is_empty() { + return Err("Couldn't find permissions"); + } + + let (offset_str, s) = s.trim_start().split_once(' ').unwrap_or((s, "")); + if offset_str.is_empty() { + return Err("Couldn't find offset"); + } + + let (dev_str, s) = s.trim_start().split_once(' ').unwrap_or((s, "")); + if dev_str.is_empty() { + return Err("Couldn't find dev"); + } + + let (inode_str, s) = s.trim_start().split_once(' ').unwrap_or((s, "")); + if inode_str.is_empty() { + return Err("Couldn't find inode"); + } + + // Pathname may be omitted in which case it will be empty + let pathname_str = s.trim_start(); + + let hex = |s| usize::from_str_radix(s, 16).map_err(|_| "Couldn't parse hex number"); + let address = if let Some((start, limit)) = range_str.split_once('-') { + (hex(start)?, hex(limit)?) + } else { + return Err("Couldn't parse address range"); + }; + let perms: [char; 4] = { + let mut chars = perms_str.chars(); + let mut c = || chars.next().ok_or("insufficient perms"); + let perms = [c()?, c()?, c()?, c()?]; + if chars.next().is_some() { + return Err("too many perms"); + } + perms + }; + let offset = hex(offset_str)?; + let dev = if let Some((major, minor)) = dev_str.split_once(':') { + (hex(major)?, hex(minor)?) + } else { + return Err("Couldn't parse dev"); + }; + let inode = hex(inode_str)?; + let pathname = pathname_str.into(); + + Ok(MapsEntry { + address, + perms, + offset, + dev, + inode, + pathname, + }) + } +} + +// Make sure we can parse 64-bit sample output if we're on a 64-bit target. +#[cfg(target_pointer_width = "64")] +#[test] +fn check_maps_entry_parsing_64bit() { + assert_eq!( + "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 \ + [vsyscall]" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xffffffffff600000, 0xffffffffff601000), + perms: ['-', '-', 'x', 'p'], + offset: 0x00000000, + dev: (0x00, 0x00), + inode: 0x0, + pathname: "[vsyscall]".into(), + } + ); + + assert_eq!( + "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795 \ + /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0x7f5985f46000, 0x7f5985f48000), + perms: ['r', 'w', '-', 'p'], + offset: 0x00039000, + dev: (0x103, 0x06), + inode: 0x76021795, + pathname: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2".into(), + } + ); + assert_eq!( + "35b1a21000-35b1a22000 rw-p 00000000 00:00 0" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0x35b1a21000, 0x35b1a22000), + perms: ['r', 'w', '-', 'p'], + offset: 0x00000000, + dev: (0x00, 0x00), + inode: 0x0, + pathname: Default::default(), + } + ); +} + +// (This output was taken from a 32-bit machine, but will work on any target) +#[test] +fn check_maps_entry_parsing_32bit() { + /* Example snippet of output: + 08056000-08077000 rw-p 00000000 00:00 0 [heap] + b7c79000-b7e02000 r--p 00000000 08:01 60662705 /usr/lib/locale/locale-archive + b7e02000-b7e03000 rw-p 00000000 00:00 0 + */ + assert_eq!( + "08056000-08077000 rw-p 00000000 00:00 0 \ + [heap]" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0x08056000, 0x08077000), + perms: ['r', 'w', '-', 'p'], + offset: 0x00000000, + dev: (0x00, 0x00), + inode: 0x0, + pathname: "[heap]".into(), + } + ); + + assert_eq!( + "b7c79000-b7e02000 r--p 00000000 08:01 60662705 \ + /usr/lib/locale/locale-archive" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xb7c79000, 0xb7e02000), + perms: ['r', '-', '-', 'p'], + offset: 0x00000000, + dev: (0x08, 0x01), + inode: 0x60662705, + pathname: "/usr/lib/locale/locale-archive".into(), + } + ); + assert_eq!( + "b7e02000-b7e03000 rw-p 00000000 00:00 0" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xb7e02000, 0xb7e03000), + perms: ['r', 'w', '-', 'p'], + offset: 0x00000000, + dev: (0x00, 0x00), + inode: 0x0, + pathname: Default::default(), + } + ); + assert_eq!( + "b7c79000-b7e02000 r--p 00000000 08:01 60662705 \ + /executable/path/with some spaces" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xb7c79000, 0xb7e02000), + perms: ['r', '-', '-', 'p'], + offset: 0x00000000, + dev: (0x08, 0x01), + inode: 0x60662705, + pathname: "/executable/path/with some spaces".into(), + } + ); + assert_eq!( + "b7c79000-b7e02000 r--p 00000000 08:01 60662705 \ + /executable/path/with multiple-continuous spaces " + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xb7c79000, 0xb7e02000), + perms: ['r', '-', '-', 'p'], + offset: 0x00000000, + dev: (0x08, 0x01), + inode: 0x60662705, + pathname: "/executable/path/with multiple-continuous spaces ".into(), + } + ); + assert_eq!( + " b7c79000-b7e02000 r--p 00000000 08:01 60662705 \ + /executable/path/starts-with-spaces" + .parse::<MapsEntry>() + .unwrap(), + MapsEntry { + address: (0xb7c79000, 0xb7e02000), + perms: ['r', '-', '-', 'p'], + offset: 0x00000000, + dev: (0x08, 0x01), + inode: 0x60662705, + pathname: "/executable/path/starts-with-spaces".into(), + } + ); +} diff --git a/vendor/backtrace/src/symbolize/gimli/stash.rs b/vendor/backtrace/src/symbolize/gimli/stash.rs new file mode 100644 index 0000000..792f9a6 --- /dev/null +++ b/vendor/backtrace/src/symbolize/gimli/stash.rs @@ -0,0 +1,50 @@ +// 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>>>, + mmaps: UnsafeCell<Vec<Mmap>>, +} + +impl Stash { + pub fn new() -> Stash { + Stash { + buffers: UnsafeCell::new(Vec::new()), + mmaps: UnsafeCell::new(Vec::new()), + } + } + + /// 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 cache_mmap(&self, map: Mmap) -> &[u8] { + // SAFETY: this is the only location for a mutable pointer to + // `mmaps`, and this structure isn't threadsafe to shared across + // threads either. We also never remove elements from `self.mmaps`, + // so a reference to the data inside the map will live as long as + // `self` does. + unsafe { + let mmaps = &mut *self.mmaps.get(); + mmaps.push(map); + mmaps.last().unwrap() + } + } +} diff --git a/vendor/backtrace/src/symbolize/miri.rs b/vendor/backtrace/src/symbolize/miri.rs new file mode 100644 index 0000000..5b0dc30 --- /dev/null +++ b/vendor/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/vendor/backtrace/src/symbolize/mod.rs b/vendor/backtrace/src/symbolize/mod.rs new file mode 100644 index 0000000..a7c1995 --- /dev/null +++ b/vendor/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, all(windows, target_env = "gnu")), + 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/vendor/backtrace/src/symbolize/noop.rs b/vendor/backtrace/src/symbolize/noop.rs new file mode 100644 index 0000000..c533365 --- /dev/null +++ b/vendor/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() {} |