Skip to content

Commit

Permalink
Make dyldcachedump and objdump load the correct dyld subcaches.
Browse files Browse the repository at this point in the history
dyldcachedump was working correctly on macOS 13+ because it was trying
the "leading zero" suffix format as well as the "no leading zero" suffix
format. This commit changes it to read the suffix from the main cache
header.

objdump was not able to parse dyld shared cache files on macOS 13+ because
it was only using the "no leading zero" suffix format, and thus not finding
the subcaches.
  • Loading branch information
mstange committed Mar 10, 2024
1 parent 1a1fab7 commit a24f21c
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 26 deletions.
65 changes: 49 additions & 16 deletions crates/examples/src/bin/dyldcachedump.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -22,21 +23,34 @@ 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) => {
println!("Failed to map file '{}': {}", file_path, err,);
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<Vec<_>> = 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
}
},
Expand Down Expand Up @@ -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<Option<DyldSubCacheSlice<'_, Endianness>>> {
let header = DyldCacheHeader::<Endianness>::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<fs::File> {
// or filename.01, filename.02, ..., filename.symbols on macOS 13
fn open_subcaches(path: &str, subcaches_info: DyldSubCacheSlice<Endianness>) -> Vec<fs::File> {
let subcache_suffixes: Vec<String> = 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);
Expand Down
50 changes: 41 additions & 9 deletions crates/examples/src/bin/objdump.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use object::{macho::DyldCacheHeader, read::macho::DyldSubCacheSlice, Endianness};
use object_examples::objdump;
use std::{env, fs, io, process};

Expand All @@ -18,14 +19,17 @@ 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) => {
eprintln!("Failed to map file '{}': {}", file_path, err,);
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(
Expand All @@ -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<Option<DyldSubCacheSlice<'_, Endianness>>> {
let header = DyldCacheHeader::<Endianness>::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<fs::File> {
// 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<Endianness>) -> Vec<fs::File> {
let subcache_suffixes: Vec<String> = 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,
};
Expand All @@ -71,5 +102,6 @@ fn open_subcaches_if_exist(path: &str) -> Vec<fs::File> {
if let Ok(subcache_file) = fs::File::open(symbols_subcache_path) {
files.push(subcache_file);
};
println!("Found {} subcache files", files.len());
files
}
2 changes: 1 addition & 1 deletion crates/examples/src/objdump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub fn print<W: Write, E: Write>(
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;
}
};
Expand Down

0 comments on commit a24f21c

Please sign in to comment.