Skip to content

Commit

Permalink
Add MachOFile::parse_at_offset.
Browse files Browse the repository at this point in the history
This allows parsing Mach-O images inside dyld shared cache files (#268):
The dyld shared cache contains multiple images at different offsets; all these
images share the same address space for absolute offsets such as symoff. Due to
these absolute offsets, one cannot just parse the images by subsetting the input
slice and parsing at header offset zero.

This patch is a breaking change because it adds a header_offset argument to the
MachHeader methods load_commands and uuid, and MachHeader is part of the public API.
  • Loading branch information
mstange committed May 18, 2021
1 parent 0ec88f3 commit c6581fb
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 11 deletions.
2 changes: 1 addition & 1 deletion examples/readobj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3248,7 +3248,7 @@ mod macho {
if let Ok(endian) = header.endian() {
let mut state = MachState::default();
print_mach_header(p, endian, header);
if let Ok(mut commands) = header.load_commands(endian, data) {
if let Ok(mut commands) = header.load_commands(endian, data, 0) {
while let Ok(Some(command)) = commands.next() {
print_load_command(p, endian, data, header, command, &mut state);
}
Expand Down
46 changes: 36 additions & 10 deletions src/read/macho/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ where
{
pub(super) endian: Mach::Endian,
pub(super) data: R,
pub(super) header_offset: u64,
pub(super) header: &'data Mach,
pub(super) sections: Vec<MachOSectionInternal<'data, Mach>>,
pub(super) symbols: SymbolTable<'data, Mach>,
Expand All @@ -45,13 +46,21 @@ where
{
/// Parse the raw Mach-O file data.
pub fn parse(data: R) -> Result<Self> {
let header = Mach::parse(data)?;
Self::parse_at_offset(data, 0)
}

/// Parse the raw Mach-O file data at an arbitrary offset inside the input data.
/// This can be used for parsing Mach-O images inside the dyld shared cache,
/// where multiple images, located at different offsets, share the same address
/// space.
pub fn parse_at_offset(data: R, header_offset: u64) -> Result<Self> {
let header = Mach::parse_at_offset(data, header_offset)?;
let endian = header.endian()?;

let mut symbols = SymbolTable::default();
// Build a list of sections to make some operations more efficient.
let mut sections = Vec::new();
if let Ok(mut commands) = header.load_commands(endian, data) {
if let Ok(mut commands) = header.load_commands(endian, data, header_offset) {
while let Ok(Some(command)) = commands.next() {
if let Some((segment, section_data)) = Mach::Segment::from_command(command)? {
for section in segment.sections(endian, section_data)? {
Expand All @@ -67,6 +76,7 @@ where
Ok(MachOFile {
endian,
header,
header_offset,
sections,
symbols,
data,
Expand Down Expand Up @@ -137,7 +147,7 @@ where
file: self,
commands: self
.header
.load_commands(self.endian, self.data)
.load_commands(self.endian, self.data, self.header_offset)
.ok()
.unwrap_or_else(Default::default),
}
Expand Down Expand Up @@ -240,7 +250,9 @@ where
if twolevel {
libraries.push(&[][..]);
}
let mut commands = self.header.load_commands(self.endian, self.data)?;
let mut commands = self
.header
.load_commands(self.endian, self.data, self.header_offset)?;
while let Some(command) = commands.next()? {
if let Some(command) = command.dysymtab()? {
dysymtab = Some(command);
Expand Down Expand Up @@ -278,7 +290,9 @@ where

fn exports(&self) -> Result<Vec<Export<'data>>> {
let mut dysymtab = None;
let mut commands = self.header.load_commands(self.endian, self.data)?;
let mut commands = self
.header
.load_commands(self.endian, self.data, self.header_offset)?;
while let Some(command) = commands.next()? {
if let Some(command) = command.dysymtab()? {
dysymtab = Some(command);
Expand Down Expand Up @@ -313,11 +327,14 @@ where
}

fn mach_uuid(&self) -> Result<Option<[u8; 16]>> {
self.header.uuid(self.endian, self.data)
self.header.uuid(self.endian, self.data, self.header_offset)
}

fn entry(&self) -> u64 {
if let Ok(mut commands) = self.header.load_commands(self.endian, self.data) {
if let Ok(mut commands) =
self.header
.load_commands(self.endian, self.data, self.header_offset)
{
while let Ok(Some(command)) = commands.next() {
if let Ok(Some(command)) = command.entry_point() {
return command.entryoff.get(self.endian);
Expand Down Expand Up @@ -481,8 +498,15 @@ pub trait MachHeader: Debug + Pod {
///
/// Also checks that the magic field in the file header is a supported format.
fn parse<'data, R: ReadRef<'data>>(data: R) -> read::Result<&'data Self> {
Self::parse_at_offset(data, 0)
}

fn parse_at_offset<'data, R: ReadRef<'data>>(
data: R,
offset: u64,
) -> read::Result<&'data Self> {
let header = data
.read_at::<Self>(0)
.read_at::<Self>(offset)
.read_error("Invalid Mach-O header size or alignment")?;
if !header.is_supported() {
return Err(Error("Unsupported Mach-O header"));
Expand All @@ -502,10 +526,11 @@ pub trait MachHeader: Debug + Pod {
&self,
endian: Self::Endian,
data: R,
header_offset: u64,
) -> Result<LoadCommandIterator<'data, Self::Endian>> {
let data = data
.read_bytes_at(
mem::size_of::<Self>() as u64,
header_offset + mem::size_of::<Self>() as u64,
self.sizeofcmds(endian).into(),
)
.read_error("Invalid Mach-O load command table size")?;
Expand All @@ -517,8 +542,9 @@ pub trait MachHeader: Debug + Pod {
&self,
endian: Self::Endian,
data: R,
header_offset: u64,
) -> Result<Option<[u8; 16]>> {
let mut commands = self.load_commands(endian, data)?;
let mut commands = self.load_commands(endian, data, header_offset)?;
while let Some(command) = commands.next()? {
if let Ok(Some(uuid)) = command.uuid() {
return Ok(Some(uuid.uuid));
Expand Down

0 comments on commit c6581fb

Please sign in to comment.