mirror of
https://github.com/encounter/object.git
synced 2026-07-10 12:18:39 -07:00
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:
@@ -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"),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user