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 = "ios",
target_os = "linux",
target_os = "macos",
target_os = "openbsd",
target_os = "solaris",
))] {
#[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 {
cx: Context<'static>,
_map: Mmap,
_stash: Stash,
}
impl Mapping {
fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
where
F: for<'a> Fn(&'a [u8], &'a Stash) -> Option<Context<'a>>,
{
let stash = Stash::new();
let cx = mk(&data, &stash)?;
Some(Mapping {
cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
_map: data,
_stash: stash,
})
}
}
struct Context<'a> {
dwarf: addr2line::Context<EndianSlice<'a, Endian>>,
object: Object<'a>,
}
impl<'data> Context<'data> {
fn new(stash: &'data Stash, object: Object<'data>) -> Option<Context<'data>> {
fn load_section<'data, S>(stash: &'data Stash, obj: &Object<'data>) -> S
where
S: gimli::Section<gimli::EndianSlice<'data, Endian>>,
{
let data = obj.section(stash, S::section_name()).unwrap_or(&[]);
S::from(EndianSlice::new(data, Endian))
}
let dwarf = addr2line::Context::from_sections(
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
load_section(stash, &object),
gimli::EndianSlice::new(&[], Endian),
)
.ok()?;
Some(Context { dwarf, object })
}
}
fn mmap(path: &Path) -> Option<Mmap> {
let file = File::open(path).ok()?;
let len = file.metadata().ok()?.len().try_into().ok()?;
unsafe { Mmap::map(&file, len) }
}
cfg_if::cfg_if! {
if #[cfg(windows)] {
use core::mem::MaybeUninit;
use super::super::windows::*;
use mystd::os::windows::prelude::*;
use alloc::vec;
mod coff;
use self::coff::Object;
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]);
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,
}],
})
}
} else if #[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
))] {
use mystd::os::unix::prelude::*;
use mystd::ffi::{OsStr, CStr};
mod macho;
use self::macho::Object;
#[allow(deprecated)]
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;
}
#[allow(deprecated)]
fn native_library(i: u32) -> Option<Library> {
use object::macho;
use object::read::macho::{MachHeader, Segment};
use object::{Bytes, NativeEndian};
let name = unsafe {
let name = libc::_dyld_get_image_name(i);
if name.is_null() {
return None;
}
CStr::from_ptr(name)
};
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, Bytes(data)).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, Bytes(data)).ok()?, endian)
}
_ => return None,
}
};
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()?,
});
}
}
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,
})
}
} else if #[cfg(all(
any(
target_os = "linux",
target_os = "fuchsia",
target_os = "freebsd",
),
not(target_env = "uclibc"),
))] {
use mystd::os::unix::prelude::*;
use mystd::ffi::{OsStr, CStr};
mod elf;
use self::elf::Object;
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;
}
unsafe extern "C" fn callback(
info: *mut libc::dl_phdr_info,
_size: libc::size_t,
vec: *mut libc::c_void,
) -> libc::c_int {
let info = &*info;
let libs = &mut *(vec as *mut Vec<Library>);
let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0;
let name = if is_main_prog {
if libs.is_empty() {
mystd::env::current_exe().map(|e| e.into()).unwrap_or_default()
} else {
OsString::new()
}
} else {
let bytes = CStr::from_ptr(info.dlpi_name).to_bytes();
OsStr::from_bytes(bytes).to_owned()
};
let headers = core::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
}
} else if #[cfg(target_env = "libnx")] {
mod elf;
use self::elf::Object;
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
}
} else {
use mystd::os::unix::prelude::*;
mod elf;
use self::elf::Object;
fn native_libraries() -> Vec<Library> {
Vec::new()
}
}
}
#[derive(Default)]
struct Cache {
libraries: Vec<Library>,
mappings: Vec<(usize, Mapping)>,
}
struct Library {
name: OsString,
segments: Vec<LibrarySegment>,
bias: usize,
}
struct LibrarySegment {
stated_virtual_memory_address: usize,
len: usize,
}
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 fn with_global(f: impl FnOnce(&mut Self)) {
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)| {
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;
}
let svma = (addr as usize).wrapping_sub(lib.bias);
Some((i, svma as *const u8))
})
.next()
}
fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a mut Context<'a>> {
let idx = self.mappings.iter().position(|(idx, _)| *idx == lib);
if let Some(idx) = idx {
if idx != 0 {
let entry = self.mappings.remove(idx);
self.mappings.insert(0, entry);
}
} else {
let name = &self.libraries[lib].name;
let mapping = Mapping::new(name.as_ref())?;
if self.mappings.len() == MAPPINGS_CACHE_SIZE {
self.mappings.pop();
}
self.mappings.insert(0, (lib, mapping));
}
let cx: &'a mut Context<'static> = &mut self.mappings[0].1.cx;
Some(unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) })
}
}
pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
let addr = what.address_or_ip();
let mut call = |sym: Symbol<'_>| {
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,
};
let cx = match cache.mapping_for_lib(lib) {
Some(cx) => cx,
None => return,
};
let mut any_frames = false;
if let Ok(mut frames) = cx.dwarf.find_frames(addr as u64) {
while let Ok(Some(frame)) = frames.next() {
any_frames = true;
let name = match frame.function {
Some(f) => Some(f.name.slice()),
None => cx.object.search_symtab(addr as u64),
};
call(Symbol::Frame {
addr: addr as *mut c_void,
location: frame.location,
name,
});
}
}
if !any_frames {
if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
if let Ok(mut frames) = object_cx.dwarf.find_frames(object_addr) {
while let Ok(Some(frame)) = frames.next() {
any_frames = true;
call(Symbol::Frame {
addr: addr as *mut c_void,
location: frame.location,
name: frame.function.map(|f| f.name.slice()),
});
}
}
}
}
if !any_frames {
if let Some(name) = cx.object.search_symtab(addr as u64) {
call(Symbol::Symtab {
addr: addr as *mut c_void,
name,
});
}
}
});
}
pub enum Symbol<'a> {
Frame {
addr: *mut c_void,
location: Option<addr2line::Location<'a>>,
name: Option<&'a [u8]>,
},
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,
}
}
}