diff --git a/crates/examples/src/bin/dyldcachedump.rs b/crates/examples/src/bin/dyldcachedump.rs index 7d328710..36745048 100644 --- a/crates/examples/src/bin/dyldcachedump.rs +++ b/crates/examples/src/bin/dyldcachedump.rs @@ -1,4 +1,5 @@ -use object::read::macho::DyldCache; +use object::macho::DyldCacheHeader; +use object::read::macho::{DyldCache, DyldSubCacheSlice}; use object::Endianness; use std::{env, fs, process}; @@ -22,7 +23,6 @@ fn main() { continue; } }; - let subcache_files = open_subcaches_if_exist(&file_path); let file = match unsafe { memmap2::Mmap::map(&file) } { Ok(mmap) => mmap, Err(err) => { @@ -30,13 +30,27 @@ fn main() { continue; } }; + + let subcaches_info = match get_subcache_info(&file) { + Ok(subcaches_info) => subcaches_info, + Err(err) => { + println!( + "Failed to parse Dyld shared cache file '{}': {}", + file_path, err, + ); + continue; + } + }; + let subcache_files = subcaches_info + .map(|info| open_subcaches(&file_path, info)) + .unwrap_or_default(); let subcache_files: Option> = subcache_files .into_iter() .map( |subcache_file| match unsafe { memmap2::Mmap::map(&subcache_file) } { Ok(mmap) => Some(mmap), Err(err) => { - eprintln!("Failed to map file '{}': {}", file_path, err); + println!("Failed to map file '{}': {}", file_path, err); None } }, @@ -69,28 +83,47 @@ fn main() { } } +/// Gets the slice of subcache info structs from the header of the main cache. +fn get_subcache_info( + main_cache_data: &[u8], +) -> object::read::Result>> { + let header = DyldCacheHeader::::parse(main_cache_data)?; + let (_arch, endian) = header.parse_magic()?; + let subcaches_info = header.subcaches(endian, main_cache_data)?; + Ok(subcaches_info) +} + // If the file is a dyld shared cache, and we're on macOS 12 or later, // then there will be one or more "subcache" files next to this file, // with the names filename.1, filename.2, ..., filename.symbols -// or filename.01, filename.02 on macOS 13 -fn open_subcaches_if_exist(path: &str) -> Vec { +// or filename.01, filename.02, ..., filename.symbols on macOS 13 +fn open_subcaches(path: &str, subcaches_info: DyldSubCacheSlice) -> Vec { + let subcache_suffixes: Vec = match subcaches_info { + DyldSubCacheSlice::V1(subcaches) => { + // macOS 12: Subcaches have the file suffixes .1, .2, .3 etc. + (1..subcaches.len() + 1).map(|i| format!(".{i}")).collect() + } + DyldSubCacheSlice::V2(subcaches) => { + // macOS 13+: The subcache file suffix is written down in the header of the main cache. + subcaches + .iter() + .map(|s| { + // The suffix is a nul-terminated string in a fixed-size byte array. + let suffix = s.file_suffix; + let len = suffix.iter().position(|&c| c == 0).unwrap_or(suffix.len()); + String::from_utf8_lossy(&suffix[..len]).to_string() + }) + .collect() + } + }; let mut files = Vec::new(); - for i in 1.. { - let subcache_path = format!("{}.{}", path, i); + for suffix in subcache_suffixes { + let subcache_path = format!("{path}{suffix}"); match fs::File::open(subcache_path) { Ok(subcache_file) => files.push(subcache_file), Err(_) => break, }; } - if files.is_empty() { - for i in 1.. { - let subcache_path = format!("{}.{:02}", path, i); - match fs::File::open(subcache_path) { - Ok(subcache_file) => files.push(subcache_file), - Err(_) => break, - }; - } - } let symbols_subcache_path = format!("{}.symbols", path); if let Ok(subcache_file) = fs::File::open(symbols_subcache_path) { files.push(subcache_file); diff --git a/crates/examples/src/bin/objdump.rs b/crates/examples/src/bin/objdump.rs index 72e00b02..71da21f2 100644 --- a/crates/examples/src/bin/objdump.rs +++ b/crates/examples/src/bin/objdump.rs @@ -1,3 +1,4 @@ +use object::{macho::DyldCacheHeader, read::macho::DyldSubCacheSlice, Endianness}; use object_examples::objdump; use std::{env, fs, io, process}; @@ -18,7 +19,6 @@ fn main() { process::exit(1); } }; - let extra_files = open_subcaches_if_exist(&file_path); let file = match unsafe { memmap2::Mmap::map(&file) } { Ok(mmap) => mmap, Err(err) => { @@ -26,6 +26,10 @@ fn main() { process::exit(1); } }; + let subcaches_info = get_subcache_info_if_dyld_cache(&file).ok().flatten(); + let extra_files = subcaches_info + .map(|info| open_subcaches(&file_path, info)) + .unwrap_or_default(); let extra_files: Vec<_> = extra_files .into_iter() .map( @@ -52,17 +56,44 @@ fn main() { .unwrap(); } +/// Gets the slice of subcache info structs from the header of the main cache, +/// if `main_cache_data` is the data of a Dyld shared cache. +fn get_subcache_info_if_dyld_cache( + main_cache_data: &[u8], +) -> object::read::Result>> { + let header = DyldCacheHeader::::parse(main_cache_data)?; + let (_arch, endian) = header.parse_magic()?; + let subcaches_info = header.subcaches(endian, main_cache_data)?; + Ok(subcaches_info) +} + // If the file is a dyld shared cache, and we're on macOS 12 or later, // then there will be one or more "subcache" files next to this file, -// with the names filename.1, filename.2 etc. -// Read those files now, if they exist, even if we don't know that -// we're dealing with a dyld shared cache. By the time we know what -// we're dealing with, it's too late to read more files. -fn open_subcaches_if_exist(path: &str) -> Vec { +// with the names filename.1, filename.2, ..., filename.symbols +// or filename.01, filename.02, ..., filename.symbols on macOS 13 +fn open_subcaches(path: &str, subcaches_info: DyldSubCacheSlice) -> Vec { + let subcache_suffixes: Vec = match subcaches_info { + DyldSubCacheSlice::V1(subcaches) => { + // macOS 12: Subcaches have the file suffixes .1, .2, .3 etc. + (1..subcaches.len() + 1).map(|i| format!(".{i}")).collect() + } + DyldSubCacheSlice::V2(subcaches) => { + // macOS 13+: The subcache file suffix is written down in the header of the main cache. + subcaches + .iter() + .map(|s| { + // The suffix is a nul-terminated string in a fixed-size byte array. + let suffix = s.file_suffix; + let len = suffix.iter().position(|&c| c == 0).unwrap_or(suffix.len()); + String::from_utf8_lossy(&suffix[..len]).to_string() + }) + .collect() + } + }; let mut files = Vec::new(); - for i in 1.. { - let subcache_path = format!("{}.{}", path, i); - match fs::File::open(&subcache_path) { + for suffix in subcache_suffixes { + let subcache_path = format!("{path}{suffix}"); + match fs::File::open(subcache_path) { Ok(subcache_file) => files.push(subcache_file), Err(_) => break, }; @@ -71,5 +102,6 @@ fn open_subcaches_if_exist(path: &str) -> Vec { if let Ok(subcache_file) = fs::File::open(symbols_subcache_path) { files.push(subcache_file); }; + println!("Found {} subcache files", files.len()); files } diff --git a/crates/examples/src/objdump.rs b/crates/examples/src/objdump.rs index 33a5e084..31c6b16d 100644 --- a/crates/examples/src/objdump.rs +++ b/crates/examples/src/objdump.rs @@ -60,7 +60,7 @@ pub fn print( let path = match image.path() { Ok(path) => path, Err(err) => { - writeln!(e, "Failed to parse dydld image name: {}", err)?; + writeln!(e, "Failed to parse dyld image name: {}", err)?; continue; } };