use core::marker::PhantomData;
use core::mem;
use crate::endian::Endian;
use crate::macho;
use crate::pod::Pod;
use crate::read::macho::{MachHeader, SymbolTable};
use crate::read::{Bytes, Error, ReadError, ReadRef, Result, StringTable};
#[derive(Debug, Default, Clone, Copy)]
pub struct LoadCommandIterator<'data, E: Endian> {
endian: E,
data: Bytes<'data>,
ncmds: u32,
}
impl<'data, E: Endian> LoadCommandIterator<'data, E> {
pub(super) fn new(endian: E, data: &'data [u8], ncmds: u32) -> Self {
LoadCommandIterator {
endian,
data: Bytes(data),
ncmds,
}
}
pub fn next(&mut self) -> Result<Option<LoadCommandData<'data, E>>> {
if self.ncmds == 0 {
return Ok(None);
}
let result = self.parse().map(Some);
if result.is_err() {
self.ncmds = 0;
} else {
self.ncmds -= 1;
}
result
}
fn parse(&mut self) -> Result<LoadCommandData<'data, E>> {
let header = self
.data
.read_at::<macho::LoadCommand<E>>(0)
.read_error("Invalid Mach-O load command header")?;
let cmd = header.cmd.get(self.endian);
let cmdsize = header.cmdsize.get(self.endian) as usize;
if cmdsize < mem::size_of::<macho::LoadCommand<E>>() {
return Err(Error("Invalid Mach-O load command size"));
}
let data = self
.data
.read_bytes(cmdsize)
.read_error("Invalid Mach-O load command size")?;
Ok(LoadCommandData {
cmd,
data,
marker: Default::default(),
})
}
}
impl<'data, E: Endian> Iterator for LoadCommandIterator<'data, E> {
type Item = Result<LoadCommandData<'data, E>>;
fn next(&mut self) -> Option<Self::Item> {
self.next().transpose()
}
}
#[derive(Debug, Clone, Copy)]
pub struct LoadCommandData<'data, E: Endian> {
cmd: u32,
data: Bytes<'data>,
marker: PhantomData<E>,
}
impl<'data, E: Endian> LoadCommandData<'data, E> {
pub fn cmd(&self) -> u32 {
self.cmd
}
pub fn cmdsize(&self) -> u32 {
self.data.len() as u32
}
#[inline]
pub fn data<T: Pod>(&self) -> Result<&'data T> {
self.data
.read_at(0)
.read_error("Invalid Mach-O command size")
}
pub fn raw_data(&self) -> &'data [u8] {
self.data.0
}
pub fn string(&self, endian: E, s: macho::LcStr<E>) -> Result<&'data [u8]> {
self.data
.read_string_at(s.offset.get(endian) as usize)
.read_error("Invalid load command string offset")
}
pub fn variant(&self) -> Result<LoadCommandVariant<'data, E>> {
Ok(match self.cmd {
macho::LC_SEGMENT => {
let mut data = self.data;
let segment = data.read().read_error("Invalid Mach-O command size")?;
LoadCommandVariant::Segment32(segment, data.0)
}
macho::LC_SYMTAB => LoadCommandVariant::Symtab(self.data()?),
macho::LC_THREAD | macho::LC_UNIXTHREAD => {
let mut data = self.data;
let thread = data.read().read_error("Invalid Mach-O command size")?;
LoadCommandVariant::Thread(thread, data.0)
}
macho::LC_DYSYMTAB => LoadCommandVariant::Dysymtab(self.data()?),
macho::LC_LOAD_DYLIB
| macho::LC_LOAD_WEAK_DYLIB
| macho::LC_REEXPORT_DYLIB
| macho::LC_LAZY_LOAD_DYLIB
| macho::LC_LOAD_UPWARD_DYLIB => LoadCommandVariant::Dylib(self.data()?),
macho::LC_ID_DYLIB => LoadCommandVariant::IdDylib(self.data()?),
macho::LC_LOAD_DYLINKER => LoadCommandVariant::LoadDylinker(self.data()?),
macho::LC_ID_DYLINKER => LoadCommandVariant::IdDylinker(self.data()?),
macho::LC_PREBOUND_DYLIB => LoadCommandVariant::PreboundDylib(self.data()?),
macho::LC_ROUTINES => LoadCommandVariant::Routines32(self.data()?),
macho::LC_SUB_FRAMEWORK => LoadCommandVariant::SubFramework(self.data()?),
macho::LC_SUB_UMBRELLA => LoadCommandVariant::SubUmbrella(self.data()?),
macho::LC_SUB_CLIENT => LoadCommandVariant::SubClient(self.data()?),
macho::LC_SUB_LIBRARY => LoadCommandVariant::SubLibrary(self.data()?),
macho::LC_TWOLEVEL_HINTS => LoadCommandVariant::TwolevelHints(self.data()?),
macho::LC_PREBIND_CKSUM => LoadCommandVariant::PrebindCksum(self.data()?),
macho::LC_SEGMENT_64 => {
let mut data = self.data;
let segment = data.read().read_error("Invalid Mach-O command size")?;
LoadCommandVariant::Segment64(segment, data.0)
}
macho::LC_ROUTINES_64 => LoadCommandVariant::Routines64(self.data()?),
macho::LC_UUID => LoadCommandVariant::Uuid(self.data()?),
macho::LC_RPATH => LoadCommandVariant::Rpath(self.data()?),
macho::LC_CODE_SIGNATURE
| macho::LC_SEGMENT_SPLIT_INFO
| macho::LC_FUNCTION_STARTS
| macho::LC_DATA_IN_CODE
| macho::LC_DYLIB_CODE_SIGN_DRS
| macho::LC_LINKER_OPTIMIZATION_HINT
| macho::LC_DYLD_EXPORTS_TRIE
| macho::LC_DYLD_CHAINED_FIXUPS => LoadCommandVariant::LinkeditData(self.data()?),
macho::LC_ENCRYPTION_INFO => LoadCommandVariant::EncryptionInfo32(self.data()?),
macho::LC_DYLD_INFO | macho::LC_DYLD_INFO_ONLY => {
LoadCommandVariant::DyldInfo(self.data()?)
}
macho::LC_VERSION_MIN_MACOSX
| macho::LC_VERSION_MIN_IPHONEOS
| macho::LC_VERSION_MIN_TVOS
| macho::LC_VERSION_MIN_WATCHOS => LoadCommandVariant::VersionMin(self.data()?),
macho::LC_DYLD_ENVIRONMENT => LoadCommandVariant::DyldEnvironment(self.data()?),
macho::LC_MAIN => LoadCommandVariant::EntryPoint(self.data()?),
macho::LC_SOURCE_VERSION => LoadCommandVariant::SourceVersion(self.data()?),
macho::LC_ENCRYPTION_INFO_64 => LoadCommandVariant::EncryptionInfo64(self.data()?),
macho::LC_LINKER_OPTION => LoadCommandVariant::LinkerOption(self.data()?),
macho::LC_NOTE => LoadCommandVariant::Note(self.data()?),
macho::LC_BUILD_VERSION => LoadCommandVariant::BuildVersion(self.data()?),
macho::LC_FILESET_ENTRY => LoadCommandVariant::FilesetEntry(self.data()?),
_ => LoadCommandVariant::Other,
})
}
pub fn segment_32(self) -> Result<Option<(&'data macho::SegmentCommand32<E>, &'data [u8])>> {
if self.cmd == macho::LC_SEGMENT {
let mut data = self.data;
let segment = data.read().read_error("Invalid Mach-O command size")?;
Ok(Some((segment, data.0)))
} else {
Ok(None)
}
}
pub fn symtab(self) -> Result<Option<&'data macho::SymtabCommand<E>>> {
if self.cmd == macho::LC_SYMTAB {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn dysymtab(self) -> Result<Option<&'data macho::DysymtabCommand<E>>> {
if self.cmd == macho::LC_DYSYMTAB {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn dylib(self) -> Result<Option<&'data macho::DylibCommand<E>>> {
if self.cmd == macho::LC_LOAD_DYLIB
|| self.cmd == macho::LC_LOAD_WEAK_DYLIB
|| self.cmd == macho::LC_REEXPORT_DYLIB
|| self.cmd == macho::LC_LAZY_LOAD_DYLIB
|| self.cmd == macho::LC_LOAD_UPWARD_DYLIB
{
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn uuid(self) -> Result<Option<&'data macho::UuidCommand<E>>> {
if self.cmd == macho::LC_UUID {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn segment_64(self) -> Result<Option<(&'data macho::SegmentCommand64<E>, &'data [u8])>> {
if self.cmd == macho::LC_SEGMENT_64 {
let mut data = self.data;
let command = data.read().read_error("Invalid Mach-O command size")?;
Ok(Some((command, data.0)))
} else {
Ok(None)
}
}
pub fn dyld_info(self) -> Result<Option<&'data macho::DyldInfoCommand<E>>> {
if self.cmd == macho::LC_DYLD_INFO || self.cmd == macho::LC_DYLD_INFO_ONLY {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn entry_point(self) -> Result<Option<&'data macho::EntryPointCommand<E>>> {
if self.cmd == macho::LC_MAIN {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
pub fn build_version(self) -> Result<Option<&'data macho::BuildVersionCommand<E>>> {
if self.cmd == macho::LC_BUILD_VERSION {
Some(self.data()).transpose()
} else {
Ok(None)
}
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum LoadCommandVariant<'data, E: Endian> {
Segment32(&'data macho::SegmentCommand32<E>, &'data [u8]),
Symtab(&'data macho::SymtabCommand<E>),
Thread(&'data macho::ThreadCommand<E>, &'data [u8]),
Dysymtab(&'data macho::DysymtabCommand<E>),
Dylib(&'data macho::DylibCommand<E>),
IdDylib(&'data macho::DylibCommand<E>),
LoadDylinker(&'data macho::DylinkerCommand<E>),
IdDylinker(&'data macho::DylinkerCommand<E>),
PreboundDylib(&'data macho::PreboundDylibCommand<E>),
Routines32(&'data macho::RoutinesCommand32<E>),
SubFramework(&'data macho::SubFrameworkCommand<E>),
SubUmbrella(&'data macho::SubUmbrellaCommand<E>),
SubClient(&'data macho::SubClientCommand<E>),
SubLibrary(&'data macho::SubLibraryCommand<E>),
TwolevelHints(&'data macho::TwolevelHintsCommand<E>),
PrebindCksum(&'data macho::PrebindCksumCommand<E>),
Segment64(&'data macho::SegmentCommand64<E>, &'data [u8]),
Routines64(&'data macho::RoutinesCommand64<E>),
Uuid(&'data macho::UuidCommand<E>),
Rpath(&'data macho::RpathCommand<E>),
LinkeditData(&'data macho::LinkeditDataCommand<E>),
EncryptionInfo32(&'data macho::EncryptionInfoCommand32<E>),
DyldInfo(&'data macho::DyldInfoCommand<E>),
VersionMin(&'data macho::VersionMinCommand<E>),
DyldEnvironment(&'data macho::DylinkerCommand<E>),
EntryPoint(&'data macho::EntryPointCommand<E>),
SourceVersion(&'data macho::SourceVersionCommand<E>),
EncryptionInfo64(&'data macho::EncryptionInfoCommand64<E>),
LinkerOption(&'data macho::LinkerOptionCommand<E>),
Note(&'data macho::NoteCommand<E>),
BuildVersion(&'data macho::BuildVersionCommand<E>),
FilesetEntry(&'data macho::FilesetEntryCommand<E>),
Other,
}
impl<E: Endian> macho::SymtabCommand<E> {
pub fn symbols<'data, Mach: MachHeader<Endian = E>, R: ReadRef<'data>>(
&self,
endian: E,
data: R,
) -> Result<SymbolTable<'data, Mach, R>> {
let symbols = data
.read_slice_at(
self.symoff.get(endian).into(),
self.nsyms.get(endian) as usize,
)
.read_error("Invalid Mach-O symbol table offset or size")?;
let str_start: u64 = self.stroff.get(endian).into();
let str_end = str_start
.checked_add(self.strsize.get(endian).into())
.read_error("Invalid Mach-O string table length")?;
let strings = StringTable::new(data, str_start, str_end);
Ok(SymbolTable::new(symbols, strings))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::LittleEndian;
#[test]
fn cmd_size_invalid() {
#[repr(align(16))]
struct Align<const N: usize>([u8; N]);
let mut commands = LoadCommandIterator::new(LittleEndian, &Align([0; 8]).0, 10);
assert!(commands.next().is_err());
let mut commands =
LoadCommandIterator::new(LittleEndian, &Align([0, 0, 0, 0, 7, 0, 0, 0, 0]).0, 10);
assert!(commands.next().is_err());
let mut commands =
LoadCommandIterator::new(LittleEndian, &Align([0, 0, 0, 0, 8, 0, 0, 0, 0]).0, 10);
assert!(commands.next().is_ok());
}
}