use super::bytes::Source;
use super::chained::ChainedFixups;
use super::commands::{BuildVersion, DylibCommand, DylibLoadKind, PackedVersion};
use super::consts::{
LC_DYLD_CHAINED_FIXUPS, LC_DYLD_EXPORTS_TRIE, LC_DYLD_INFO, LC_DYLD_INFO_ONLY, LC_ID_DYLIB,
LC_RPATH, LC_SUB_CLIENT, LC_SUB_FRAMEWORK, LC_SYMTAB, MH_DYLIB, MH_DYLIB_STUB, MH_EXECUTE,
MH_NO_REEXPORTED_DYLIBS,
};
use super::file::{MachHeader, MachOFile};
use super::symbol::SymbolTable;
use super::trie::ExportTrieIter;
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DylibDependency<'a> {
pub kind: DylibLoadKind,
pub name: &'a [u8],
pub current_version: PackedVersion,
pub compatibility_version: PackedVersion,
}
#[derive(Clone, Debug)]
pub struct Dylib<'a> {
file: MachOFile<'a>,
id: Option<DylibCommand<'a>>,
dependencies: Vec<DylibDependency<'a>>,
rpaths: Vec<&'a [u8]>,
umbrella: Option<&'a [u8]>,
allowable_clients: Vec<&'a [u8]>,
build_versions: Vec<BuildVersion<'a>>,
exports_trie: (&'a [u8], u64),
chained_fixups: Option<(&'a [u8], u64)>,
symbols: SymbolTable<'a>,
}
impl<'a> Dylib<'a> {
pub fn parse(data: &'a [u8], source: Source<'a>) -> Result<Self> {
Self::parse_kinds(data, source, &[MH_DYLIB, MH_DYLIB_STUB])
}
pub fn parse_image(data: &'a [u8], source: Source<'a>) -> Result<Self> {
Self::parse_kinds(data, source, &[MH_DYLIB, MH_DYLIB_STUB, MH_EXECUTE])
}
fn parse_kinds(data: &'a [u8], source: Source<'a>, kinds: &[u32]) -> Result<Self> {
let file = MachOFile::parse(data, source)?;
if !kinds.contains(&file.header().file_type) {
return Err(source.malformed(12, "Mach-O file type (expected MH_DYLIB)"));
}
let mut dylib = Self {
file,
id: None,
dependencies: Vec::new(),
rpaths: Vec::new(),
umbrella: None,
allowable_clients: Vec::new(),
build_versions: Vec::new(),
exports_trie: (&[], 0),
chained_fixups: None,
symbols: SymbolTable::empty(file.endian(), file.is64(), source),
};
let mut trie_from_exports_command = false;
for command in file.load_commands() {
let command = command?;
match command.cmd {
LC_ID_DYLIB => {
if dylib.id.is_none() {
dylib.id = Some(command.dylib()?);
}
}
cmd if DylibLoadKind::from_cmd(cmd).is_some() => {
let dependency = command.dylib()?;
dylib.dependencies.push(DylibDependency {
kind: DylibLoadKind::from_cmd(cmd).unwrap_or(DylibLoadKind::Regular),
name: dependency.name,
current_version: dependency.current_version,
compatibility_version: dependency.compatibility_version,
});
}
LC_RPATH => dylib.rpaths.push(command.string()?),
LC_SUB_FRAMEWORK => dylib.umbrella = Some(command.string()?),
LC_SUB_CLIENT => dylib.allowable_clients.push(command.string()?),
LC_DYLD_INFO | LC_DYLD_INFO_ONLY => {
let info = command.dyld_info()?;
if !trie_from_exports_command {
dylib.exports_trie = (
file.bytes(
u64::from(info.export_off),
u64::from(info.export_size),
"export trie",
)?,
u64::from(info.export_off),
);
}
}
LC_DYLD_EXPORTS_TRIE => {
let info = command.linkedit_data()?;
trie_from_exports_command = true;
dylib.exports_trie = (
file.bytes(
u64::from(info.dataoff),
u64::from(info.datasize),
"export trie",
)?,
u64::from(info.dataoff),
);
}
LC_DYLD_CHAINED_FIXUPS => {
let info = command.linkedit_data()?;
dylib.chained_fixups = Some((
file.bytes(
u64::from(info.dataoff),
u64::from(info.datasize),
"chained fixups",
)?,
u64::from(info.dataoff),
));
}
LC_SYMTAB => {
let symtab = command.symtab()?;
let entry = if file.is64() { 16 } else { 12 };
let records = file.bytes(
u64::from(symtab.symoff),
u64::from(symtab.nsyms).saturating_mul(entry),
"symbol table",
)?;
let strtab = file.bytes(
u64::from(symtab.stroff),
u64::from(symtab.strsize),
"string table",
)?;
dylib.symbols = SymbolTable::new(
records,
strtab,
u64::from(symtab.symoff),
file.endian(),
file.is64(),
source,
);
}
_ if command.is_version() => {
dylib.build_versions.push(command.build_version()?);
}
_ => {}
}
}
if file.header().file_type == MH_DYLIB && dylib.id.is_none() {
return Err(source.malformed(0, "dylib (no LC_ID_DYLIB)"));
}
Ok(dylib)
}
#[must_use]
pub fn file(&self) -> &MachOFile<'a> {
&self.file
}
#[must_use]
pub fn header(&self) -> &MachHeader {
self.file.header()
}
#[must_use]
pub fn id(&self) -> Option<&DylibCommand<'a>> {
self.id.as_ref()
}
#[must_use]
pub fn install_name(&self) -> &'a [u8] {
self.id.map_or(&[], |id| id.name)
}
#[must_use]
pub fn dependencies(&self) -> &[DylibDependency<'a>] {
&self.dependencies
}
pub fn reexports(&self) -> impl Iterator<Item = &DylibDependency<'a>> + '_ {
self.dependencies
.iter()
.filter(|d| d.kind == DylibLoadKind::Reexport)
}
#[must_use]
pub fn no_reexported_dylibs(&self) -> bool {
self.header().flags & MH_NO_REEXPORTED_DYLIBS != 0
}
#[must_use]
pub fn rpaths(&self) -> &[&'a [u8]] {
&self.rpaths
}
#[must_use]
pub fn umbrella(&self) -> Option<&'a [u8]> {
self.umbrella
}
#[must_use]
pub fn allowable_clients(&self) -> &[&'a [u8]] {
&self.allowable_clients
}
#[must_use]
pub fn build_versions(&self) -> &[BuildVersion<'a>] {
&self.build_versions
}
#[must_use]
pub fn exports_trie(&self) -> &'a [u8] {
self.exports_trie.0
}
#[must_use]
pub fn exports(&self) -> ExportTrieIter<'a> {
ExportTrieIter::new(self.exports_trie.0, self.exports_trie.1, self.file.source())
}
#[must_use]
pub fn symbols(&self) -> &SymbolTable<'a> {
&self.symbols
}
pub fn chained_fixups(&self) -> Result<Option<ChainedFixups<'a>>> {
self.chained_fixups
.map(|(data, offset)| {
ChainedFixups::parse(data, offset, self.file.endian(), self.file.source())
})
.transpose()
}
}