use alloc::vec::Vec; use core::fmt::Debug; use core::{fmt, slice, str}; use crate::endian::{self, Endianness}; use crate::macho; use crate::pod::Pod; use crate::read::util::StringTable; use crate::read::{ self, ObjectMap, ObjectMapEntry, ObjectSymbol, ObjectSymbolTable, ReadError, ReadRef, Result, SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapEntry, SymbolScope, SymbolSection, }; use super::{MachHeader, MachOFile}; /// A table of symbol entries in a Mach-O file. /// /// Also includes the string table used for the symbol names. /// /// Returned by [`macho::SymtabCommand::symbols`]. #[derive(Debug, Clone, Copy)] pub struct SymbolTable<'data, Mach: MachHeader, R = &'data [u8]> where R: ReadRef<'data>, { symbols: &'data [Mach::Nlist], strings: StringTable<'data, R>, } impl<'data, Mach: MachHeader, R: ReadRef<'data>> Default for SymbolTable<'data, Mach, R> { fn default() -> Self { SymbolTable { symbols: &[], strings: Default::default(), } } } impl<'data, Mach: MachHeader, R: ReadRef<'data>> SymbolTable<'data, Mach, R> { #[inline] pub(super) fn new(symbols: &'data [Mach::Nlist], strings: StringTable<'data, R>) -> Self { SymbolTable { symbols, strings } } /// Return the string table used for the symbol names. #[inline] pub fn strings(&self) -> StringTable<'data, R> { self.strings } /// Iterate over the symbols. #[inline] pub fn iter(&self) -> slice::Iter<'data, Mach::Nlist> { self.symbols.iter() } /// Return true if the symbol table is empty. #[inline] pub fn is_empty(&self) -> bool { self.symbols.is_empty() } /// The number of symbols. #[inline] pub fn len(&self) -> usize { self.symbols.len() } /// Return the symbol at the given index. pub fn symbol(&self, index: usize) -> Result<&'data Mach::Nlist> { self.symbols .get(index) .read_error("Invalid Mach-O symbol index") } /// Construct a map from addresses to a user-defined map entry. pub fn map Option>( &self, f: F, ) -> SymbolMap { let mut symbols = Vec::new(); for nlist in self.symbols { if !nlist.is_definition() { continue; } if let Some(entry) = f(nlist) { symbols.push(entry); } } SymbolMap::new(symbols) } /// Construct a map from addresses to symbol names and object file names. pub fn object_map(&self, endian: Mach::Endian) -> ObjectMap<'data> { let mut symbols = Vec::new(); let mut objects = Vec::new(); let mut object = None; let mut current_function = None; // Each module starts with one or two N_SO symbols (path, or directory + filename) // and one N_OSO symbol. The module is terminated by an empty N_SO symbol. for nlist in self.symbols { let n_type = nlist.n_type(); if n_type & macho::N_STAB == 0 { continue; } // TODO: includes variables too (N_GSYM, N_STSYM). These may need to get their // address from regular symbols though. match n_type { macho::N_SO => { object = None; } macho::N_OSO => { object = None; if let Ok(name) = nlist.name(endian, self.strings) { if !name.is_empty() { object = Some(objects.len()); objects.push(name); } } } macho::N_FUN => { if let Ok(name) = nlist.name(endian, self.strings) { if !name.is_empty() { current_function = Some((name, nlist.n_value(endian).into())) } else if let Some((name, address)) = current_function.take() { if let Some(object) = object { symbols.push(ObjectMapEntry { address, size: nlist.n_value(endian).into(), name, object, }); } } } } _ => {} } } ObjectMap { symbols: SymbolMap::new(symbols), objects, } } } /// A symbol table in a [`MachOFile32`](super::MachOFile32). pub type MachOSymbolTable32<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbolTable<'data, 'file, macho::MachHeader32, R>; /// A symbol table in a [`MachOFile64`](super::MachOFile64). pub type MachOSymbolTable64<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbolTable<'data, 'file, macho::MachHeader64, R>; /// A symbol table in a [`MachOFile`]. #[derive(Debug, Clone, Copy)] pub struct MachOSymbolTable<'data, 'file, Mach, R = &'data [u8]> where Mach: MachHeader, R: ReadRef<'data>, { pub(super) file: &'file MachOFile<'data, Mach, R>, } impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbolTable<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { } impl<'data, 'file, Mach, R> ObjectSymbolTable<'data> for MachOSymbolTable<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { type Symbol = MachOSymbol<'data, 'file, Mach, R>; type SymbolIterator = MachOSymbolIterator<'data, 'file, Mach, R>; fn symbols(&self) -> Self::SymbolIterator { MachOSymbolIterator { file: self.file, index: 0, } } fn symbol_by_index(&self, index: SymbolIndex) -> Result { let nlist = self.file.symbols.symbol(index.0)?; MachOSymbol::new(self.file, index, nlist).read_error("Unsupported Mach-O symbol index") } } /// An iterator for the symbols in a [`MachOFile32`](super::MachOFile32). pub type MachOSymbolIterator32<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbolIterator<'data, 'file, macho::MachHeader32, R>; /// An iterator for the symbols in a [`MachOFile64`](super::MachOFile64). pub type MachOSymbolIterator64<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbolIterator<'data, 'file, macho::MachHeader64, R>; /// An iterator for the symbols in a [`MachOFile`]. pub struct MachOSymbolIterator<'data, 'file, Mach, R = &'data [u8]> where Mach: MachHeader, R: ReadRef<'data>, { pub(super) file: &'file MachOFile<'data, Mach, R>, pub(super) index: usize, } impl<'data, 'file, Mach, R> fmt::Debug for MachOSymbolIterator<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MachOSymbolIterator").finish() } } impl<'data, 'file, Mach, R> Iterator for MachOSymbolIterator<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { type Item = MachOSymbol<'data, 'file, Mach, R>; fn next(&mut self) -> Option { loop { let index = self.index; let nlist = self.file.symbols.symbols.get(index)?; self.index += 1; if let Some(symbol) = MachOSymbol::new(self.file, SymbolIndex(index), nlist) { return Some(symbol); } } } } /// A symbol in a [`MachOFile32`](super::MachOFile32). pub type MachOSymbol32<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbol<'data, 'file, macho::MachHeader32, R>; /// A symbol in a [`MachOFile64`](super::MachOFile64). pub type MachOSymbol64<'data, 'file, Endian = Endianness, R = &'data [u8]> = MachOSymbol<'data, 'file, macho::MachHeader64, R>; /// A symbol in a [`MachOFile`]. /// /// Most functionality is provided by the [`ObjectSymbol`] trait implementation. #[derive(Debug, Clone, Copy)] pub struct MachOSymbol<'data, 'file, Mach, R = &'data [u8]> where Mach: MachHeader, R: ReadRef<'data>, { file: &'file MachOFile<'data, Mach, R>, index: SymbolIndex, nlist: &'data Mach::Nlist, } impl<'data, 'file, Mach, R> MachOSymbol<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { pub(super) fn new( file: &'file MachOFile<'data, Mach, R>, index: SymbolIndex, nlist: &'data Mach::Nlist, ) -> Option { if nlist.n_type() & macho::N_STAB != 0 { return None; } Some(MachOSymbol { file, index, nlist }) } } impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbol<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { } impl<'data, 'file, Mach, R> ObjectSymbol<'data> for MachOSymbol<'data, 'file, Mach, R> where Mach: MachHeader, R: ReadRef<'data>, { #[inline] fn index(&self) -> SymbolIndex { self.index } fn name_bytes(&self) -> Result<&'data [u8]> { self.nlist.name(self.file.endian, self.file.symbols.strings) } fn name(&self) -> Result<&'data str> { let name = self.name_bytes()?; str::from_utf8(name) .ok() .read_error("Non UTF-8 Mach-O symbol name") } #[inline] fn address(&self) -> u64 { self.nlist.n_value(self.file.endian).into() } #[inline] fn size(&self) -> u64 { 0 } fn kind(&self) -> SymbolKind { self.section() .index() .and_then(|index| self.file.section_internal(index).ok()) .map(|section| match section.kind { SectionKind::Text => SymbolKind::Text, SectionKind::Data | SectionKind::ReadOnlyData | SectionKind::ReadOnlyString | SectionKind::UninitializedData | SectionKind::Common => SymbolKind::Data, SectionKind::Tls | SectionKind::UninitializedTls | SectionKind::TlsVariables => { SymbolKind::Tls } _ => SymbolKind::Unknown, }) .unwrap_or(SymbolKind::Unknown) } fn section(&self) -> SymbolSection { match self.nlist.n_type() & macho::N_TYPE { macho::N_UNDF => SymbolSection::Undefined, macho::N_ABS => SymbolSection::Absolute, macho::N_SECT => { let n_sect = self.nlist.n_sect(); if n_sect != 0 { SymbolSection::Section(SectionIndex(n_sect as usize)) } else { SymbolSection::Unknown } } _ => SymbolSection::Unknown, } } #[inline] fn is_undefined(&self) -> bool { self.nlist.n_type() & macho::N_TYPE == macho::N_UNDF } #[inline] fn is_definition(&self) -> bool { self.nlist.is_definition() } #[inline] fn is_common(&self) -> bool { // Mach-O common symbols are based on section, not symbol false } #[inline] fn is_weak(&self) -> bool { self.nlist.n_desc(self.file.endian) & (macho::N_WEAK_REF | macho::N_WEAK_DEF) != 0 } fn scope(&self) -> SymbolScope { let n_type = self.nlist.n_type(); if n_type & macho::N_TYPE == macho::N_UNDF { SymbolScope::Unknown } else if n_type & macho::N_EXT == 0 { SymbolScope::Compilation } else if n_type & macho::N_PEXT != 0 { SymbolScope::Linkage } else { SymbolScope::Dynamic } } #[inline] fn is_global(&self) -> bool { self.scope() != SymbolScope::Compilation } #[inline] fn is_local(&self) -> bool { self.scope() == SymbolScope::Compilation } #[inline] fn flags(&self) -> SymbolFlags { let n_desc = self.nlist.n_desc(self.file.endian); SymbolFlags::MachO { n_desc } } } /// A trait for generic access to [`macho::Nlist32`] and [`macho::Nlist64`]. #[allow(missing_docs)] pub trait Nlist: Debug + Pod { type Word: Into; type Endian: endian::Endian; fn n_strx(&self, endian: Self::Endian) -> u32; fn n_type(&self) -> u8; fn n_sect(&self) -> u8; fn n_desc(&self, endian: Self::Endian) -> u16; fn n_value(&self, endian: Self::Endian) -> Self::Word; fn name<'data, R: ReadRef<'data>>( &self, endian: Self::Endian, strings: StringTable<'data, R>, ) -> Result<&'data [u8]> { strings .get(self.n_strx(endian)) .read_error("Invalid Mach-O symbol name offset") } /// Return true if this is a STAB symbol. /// /// This determines the meaning of the `n_type` field. fn is_stab(&self) -> bool { self.n_type() & macho::N_STAB != 0 } /// Return true if this is an undefined symbol. fn is_undefined(&self) -> bool { let n_type = self.n_type(); n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_UNDF } /// Return true if the symbol is a definition of a function or data object. fn is_definition(&self) -> bool { let n_type = self.n_type(); n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_SECT } /// Return the library ordinal. /// /// This is either a 1-based index into the dylib load commands, /// or a special ordinal. #[inline] fn library_ordinal(&self, endian: Self::Endian) -> u8 { (self.n_desc(endian) >> 8) as u8 } } impl Nlist for macho::Nlist32 { type Word = u32; type Endian = Endian; fn n_strx(&self, endian: Self::Endian) -> u32 { self.n_strx.get(endian) } fn n_type(&self) -> u8 { self.n_type } fn n_sect(&self) -> u8 { self.n_sect } fn n_desc(&self, endian: Self::Endian) -> u16 { self.n_desc.get(endian) } fn n_value(&self, endian: Self::Endian) -> Self::Word { self.n_value.get(endian) } } impl Nlist for macho::Nlist64 { type Word = u64; type Endian = Endian; fn n_strx(&self, endian: Self::Endian) -> u32 { self.n_strx.get(endian) } fn n_type(&self) -> u8 { self.n_type } fn n_sect(&self) -> u8 { self.n_sect } fn n_desc(&self, endian: Self::Endian) -> u16 { self.n_desc.get(endian) } fn n_value(&self, endian: Self::Endian) -> Self::Word { self.n_value.get(endian) } }