macho: Implement parsing of LC_FUNCTION_STARTS data (#814)

The data consists entirely of a list of uleb128 address deltas.
This commit is contained in:
Jez Ng
2025-10-28 23:12:56 -04:00
committed by GitHub
parent 4001fd5552
commit d152afe385
8 changed files with 142 additions and 1 deletions
+7
View File
@@ -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"),
+27
View File
@@ -265,6 +265,7 @@ struct MachState<'a> {
symbols: Vec<Option<&'a [u8]>>,
sections: Vec<Vec<u8>>,
section_index: usize,
text_segment_addr: u64,
}
fn print_macho<Mach: MachHeader<Endian = Endianness>>(
@@ -287,6 +288,9 @@ fn print_macho<Mach: MachHeader<Endian = Endianness>>(
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<Mach: MachHeader>(
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::<Mach>(p, endian, state, x);
});
}
LoadCommandVariant::EncryptionInfo32(x) => {
@@ -1309,3 +1314,25 @@ const FLAGS_X86_64_RELOC: &[Flag<u8>] = &flags!(
X86_64_RELOC_SIGNED_4,
X86_64_RELOC_TLV,
);
fn print_function_starts<Mach: MachHeader>(
p: &mut Printer<'_>,
endian: Mach::Endian,
state: &MachState,
linkedit: &LinkeditDataCommand<Mach::Endian>,
) {
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));
}
});
}
+3
View File
@@ -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,
@@ -257,6 +257,9 @@ LinkeditDataCommand {
CmdSize: 0x10
DataOffset: 0x8090
DataSize: 0x8
FunctionStarts {
Address: 0x100003F68
}
}
LinkeditDataCommand {
Cmd: LC_DATA_IN_CODE (0x29)
@@ -335,6 +335,9 @@ LinkeditDataCommand {
CmdSize: 0x10
DataOffset: 0xC060
DataSize: 0x8
FunctionStarts {
Address: 0x100003F60
}
}
LinkeditDataCommand {
Cmd: LC_DATA_IN_CODE (0x29)
+46
View File
@@ -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<u64>;
fn next(&mut self) -> Option<Self::Item> {
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))
}
}
+50 -1
View File
@@ -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<E: Endian> macho::SymtabCommand<E> {
}
}
impl<E: Endian> macho::LinkeditDataCommand<E> {
/// 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<FunctionStartsIterator<'data>> {
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());
}
}
+3
View File
@@ -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::*;