| use alloc::vec::Vec; |
| use core::slice; |
| |
| use crate::read::{Error, File, ReadError, ReadRef, Result}; |
| use crate::{macho, Architecture, Endian, Endianness}; |
| |
| /// A parsed representation of the dyld shared cache. |
| #[derive(Debug)] |
| pub struct DyldCache<'data, E = Endianness, R = &'data [u8]> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| endian: E, |
| data: R, |
| subcaches: Vec<DyldSubCache<'data, E, R>>, |
| mappings: &'data [macho::DyldCacheMappingInfo<E>], |
| images: &'data [macho::DyldCacheImageInfo<E>], |
| arch: Architecture, |
| } |
| |
| /// Information about a subcache. |
| #[derive(Debug)] |
| pub struct DyldSubCache<'data, E = Endianness, R = &'data [u8]> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| data: R, |
| mappings: &'data [macho::DyldCacheMappingInfo<E>], |
| } |
| |
| // This is the offset of the images_across_all_subcaches_count field. |
| const MIN_HEADER_SIZE_SUBCACHES: u32 = 0x1c4; |
| |
| impl<'data, E, R> DyldCache<'data, E, R> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| /// 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, ...] |
| pub fn parse(data: R, subcache_data: &[R]) -> Result<Self> { |
| let header = macho::DyldCacheHeader::parse(data)?; |
| let (arch, endian) = header.parse_magic()?; |
| let mappings = header.mappings(endian, data)?; |
| |
| let symbols_subcache_uuid = header.symbols_subcache_uuid(endian); |
| let subcaches_info = header.subcaches(endian, data)?.unwrap_or(&[]); |
| |
| if subcache_data.len() != subcaches_info.len() + symbols_subcache_uuid.is_some() as usize { |
| return Err(Error("Incorrect number of SubCaches")); |
| } |
| |
| // Split out the .symbols subcache data from the other subcaches. |
| let (symbols_subcache_data_and_uuid, subcache_data) = |
| if let Some(symbols_uuid) = symbols_subcache_uuid { |
| let (sym_data, rest_data) = subcache_data.split_last().unwrap(); |
| (Some((*sym_data, symbols_uuid)), rest_data) |
| } else { |
| (None, subcache_data) |
| }; |
| |
| // Read the regular SubCaches (.1, .2, ...), if present. |
| let mut subcaches = Vec::new(); |
| for (&data, info) in subcache_data.iter().zip(subcaches_info.iter()) { |
| let sc_header = macho::DyldCacheHeader::<E>::parse(data)?; |
| if sc_header.uuid != info.uuid { |
| return Err(Error("Unexpected SubCache UUID")); |
| } |
| let mappings = sc_header.mappings(endian, data)?; |
| subcaches.push(DyldSubCache { data, mappings }); |
| } |
| |
| // Read the .symbols SubCache, if present. |
| // Other than the UUID verification, the symbols SubCache is currently unused. |
| let _symbols_subcache = match symbols_subcache_data_and_uuid { |
| Some((data, uuid)) => { |
| let sc_header = macho::DyldCacheHeader::<E>::parse(data)?; |
| if sc_header.uuid != uuid { |
| return Err(Error("Unexpected .symbols SubCache UUID")); |
| } |
| let mappings = sc_header.mappings(endian, data)?; |
| Some(DyldSubCache { data, mappings }) |
| } |
| None => None, |
| }; |
| |
| let images = header.images(endian, data)?; |
| Ok(DyldCache { |
| endian, |
| data, |
| subcaches, |
| mappings, |
| images, |
| arch, |
| }) |
| } |
| |
| /// Get the architecture type of the file. |
| pub fn architecture(&self) -> Architecture { |
| self.arch |
| } |
| |
| /// Get the endianness of the file. |
| #[inline] |
| pub fn endianness(&self) -> Endianness { |
| if self.is_little_endian() { |
| Endianness::Little |
| } else { |
| Endianness::Big |
| } |
| } |
| |
| /// Return true if the file is little endian, false if it is big endian. |
| pub fn is_little_endian(&self) -> bool { |
| self.endian.is_little_endian() |
| } |
| |
| /// Iterate over the images in this cache. |
| pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> { |
| DyldCacheImageIterator { |
| cache: self, |
| iter: self.images.iter(), |
| } |
| } |
| |
| /// Find the address in a mapping and return the cache or subcache data it was found in, |
| /// together with the translated file offset. |
| pub fn data_and_offset_for_address(&self, address: u64) -> Option<(R, u64)> { |
| if let Some(file_offset) = address_to_file_offset(address, self.endian, self.mappings) { |
| return Some((self.data, file_offset)); |
| } |
| for subcache in &self.subcaches { |
| if let Some(file_offset) = |
| address_to_file_offset(address, self.endian, subcache.mappings) |
| { |
| return Some((subcache.data, file_offset)); |
| } |
| } |
| None |
| } |
| } |
| |
| /// An iterator over all the images (dylibs) in the dyld shared cache. |
| #[derive(Debug)] |
| pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| cache: &'cache DyldCache<'data, E, R>, |
| iter: slice::Iter<'data, macho::DyldCacheImageInfo<E>>, |
| } |
| |
| impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, E, R> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| type Item = DyldCacheImage<'data, 'cache, E, R>; |
| |
| fn next(&mut self) -> Option<DyldCacheImage<'data, 'cache, E, R>> { |
| let image_info = self.iter.next()?; |
| Some(DyldCacheImage { |
| cache: self.cache, |
| image_info, |
| }) |
| } |
| } |
| |
| /// One image (dylib) from inside the dyld shared cache. |
| #[derive(Debug)] |
| pub struct DyldCacheImage<'data, 'cache, E = Endianness, R = &'data [u8]> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| pub(crate) cache: &'cache DyldCache<'data, E, R>, |
| image_info: &'data macho::DyldCacheImageInfo<E>, |
| } |
| |
| impl<'data, 'cache, E, R> DyldCacheImage<'data, 'cache, E, R> |
| where |
| E: Endian, |
| R: ReadRef<'data>, |
| { |
| /// The file system path of this image. |
| pub fn path(&self) -> Result<&'data str> { |
| let path = self.image_info.path(self.cache.endian, self.cache.data)?; |
| // The path should always be ascii, so from_utf8 should always succeed. |
| let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?; |
| Ok(path) |
| } |
| |
| /// The subcache data which contains the Mach-O header for this image, |
| /// together with the file offset at which this image starts. |
| pub fn image_data_and_offset(&self) -> Result<(R, u64)> { |
| let address = self.image_info.address.get(self.cache.endian); |
| self.cache |
| .data_and_offset_for_address(address) |
| .ok_or(Error("Address not found in any mapping")) |
| } |
| |
| /// Parse this image into an Object. |
| pub fn parse_object(&self) -> Result<File<'data, R>> { |
| File::parse_dyld_cache_image(self) |
| } |
| } |
| |
| impl<E: Endian> macho::DyldCacheHeader<E> { |
| /// Read the dyld cache header. |
| pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> { |
| data.read_at::<macho::DyldCacheHeader<E>>(0) |
| .read_error("Invalid dyld cache header size or alignment") |
| } |
| |
| /// Returns (arch, endian) based on the magic string. |
| pub fn parse_magic(&self) -> Result<(Architecture, E)> { |
| let (arch, is_big_endian) = match &self.magic { |
| b"dyld_v1 i386\0" => (Architecture::I386, false), |
| b"dyld_v1 x86_64\0" => (Architecture::X86_64, false), |
| b"dyld_v1 x86_64h\0" => (Architecture::X86_64, false), |
| b"dyld_v1 ppc\0" => (Architecture::PowerPc, true), |
| b"dyld_v1 armv6\0" => (Architecture::Arm, false), |
| b"dyld_v1 armv7\0" => (Architecture::Arm, false), |
| b"dyld_v1 armv7f\0" => (Architecture::Arm, false), |
| b"dyld_v1 armv7s\0" => (Architecture::Arm, false), |
| b"dyld_v1 armv7k\0" => (Architecture::Arm, false), |
| b"dyld_v1 arm64\0" => (Architecture::Aarch64, false), |
| b"dyld_v1 arm64e\0" => (Architecture::Aarch64, false), |
| _ => return Err(Error("Unrecognized dyld cache magic")), |
| }; |
| let endian = |
| E::from_big_endian(is_big_endian).read_error("Unsupported dyld cache endian")?; |
| Ok((arch, endian)) |
| } |
| |
| /// Return the mapping information table. |
| pub fn mappings<'data, R: ReadRef<'data>>( |
| &self, |
| endian: E, |
| data: R, |
| ) -> Result<&'data [macho::DyldCacheMappingInfo<E>]> { |
| data.read_slice_at::<macho::DyldCacheMappingInfo<E>>( |
| self.mapping_offset.get(endian).into(), |
| self.mapping_count.get(endian) as usize, |
| ) |
| .read_error("Invalid dyld cache mapping size or alignment") |
| } |
| |
| /// Return the information about subcaches, if present. |
| pub fn subcaches<'data, R: ReadRef<'data>>( |
| &self, |
| endian: E, |
| data: R, |
| ) -> Result<Option<&'data [macho::DyldSubCacheInfo<E>]>> { |
| if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES { |
| let subcaches = data |
| .read_slice_at::<macho::DyldSubCacheInfo<E>>( |
| self.subcaches_offset.get(endian).into(), |
| self.subcaches_count.get(endian) as usize, |
| ) |
| .read_error("Invalid dyld subcaches size or alignment")?; |
| Ok(Some(subcaches)) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| /// Return the UUID for the .symbols subcache, if present. |
| pub fn symbols_subcache_uuid(&self, endian: E) -> Option<[u8; 16]> { |
| if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES { |
| let uuid = self.symbols_subcache_uuid; |
| if uuid != [0; 16] { |
| return Some(uuid); |
| } |
| } |
| None |
| } |
| |
| /// Return the image information table. |
| pub fn images<'data, R: ReadRef<'data>>( |
| &self, |
| endian: E, |
| data: R, |
| ) -> Result<&'data [macho::DyldCacheImageInfo<E>]> { |
| if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES { |
| data.read_slice_at::<macho::DyldCacheImageInfo<E>>( |
| self.images_across_all_subcaches_offset.get(endian).into(), |
| self.images_across_all_subcaches_count.get(endian) as usize, |
| ) |
| .read_error("Invalid dyld cache image size or alignment") |
| } else { |
| data.read_slice_at::<macho::DyldCacheImageInfo<E>>( |
| self.images_offset.get(endian).into(), |
| self.images_count.get(endian) as usize, |
| ) |
| .read_error("Invalid dyld cache image size or alignment") |
| } |
| } |
| } |
| |
| impl<E: Endian> macho::DyldCacheImageInfo<E> { |
| /// The file system path of this image. |
| pub fn path<'data, R: ReadRef<'data>>(&self, endian: E, data: R) -> Result<&'data [u8]> { |
| let r_start = self.path_file_offset.get(endian).into(); |
| let r_end = data.len().read_error("Couldn't get data len()")?; |
| data.read_bytes_at_until(r_start..r_end, 0) |
| .read_error("Couldn't read dyld cache image path") |
| } |
| |
| /// Find the file offset of the image by looking up its address in the mappings. |
| pub fn file_offset( |
| &self, |
| endian: E, |
| mappings: &[macho::DyldCacheMappingInfo<E>], |
| ) -> Result<u64> { |
| let address = self.address.get(endian); |
| address_to_file_offset(address, endian, mappings) |
| .read_error("Invalid dyld cache image address") |
| } |
| } |
| |
| /// Find the file offset of the image by looking up its address in the mappings. |
| pub fn address_to_file_offset<E: Endian>( |
| address: u64, |
| endian: E, |
| mappings: &[macho::DyldCacheMappingInfo<E>], |
| ) -> Option<u64> { |
| for mapping in mappings { |
| let mapping_address = mapping.address.get(endian); |
| if address >= mapping_address |
| && address < mapping_address.wrapping_add(mapping.size.get(endian)) |
| { |
| return Some(address - mapping_address + mapping.file_offset.get(endian)); |
| } |
| } |
| None |
| } |