diff --git a/crates/examples/src/bin/readobj.rs b/crates/examples/src/bin/readobj.rs index c30e9e1..1ab3308 100644 --- a/crates/examples/src/bin/readobj.rs +++ b/crates/examples/src/bin/readobj.rs @@ -82,6 +82,12 @@ fn main() { .action(ArgAction::SetTrue) .help("Print the Mach-O load commands"), ) + .arg( + Arg::new("macho-function-starts") + .long("macho-function-starts") + .action(ArgAction::SetTrue) + .help("Print the Mach-O function start addresses"), + ) .arg( Arg::new("pe-rich") .long("pe-rich") @@ -130,6 +136,7 @@ fn main() { elf_notes: matches.get_flag("elf-notes"), elf_versions: matches.get_flag("elf-version-info"), elf_attributes: matches.get_flag("elf-attributes"), + macho_function_starts: matches.get_flag("macho-function-starts"), macho_load_commands: matches.get_flag("macho-load-commands"), pe_rich: matches.get_flag("pe-rich"), pe_base_relocs: matches.get_flag("pe-base-relocs"), diff --git a/crates/examples/src/readobj/macho.rs b/crates/examples/src/readobj/macho.rs index 116f996..6ecc234 100644 --- a/crates/examples/src/readobj/macho.rs +++ b/crates/examples/src/readobj/macho.rs @@ -265,6 +265,7 @@ struct MachState<'a> { symbols: Vec>, sections: Vec>, section_index: usize, + text_segment_addr: u64, } fn print_macho>( @@ -287,6 +288,9 @@ fn print_macho>( let mut symtab_command = None; while let Ok(Some(command)) = commands.next() { if let Ok(Some((segment, section_data))) = Mach::Segment::from_command(command) { + if segment.name() == macho::SEG_TEXT.as_bytes() { + state.text_segment_addr = segment.vmaddr(endian).into(); + } if let Some(cache) = cache { // The symbol table will be in the linkedit segment, but that may be in a // different subcache, so we need to remember the data for that subcache. @@ -577,6 +581,7 @@ fn print_load_command( p.field_hex("CmdSize", x.cmdsize.get(endian)); p.field_hex("DataOffset", x.dataoff.get(endian)); p.field_hex("DataSize", x.datasize.get(endian)); + print_function_starts::(p, endian, state, x); }); } LoadCommandVariant::EncryptionInfo32(x) => { @@ -1309,3 +1314,25 @@ const FLAGS_X86_64_RELOC: &[Flag] = &flags!( X86_64_RELOC_SIGNED_4, X86_64_RELOC_TLV, ); + +fn print_function_starts( + p: &mut Printer<'_>, + endian: Mach::Endian, + state: &MachState, + linkedit: &LinkeditDataCommand, +) { + if !p.options.macho_function_starts || linkedit.cmd.get(endian) != macho::LC_FUNCTION_STARTS { + return; + } + let Some(function_starts) = linkedit + .function_starts(endian, state.linkedit_data, state.text_segment_addr) + .print_err(p) + else { + return; + }; + p.group("FunctionStarts", |p| { + for addr in function_starts { + addr.print_err(p).map(|addr| p.field_hex("Address", addr)); + } + }); +} diff --git a/crates/examples/src/readobj/mod.rs b/crates/examples/src/readobj/mod.rs index 5b94c31..0a295b6 100644 --- a/crates/examples/src/readobj/mod.rs +++ b/crates/examples/src/readobj/mod.rs @@ -23,6 +23,7 @@ pub struct PrintOptions { // Mach-O specific selectors pub macho_load_commands: bool, + pub macho_function_starts: bool, // PE specific selectors pub pe_rich: bool, @@ -50,6 +51,7 @@ impl PrintOptions { elf_versions: true, elf_attributes: true, macho_load_commands: true, + macho_function_starts: true, pe_rich: true, pe_base_relocs: true, pe_imports: true, @@ -73,6 +75,7 @@ impl PrintOptions { elf_versions: false, elf_attributes: false, macho_load_commands: false, + macho_function_starts: false, pe_rich: false, pe_base_relocs: false, pe_imports: false, diff --git a/crates/examples/testfiles/macho/base-aarch64.readobj b/crates/examples/testfiles/macho/base-aarch64.readobj index 420d182..0a45594 100644 --- a/crates/examples/testfiles/macho/base-aarch64.readobj +++ b/crates/examples/testfiles/macho/base-aarch64.readobj @@ -257,6 +257,9 @@ LinkeditDataCommand { CmdSize: 0x10 DataOffset: 0x8090 DataSize: 0x8 + FunctionStarts { + Address: 0x100003F68 + } } LinkeditDataCommand { Cmd: LC_DATA_IN_CODE (0x29) diff --git a/crates/examples/testfiles/macho/base-x86_64.readobj b/crates/examples/testfiles/macho/base-x86_64.readobj index f7a6e5e..4253672 100644 --- a/crates/examples/testfiles/macho/base-x86_64.readobj +++ b/crates/examples/testfiles/macho/base-x86_64.readobj @@ -335,6 +335,9 @@ LinkeditDataCommand { CmdSize: 0x10 DataOffset: 0xC060 DataSize: 0x8 + FunctionStarts { + Address: 0x100003F60 + } } LinkeditDataCommand { Cmd: LC_DATA_IN_CODE (0x29) diff --git a/src/read/macho/function_starts.rs b/src/read/macho/function_starts.rs new file mode 100644 index 0000000..6ba7651 --- /dev/null +++ b/src/read/macho/function_starts.rs @@ -0,0 +1,46 @@ +use crate::read::{Bytes, Error, Result}; + +/// Iterator over the function starts in a `LC_FUNCTION_STARTS` load command. +#[derive(Debug, Default, Clone, Copy)] +pub struct FunctionStartsIterator<'data> { + data: Bytes<'data>, + addr: u64, +} + +impl<'data> FunctionStartsIterator<'data> { + pub(super) fn new(data: &'data [u8], addr: u64) -> Self { + FunctionStartsIterator { + data: Bytes(data), + addr, + } + } +} + +impl<'data> Iterator for FunctionStartsIterator<'data> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + + let delta = match self.data.read_uleb128() { + Ok(0) => return None, + Ok(delta) => delta, + Err(()) => { + self.data = Bytes(&[]); + return Some(Err(Error("Invalid ULEB128 in LC_FUNCTION_STARTS"))); + } + }; + + self.addr = match self.addr.checked_add(delta) { + Some(addr) => addr, + None => { + self.data = Bytes(&[]); + return Some(Err(Error("Address overflow in LC_FUNCTION_STARTS"))); + } + }; + + Some(Ok(self.addr)) + } +} diff --git a/src/read/macho/load_command.rs b/src/read/macho/load_command.rs index be690db..f064a8f 100644 --- a/src/read/macho/load_command.rs +++ b/src/read/macho/load_command.rs @@ -4,7 +4,7 @@ use core::mem; use crate::endian::Endian; use crate::macho; use crate::pod::Pod; -use crate::read::macho::{MachHeader, SymbolTable}; +use crate::read::macho::{FunctionStartsIterator, MachHeader, SymbolTable}; use crate::read::{Bytes, Error, ReadError, ReadRef, Result, StringTable}; /// An iterator for the load commands from a [`MachHeader`]. @@ -381,6 +381,32 @@ impl macho::SymtabCommand { } } +impl macho::LinkeditDataCommand { + /// Return an iterator over the function start addresses. + /// + /// Only works if the command is a `LC_FUNCTION_STARTS` command. + /// + /// # Arguments + /// * `text_segment_addr` - The VM address of the __TEXT segment. + pub fn function_starts<'data, R: ReadRef<'data>>( + &self, + endian: E, + data: R, + text_segment_addr: u64, + ) -> Result> { + if self.cmd.get(endian) != macho::LC_FUNCTION_STARTS { + return Err(Error("Not a function starts command")); + } + let data = data + .read_bytes_at( + self.dataoff.get(endian).into(), + self.datasize.get(endian).into(), + ) + .read_error("Invalid function starts offset or size")?; + Ok(FunctionStartsIterator::new(data, text_segment_addr)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -399,4 +425,27 @@ mod tests { LoadCommandIterator::new(LittleEndian, &Align([0, 0, 0, 0, 8, 0, 0, 0, 0]).0, 10); assert!(commands.next().is_ok()); } + + #[test] + fn function_starts_invalid_uleb128() { + use crate::endian::U32; + use crate::macho; + + // Invalid ULEB128: continuation bit set but no following byte + let data = [0x80]; + + let cmd = macho::LinkeditDataCommand { + cmd: U32::new(LittleEndian, macho::LC_FUNCTION_STARTS), + cmdsize: U32::new(LittleEndian, 16), + dataoff: U32::new(LittleEndian, 0), + datasize: U32::new(LittleEndian, data.len() as u32), + }; + + let mut iter = cmd.function_starts(LittleEndian, &data[..], 0).unwrap(); + + // First call returns error + assert!(iter.next().unwrap().is_err()); + // Second call returns None (iterator exhausted) + assert!(iter.next().is_none()); + } } diff --git a/src/read/macho/mod.rs b/src/read/macho/mod.rs index ab51ff3..8a8a4ef 100644 --- a/src/read/macho/mod.rs +++ b/src/read/macho/mod.rs @@ -56,6 +56,9 @@ pub use fat::*; mod file; pub use file::*; +mod function_starts; +pub use function_starts::*; + mod load_command; pub use load_command::*;