Skip to content

Commit

Permalink
read/macho: add DyldCache::subcache_suffixes
Browse files Browse the repository at this point in the history
  • Loading branch information
philipc committed Jan 9, 2025
1 parent d4ebf2d commit e109535
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 153 deletions.
105 changes: 28 additions & 77 deletions crates/examples/src/bin/dyldcachedump.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use object::macho::DyldCacheHeader;
use object::read::macho::{DyldCache, DyldSubCacheSlice};
use object::read::macho::DyldCache;
use object::Endianness;
use std::{env, fs, process};

Expand All @@ -23,16 +22,17 @@ fn main() {
continue;
}
};
let file = match unsafe { memmap2::Mmap::map(&file) } {
let mmap = match unsafe { memmap2::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
println!("Failed to map file '{}': {}", file_path, err,);
continue;
}
};
let data = &*mmap;

let subcaches_info = match get_subcache_info(&file) {
Ok(subcaches_info) => subcaches_info,
let subcache_suffixes = match DyldCache::<Endianness>::subcache_suffixes(data) {
Ok(subcaches_suffixes) => subcaches_suffixes,
Err(err) => {
println!(
"Failed to parse Dyld shared cache file '{}': {}",
Expand All @@ -41,29 +41,33 @@ fn main() {
continue;
}
};
let subcache_files = subcaches_info
.map(|info| open_subcaches(&file_path, info))
.unwrap_or_default();
let subcache_files: Option<Vec<_>> = subcache_files
let Ok(subcache_files) = subcache_suffixes
.into_iter()
.map(
|subcache_file| match unsafe { memmap2::Mmap::map(&subcache_file) } {
Ok(mmap) => Some(mmap),
.map(|suffix| {
let subcache_path = format!("{}{}", file_path, suffix);
let file = match fs::File::open(&subcache_path) {
Ok(file) => file,
Err(err) => {
println!("Failed to map file '{}': {}", file_path, err);
None
println!("Failed to open file '{}': {}", subcache_path, err);
return Err(());
}
},
)
.collect();
let subcache_files: Vec<&[u8]> = match &subcache_files {
Some(subcache_files) => subcache_files
.iter()
.map(|subcache_file| &**subcache_file)
.collect(),
None => continue,
};
let mmap = match unsafe { memmap2::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
println!("Failed to map file '{}': {}", subcache_path, err);
return Err(());
}
};
Ok(mmap)
})
.collect::<Result<Vec<_>, _>>()
else {
continue;
};
let cache = match DyldCache::<Endianness>::parse(&*file, &subcache_files) {
let subcache_data: Vec<&[u8]> = subcache_files.iter().map(|f| &**f).collect();

let cache = match DyldCache::<Endianness>::parse(data, &subcache_data) {
Ok(cache) => cache,
Err(err) => {
println!(
Expand All @@ -82,56 +86,3 @@ 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, ..., 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()
}
_ => panic!(
"If this case is hit, it means that someone added a variant to the (non-exhaustive) \
DyldSubCacheSlice enum and forgot to update this example"
),
};
let mut files = Vec::new();
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,
};
}
let symbols_subcache_path = format!("{}.symbols", path);
if let Ok(subcache_file) = fs::File::open(symbols_subcache_path) {
files.push(subcache_file);
};
println!("Found {} subcache files", files.len());
files
}
92 changes: 23 additions & 69 deletions crates/examples/src/bin/objdump.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use object::{macho::DyldCacheHeader, read::macho::DyldSubCacheSlice, Endianness};
use object::read::macho::DyldCache;
use object::Endianness;
use object_examples::objdump;
use std::{env, fs, io, process};

Expand All @@ -19,93 +20,46 @@ fn main() {
process::exit(1);
}
};
let file = match unsafe { memmap2::Mmap::map(&file) } {
let mmap = 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
let data = &*mmap;

let subcache_suffixes = DyldCache::<Endianness>::subcache_suffixes(data).unwrap_or_default();
let subcache_files = subcache_suffixes
.into_iter()
.map(
|subcache_file| match unsafe { memmap2::Mmap::map(&subcache_file) } {
.map(|suffix| {
let subcache_path = format!("{}{}", file_path, suffix);
let file = match fs::File::open(&subcache_path) {
Ok(file) => file,
Err(err) => {
eprintln!("Failed to open file '{}': {}", subcache_path, err);
process::exit(1);
}
};
match unsafe { memmap2::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
eprintln!("Failed to map file '{}': {}", file_path, err,);
eprintln!("Failed to map file '{}': {}", subcache_path, err);
process::exit(1);
}
},
)
.collect();
let extra_file_data: Vec<&[u8]> = extra_files.iter().map(|f| &**f).collect();
}
})
.collect::<Vec<_>>();
let extra_file_data: Vec<&[u8]> = subcache_files.iter().map(|f| &**f).collect();

let stdout = io::stdout();
let stderr = io::stderr();
objdump::print(
&mut stdout.lock(),
&mut stderr.lock(),
&file,
data,
&extra_file_data,
member_names,
)
.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, ..., 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()
}
_ => panic!(
"If this case is hit, it means that someone added a variant to the (non-exhaustive) \
DyldSubCacheSlice enum and forgot to update this example"
),
};
let mut files = Vec::new();
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,
};
}
let symbols_subcache_path = format!("{}.symbols", path);
if let Ok(subcache_file) = fs::File::open(symbols_subcache_path) {
files.push(subcache_file);
};
println!("Found {} subcache files", files.len());
files
}
45 changes: 38 additions & 7 deletions src/read/macho/dyld_cache.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::{self, Debug};
use core::{mem, slice};
Expand Down Expand Up @@ -44,25 +45,55 @@ pub enum DyldSubCacheSlice<'data, E: Endian> {
V2(&'data [macho::DyldSubCacheEntryV2<E>]),
}

// This is the offset of the end of the images_across_all_subcaches_count field.
// This is the offset of the end of the images_count field.
const MIN_HEADER_SIZE_SUBCACHES_V1: u32 = 0x1c8;

// This is the offset of the end of the cacheSubType field.
// This field comes right after the images_across_all_subcaches_count field,
// and we don't currently have it in our definition of the DyldCacheHeader type.
// This is the offset of the end of the cache_sub_type field.
const MIN_HEADER_SIZE_SUBCACHES_V2: u32 = 0x1d0;

impl<'data, E, R> DyldCache<'data, E, R>
where
E: Endian,
R: ReadRef<'data>,
{
/// Return the suffixes of the subcache files given the data of the main cache file.
///
/// Each of these should be appended to the path of the main cache file.
pub fn subcache_suffixes(data: R) -> Result<Vec<String>> {
let header = macho::DyldCacheHeader::<E>::parse(data)?;
let (_arch, endian) = header.parse_magic()?;
let Some(subcaches_info) = header.subcaches(endian, data)? else {
return Ok(Vec::new());
};
let mut 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()
}
};
if header.symbols_subcache_uuid(endian).is_some() {
subcache_suffixes.push(".symbols".to_string());
}
Ok(subcache_suffixes)
}

/// Parse the raw dyld shared cache data.
///
/// For shared caches from macOS 12 / iOS 15 and above, the subcache files need to be
/// supplied as well, in the correct order, with the `.symbols` subcache last (if present).
/// For example, `data` would be the data for `dyld_shared_cache_x86_64`,
/// and `subcache_data` would be the data for `[dyld_shared_cache_x86_64.1, dyld_shared_cache_x86_64.2, ...]`.
/// supplied as well, in the correct order. Use [`Self::subcache_suffixes`] to obtain
/// the suffixes for the path of the files.
pub fn parse(data: R, subcache_data: &[R]) -> Result<Self> {
let header = macho::DyldCacheHeader::parse(data)?;
let (arch, endian) = header.parse_magic()?;
Expand Down

0 comments on commit e109535

Please sign in to comment.