use super::{Box, Context, Mapping, Path, Stash, Vec}; use core::convert::TryInto; use object::macho; use object::read::macho::{MachHeader, Nlist, Section, Segment as _}; use object::{Bytes, NativeEndian}; #[cfg(target_pointer_width = "32")] type Mach = object::macho::MachHeader32; #[cfg(target_pointer_width = "64")] type Mach = object::macho::MachHeader64; type MachSegment = ::Segment; type MachSection = ::Section; type MachNlist = ::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 { // First up we need to load the unique UUID which is stored in the macho // header of the file we're reading, specified at `path`. let map = super::mmap(path)?; let (macho, data) = find_header(&map)?; let endian = macho.endian().ok()?; let uuid = macho.uuid(endian, data, 0).ok()?; // Next we need to look for a `*.dSYM` file. For now we just probe the // containing directory and look around for something that matches // `*.dSYM`. Once it's found we root through the dwarf resources that it // contains and try to find a macho file which has a matching UUID as // the one of our own file. If we find a match that's the dwarf file we // want to return. if let Some(uuid) = uuid { if let Some(parent) = path.parent() { if let Some(mapping) = Mapping::load_dsym(parent, uuid) { return Some(mapping); } } } // Looks like nothing matched our UUID, so let's at least return our own // file. This should have the symbol table for at least some // symbolication purposes. Mapping::mk(map, |data, stash| { let (macho, data) = find_header(data)?; let endian = macho.endian().ok()?; let obj = Object::parse(macho, endian, data)?; Context::new(stash, obj, None) }) } fn load_dsym(dir: &Path, uuid: [u8; 16]) -> Option { 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 { // Look for files in the `DWARF` directory which have a matching uuid to // the original object file. If we find one then we found the debug // information. for entry in dir.read_dir().ok()? { let entry = entry.ok()?; let map = super::mmap(&entry.path())?; let candidate = Mapping::mk(map, |data, stash| { let (macho, data) = find_header(data)?; let endian = macho.endian().ok()?; let entry_uuid = macho.uuid(endian, data, 0).ok()??; if entry_uuid != uuid { return None; } let obj = Object::parse(macho, endian, data)?; Context::new(stash, obj, None) }); if let Some(candidate) = candidate { return Some(candidate); } } None } } fn find_header(data: &'_ [u8]) -> Option<(&'_ Mach, &'_ [u8])> { use object::endian::BigEndian; let desired_cpu = || { if cfg!(target_arch = "x86") { Some(macho::CPU_TYPE_X86) } else if cfg!(target_arch = "x86_64") { Some(macho::CPU_TYPE_X86_64) } else if cfg!(target_arch = "arm") { Some(macho::CPU_TYPE_ARM) } else if cfg!(target_arch = "aarch64") { Some(macho::CPU_TYPE_ARM64) } else { None } }; let mut data = Bytes(data); match data .clone() .read::>() .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::().ok()?; let nfat = header.nfat_arch.get(endian); let arch = (0..nfat) .filter_map(|_| header_data.read::().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::().ok()?; let nfat = header.nfat_arch.get(endian); let arch = (0..nfat) .filter_map(|_| header_data.read::().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>, // The outer Option is for lazy loading, and the inner Option allows load errors to be cached. object_mappings: Box<[Option>]>, } impl<'a> Object<'a> { fn parse(mach: &'a Mach, endian: NativeEndian, data: &'a [u8]) -> Option> { 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::(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 { use super::mystd::ffi::OsStr; use super::mystd::os::unix::prelude::*; let map; // `N_OSO` symbol names can be either `/path/to/object.o` or `/path/to/archive.a(object.o)`. let member_name = if let Some((archive_path, member_name)) = split_archive_path(path) { map = super::mmap(Path::new(OsStr::from_bytes(archive_path)))?; Some(member_name) } else { map = super::mmap(Path::new(OsStr::from_bytes(path)))?; None }; Mapping::mk(map, |data, stash| { let data = match member_name { Some(member_name) => { let archive = object::read::archive::ArchiveFile::parse(data).ok()?; let member = archive .members() .filter_map(Result::ok) .find(|m| m.name() == member_name)?; member.data(data).ok()? } None => data, }; let (macho, data) = find_header(data)?; let endian = macho.endian().ok()?; let obj = Object::parse(macho, endian, data)?; Context::new(stash, obj, None) }) } fn split_archive_path(path: &[u8]) -> Option<(&[u8], &[u8])> { let (last, path) = path.split_last()?; if *last != b')' { return None; } let index = path.iter().position(|&x| x == b'(')?; let (archive, rest) = path.split_at(index); Some((archive, &rest[1..])) }