readobj/macho: fix symbol table in dyld cache

The symbol table data is stored in the linkedit segment,
but that segment may be in a different subcache from the
one containing the image headers. This occurs for the
arm dyld cache in macOS 13.

MachOFile::parse_dyld_cache_image already handled this
correctly, but readobj doesn't use that.
This commit is contained in:
Philip Craig
2025-06-01 14:16:33 +10:00
parent a149a2b811
commit fd0e96500a
3 changed files with 58 additions and 37 deletions
+56 -17
View File
@@ -112,15 +112,23 @@ pub(super) fn print_dyld_cache_images(p: &mut Printer<'_>, cache: &DyldCache) {
});
}
if let Some((data, offset)) = image.image_data_and_offset().print_err(p) {
if p.options.file {
p.blank();
}
print_object_at(p, data, offset);
print_dyld_cache_image(p, data, offset, cache);
p.blank();
}
}
}
fn print_dyld_cache_image(p: &mut Printer<'_>, data: &[u8], offset: u64, cache: &DyldCache) {
let Some(kind) = object::FileKind::parse_at(data, offset).print_err(p) else {
return;
};
match kind {
object::FileKind::MachO32 => macho::print_macho32(p, data, offset, Some(cache)),
object::FileKind::MachO64 => macho::print_macho64(p, data, offset, Some(cache)),
_ => writeln!(p.w(), "Format: {:?}", kind).unwrap(),
}
}
pub(super) fn print_macho_fat32(p: &mut Printer<'_>, data: &[u8]) {
if let Some(fat) = MachOFatFile32::parse(data).print_err(p) {
writeln!(p.w(), "Format: Mach-O Fat 32-bit").unwrap();
@@ -175,23 +183,34 @@ pub(super) fn print_fat_arch<Arch: FatArch>(p: &mut Printer<'_>, arch: &Arch) {
});
}
pub(super) fn print_macho32(p: &mut Printer<'_>, data: &[u8], offset: u64) {
pub(super) fn print_macho32(
p: &mut Printer<'_>,
data: &[u8],
offset: u64,
cache: Option<&DyldCache>,
) {
if let Some(header) = MachHeader32::parse(data, offset).print_err(p) {
writeln!(p.w(), "Format: Mach-O 32-bit").unwrap();
print_macho(p, header, data, offset);
print_macho(p, header, data, offset, cache);
}
}
pub(super) fn print_macho64(p: &mut Printer<'_>, data: &[u8], offset: u64) {
pub(super) fn print_macho64(
p: &mut Printer<'_>,
data: &[u8],
offset: u64,
cache: Option<&DyldCache>,
) {
if let Some(header) = MachHeader64::parse(data, offset).print_err(p) {
writeln!(p.w(), "Format: Mach-O 64-bit").unwrap();
print_macho(p, header, data, offset);
print_macho(p, header, data, offset, cache);
}
}
#[derive(Default)]
struct MachState<'a> {
cputype: u32,
linkedit_data: &'a [u8],
symbols: Vec<Option<&'a [u8]>>,
sections: Vec<Vec<u8>>,
section_index: usize,
@@ -202,16 +221,33 @@ fn print_macho<Mach: MachHeader<Endian = Endianness>>(
header: &Mach,
data: &[u8],
offset: u64,
cache: Option<&DyldCache>,
) {
if let Some(endian) = header.endian().print_err(p) {
let mut state = MachState {
cputype: header.cputype(endian),
linkedit_data: data,
// Dummy first entry because section index starts at 1.
sections: vec![vec![]],
..MachState::default()
};
if let Ok(mut commands) = header.load_commands(endian, data, 0) {
// Scan the load commands for info that we need to reference during parsing.
if let Ok(mut commands) = header.load_commands(endian, data, offset) {
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 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.
// TODO: this logic should be in the object crate somehow. It already
// exists there for MachOFile but we're not using that here.
if segment.name() == macho::SEG_LINKEDIT.as_bytes() {
let addr = segment.vmaddr(endian).into();
if let Some((data, _offset)) = cache.data_and_offset_for_address(addr) {
state.linkedit_data = data;
}
}
}
if let Ok(segment_sections) = segment.sections(endian, section_data) {
state
.sections
@@ -224,13 +260,16 @@ fn print_macho<Mach: MachHeader<Endian = Endianness>>(
}));
}
} else if let Ok(Some(command)) = command.symtab() {
if let Ok(symtab) = command.symbols::<Mach, _>(endian, data) {
state.symbols.extend(
symtab
.iter()
.map(|symbol| symbol.name(endian, symtab.strings()).ok()),
);
}
symtab_command = Some(command);
}
}
if let Some(symtab_command) = symtab_command {
if let Ok(symtab) = symtab_command.symbols::<Mach, _>(endian, state.linkedit_data) {
state.symbols.extend(
symtab
.iter()
.map(|symbol| symbol.name(endian, symtab.strings()).ok()),
);
}
}
}
@@ -286,7 +325,7 @@ fn print_load_command<Mach: MachHeader>(
print_segment(p, endian, data, segment, section_data, state);
}
LoadCommandVariant::Symtab(symtab) => {
print_symtab::<Mach>(p, endian, data, symtab, state);
print_symtab::<Mach>(p, endian, state.linkedit_data, symtab, state);
}
_ => {}
}
+2 -18
View File
@@ -290,8 +290,8 @@ fn print_object(p: &mut Printer<'_>, data: &[u8], extra_files: &[&[u8]]) {
object::FileKind::DyldCache => macho::print_dyld_cache(p, data, extra_files),
object::FileKind::Elf32 => elf::print_elf32(p, data),
object::FileKind::Elf64 => elf::print_elf64(p, data),
object::FileKind::MachO32 => macho::print_macho32(p, data, 0),
object::FileKind::MachO64 => macho::print_macho64(p, data, 0),
object::FileKind::MachO32 => macho::print_macho32(p, data, 0, None),
object::FileKind::MachO64 => macho::print_macho64(p, data, 0, None),
object::FileKind::MachOFat32 => macho::print_macho_fat32(p, data),
object::FileKind::MachOFat64 => macho::print_macho_fat64(p, data),
object::FileKind::Pe32 => pe::print_pe32(p, data),
@@ -303,22 +303,6 @@ fn print_object(p: &mut Printer<'_>, data: &[u8], extra_files: &[&[u8]]) {
}
}
fn print_object_at(p: &mut Printer<'_>, data: &[u8], offset: u64) {
let kind = match object::FileKind::parse_at(data, offset) {
Ok(file) => file,
Err(err) => {
println!("Failed to parse file: {}", err);
return;
}
};
match kind {
object::FileKind::MachO32 => macho::print_macho32(p, data, offset),
object::FileKind::MachO64 => macho::print_macho64(p, data, offset),
// TODO
_ => {}
}
}
fn print_archive(p: &mut Printer<'_>, data: &[u8]) {
if let Some(archive) = ArchiveFile::parse(data).print_err(p) {
write!(p.w(), "Format: Archive ({:?})", archive.kind()).unwrap();
-2
View File
@@ -195,8 +195,6 @@ impl<'data, E: Endian> LoadCommandData<'data, E> {
}
/// Try to parse this command as a [`macho::SymtabCommand`].
///
/// Returns the segment command and the data containing the sections.
pub fn symtab(self) -> Result<Option<&'data macho::SymtabCommand<E>>> {
if self.cmd == macho::LC_SYMTAB {
Some(self.data()).transpose()