1use alloc::vec::Vec;
2use core::slice;
3
4use crate::read::{Error, File, ReadError, ReadRef, Result};
5use crate::{macho, Architecture, Endian, Endianness};
6
7/// A parsed representation of the dyld shared cache.
8#[derive(Debug)]
9pub struct DyldCache<'data, E = Endianness, R = &'data [u8]>
10where
11 E: Endian,
12 R: ReadRef<'data>,
13{
14 endian: E,
15 data: R,
16 subcaches: Vec<DyldSubCache<'data, E, R>>,
17 mappings: &'data [macho::DyldCacheMappingInfo<E>],
18 images: &'data [macho::DyldCacheImageInfo<E>],
19 arch: Architecture,
20}
21
22/// Information about a subcache.
23#[derive(Debug)]
24pub struct DyldSubCache<'data, E = Endianness, R = &'data [u8]>
25where
26 E: Endian,
27 R: ReadRef<'data>,
28{
29 data: R,
30 mappings: &'data [macho::DyldCacheMappingInfo<E>],
31}
32
33// This is the offset of the images_across_all_subcaches_count field.
34const MIN_HEADER_SIZE_SUBCACHES: u32 = 0x1c4;
35
36impl<'data, E, R> DyldCache<'data, E, R>
37where
38 E: Endian,
39 R: ReadRef<'data>,
40{
41 /// Parse the raw dyld shared cache data.
42 ///
43 /// For shared caches from macOS 12 / iOS 15 and above, the subcache files need to be
44 /// supplied as well, in the correct order, with the `.symbols` subcache last (if present).
45 /// For example, `data` would be the data for `dyld_shared_cache_x86_64`,
46 /// and `subcache_data` would be the data for `[dyld_shared_cache_x86_64.1, dyld_shared_cache_x86_64.2, ...]`.
47 pub fn parse(data: R, subcache_data: &[R]) -> Result<Self> {
48 let header = macho::DyldCacheHeader::parse(data)?;
49 let (arch, endian) = header.parse_magic()?;
50 let mappings = header.mappings(endian, data)?;
51
52 let symbols_subcache_uuid = header.symbols_subcache_uuid(endian);
53 let subcaches_info = header.subcaches(endian, data)?.unwrap_or(&[]);
54
55 if subcache_data.len() != subcaches_info.len() + symbols_subcache_uuid.is_some() as usize {
56 return Err(Error("Incorrect number of SubCaches"));
57 }
58
59 // Split out the .symbols subcache data from the other subcaches.
60 let (symbols_subcache_data_and_uuid, subcache_data) =
61 if let Some(symbols_uuid) = symbols_subcache_uuid {
62 let (sym_data, rest_data) = subcache_data.split_last().unwrap();
63 (Some((*sym_data, symbols_uuid)), rest_data)
64 } else {
65 (None, subcache_data)
66 };
67
68 // Read the regular SubCaches (.1, .2, ...), if present.
69 let mut subcaches = Vec::new();
70 for (&data, info) in subcache_data.iter().zip(subcaches_info.iter()) {
71 let sc_header = macho::DyldCacheHeader::<E>::parse(data)?;
72 if sc_header.uuid != info.uuid {
73 return Err(Error("Unexpected SubCache UUID"));
74 }
75 let mappings = sc_header.mappings(endian, data)?;
76 subcaches.push(DyldSubCache { data, mappings });
77 }
78
79 // Read the .symbols SubCache, if present.
80 // Other than the UUID verification, the symbols SubCache is currently unused.
81 let _symbols_subcache = match symbols_subcache_data_and_uuid {
82 Some((data, uuid)) => {
83 let sc_header = macho::DyldCacheHeader::<E>::parse(data)?;
84 if sc_header.uuid != uuid {
85 return Err(Error("Unexpected .symbols SubCache UUID"));
86 }
87 let mappings = sc_header.mappings(endian, data)?;
88 Some(DyldSubCache { data, mappings })
89 }
90 None => None,
91 };
92
93 let images = header.images(endian, data)?;
94 Ok(DyldCache {
95 endian,
96 data,
97 subcaches,
98 mappings,
99 images,
100 arch,
101 })
102 }
103
104 /// Get the architecture type of the file.
105 pub fn architecture(&self) -> Architecture {
106 self.arch
107 }
108
109 /// Get the endianness of the file.
110 #[inline]
111 pub fn endianness(&self) -> Endianness {
112 if self.is_little_endian() {
113 Endianness::Little
114 } else {
115 Endianness::Big
116 }
117 }
118
119 /// Return true if the file is little endian, false if it is big endian.
120 pub fn is_little_endian(&self) -> bool {
121 self.endian.is_little_endian()
122 }
123
124 /// Iterate over the images in this cache.
125 pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> {
126 DyldCacheImageIterator {
127 cache: self,
128 iter: self.images.iter(),
129 }
130 }
131
132 /// Find the address in a mapping and return the cache or subcache data it was found in,
133 /// together with the translated file offset.
134 pub fn data_and_offset_for_address(&self, address: u64) -> Option<(R, u64)> {
135 if let Some(file_offset) = address_to_file_offset(address, self.endian, self.mappings) {
136 return Some((self.data, file_offset));
137 }
138 for subcache in &self.subcaches {
139 if let Some(file_offset) =
140 address_to_file_offset(address, self.endian, subcache.mappings)
141 {
142 return Some((subcache.data, file_offset));
143 }
144 }
145 None
146 }
147}
148
149/// An iterator over all the images (dylibs) in the dyld shared cache.
150#[derive(Debug)]
151pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]>
152where
153 E: Endian,
154 R: ReadRef<'data>,
155{
156 cache: &'cache DyldCache<'data, E, R>,
157 iter: slice::Iter<'data, macho::DyldCacheImageInfo<E>>,
158}
159
160impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, E, R>
161where
162 E: Endian,
163 R: ReadRef<'data>,
164{
165 type Item = DyldCacheImage<'data, 'cache, E, R>;
166
167 fn next(&mut self) -> Option<DyldCacheImage<'data, 'cache, E, R>> {
168 let image_info: &DyldCacheImageInfo = self.iter.next()?;
169 Some(DyldCacheImage {
170 cache: self.cache,
171 image_info,
172 })
173 }
174}
175
176/// One image (dylib) from inside the dyld shared cache.
177#[derive(Debug)]
178pub struct DyldCacheImage<'data, 'cache, E = Endianness, R = &'data [u8]>
179where
180 E: Endian,
181 R: ReadRef<'data>,
182{
183 pub(crate) cache: &'cache DyldCache<'data, E, R>,
184 image_info: &'data macho::DyldCacheImageInfo<E>,
185}
186
187impl<'data, 'cache, E, R> DyldCacheImage<'data, 'cache, E, R>
188where
189 E: Endian,
190 R: ReadRef<'data>,
191{
192 /// The file system path of this image.
193 pub fn path(&self) -> Result<&'data str> {
194 let path: &[u8] = self.image_info.path(self.cache.endian, self.cache.data)?;
195 // The path should always be ascii, so from_utf8 should always succeed.
196 let path: &str = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?;
197 Ok(path)
198 }
199
200 /// The subcache data which contains the Mach-O header for this image,
201 /// together with the file offset at which this image starts.
202 pub fn image_data_and_offset(&self) -> Result<(R, u64)> {
203 let address: u64 = self.image_info.address.get(self.cache.endian);
204 self.cache
205 .data_and_offset_for_address(address)
206 .ok_or(err:Error("Address not found in any mapping"))
207 }
208
209 /// Parse this image into an Object.
210 pub fn parse_object(&self) -> Result<File<'data, R>> {
211 File::parse_dyld_cache_image(self)
212 }
213}
214
215impl<E: Endian> macho::DyldCacheHeader<E> {
216 /// Read the dyld cache header.
217 pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> {
218 data.read_at::<macho::DyldCacheHeader<E>>(0)
219 .read_error("Invalid dyld cache header size or alignment")
220 }
221
222 /// Returns (arch, endian) based on the magic string.
223 pub fn parse_magic(&self) -> Result<(Architecture, E)> {
224 let (arch, is_big_endian) = match &self.magic {
225 b"dyld_v1 i386\0" => (Architecture::I386, false),
226 b"dyld_v1 x86_64\0" => (Architecture::X86_64, false),
227 b"dyld_v1 x86_64h\0" => (Architecture::X86_64, false),
228 b"dyld_v1 ppc\0" => (Architecture::PowerPc, true),
229 b"dyld_v1 armv6\0" => (Architecture::Arm, false),
230 b"dyld_v1 armv7\0" => (Architecture::Arm, false),
231 b"dyld_v1 armv7f\0" => (Architecture::Arm, false),
232 b"dyld_v1 armv7s\0" => (Architecture::Arm, false),
233 b"dyld_v1 armv7k\0" => (Architecture::Arm, false),
234 b"dyld_v1 arm64\0" => (Architecture::Aarch64, false),
235 b"dyld_v1 arm64e\0" => (Architecture::Aarch64, false),
236 _ => return Err(Error("Unrecognized dyld cache magic")),
237 };
238 let endian =
239 E::from_big_endian(is_big_endian).read_error("Unsupported dyld cache endian")?;
240 Ok((arch, endian))
241 }
242
243 /// Return the mapping information table.
244 pub fn mappings<'data, R: ReadRef<'data>>(
245 &self,
246 endian: E,
247 data: R,
248 ) -> Result<&'data [macho::DyldCacheMappingInfo<E>]> {
249 data.read_slice_at::<macho::DyldCacheMappingInfo<E>>(
250 self.mapping_offset.get(endian).into(),
251 self.mapping_count.get(endian) as usize,
252 )
253 .read_error("Invalid dyld cache mapping size or alignment")
254 }
255
256 /// Return the information about subcaches, if present.
257 pub fn subcaches<'data, R: ReadRef<'data>>(
258 &self,
259 endian: E,
260 data: R,
261 ) -> Result<Option<&'data [macho::DyldSubCacheInfo<E>]>> {
262 if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES {
263 let subcaches = data
264 .read_slice_at::<macho::DyldSubCacheInfo<E>>(
265 self.subcaches_offset.get(endian).into(),
266 self.subcaches_count.get(endian) as usize,
267 )
268 .read_error("Invalid dyld subcaches size or alignment")?;
269 Ok(Some(subcaches))
270 } else {
271 Ok(None)
272 }
273 }
274
275 /// Return the UUID for the .symbols subcache, if present.
276 pub fn symbols_subcache_uuid(&self, endian: E) -> Option<[u8; 16]> {
277 if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES {
278 let uuid = self.symbols_subcache_uuid;
279 if uuid != [0; 16] {
280 return Some(uuid);
281 }
282 }
283 None
284 }
285
286 /// Return the image information table.
287 pub fn images<'data, R: ReadRef<'data>>(
288 &self,
289 endian: E,
290 data: R,
291 ) -> Result<&'data [macho::DyldCacheImageInfo<E>]> {
292 if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES {
293 data.read_slice_at::<macho::DyldCacheImageInfo<E>>(
294 self.images_across_all_subcaches_offset.get(endian).into(),
295 self.images_across_all_subcaches_count.get(endian) as usize,
296 )
297 .read_error("Invalid dyld cache image size or alignment")
298 } else {
299 data.read_slice_at::<macho::DyldCacheImageInfo<E>>(
300 self.images_offset.get(endian).into(),
301 self.images_count.get(endian) as usize,
302 )
303 .read_error("Invalid dyld cache image size or alignment")
304 }
305 }
306}
307
308impl<E: Endian> macho::DyldCacheImageInfo<E> {
309 /// The file system path of this image.
310 pub fn path<'data, R: ReadRef<'data>>(&self, endian: E, data: R) -> Result<&'data [u8]> {
311 let r_start: u64 = self.path_file_offset.get(endian).into();
312 let r_end: u64 = data.len().read_error("Couldn't get data len()")?;
313 dataResult<&[u8], ()>.read_bytes_at_until(range:r_start..r_end, delimiter:0)
314 .read_error("Couldn't read dyld cache image path")
315 }
316
317 /// Find the file offset of the image by looking up its address in the mappings.
318 pub fn file_offset(
319 &self,
320 endian: E,
321 mappings: &[macho::DyldCacheMappingInfo<E>],
322 ) -> Result<u64> {
323 let address: u64 = self.address.get(endian);
324 address_to_file_offsetOption(address, endian, mappings)
325 .read_error("Invalid dyld cache image address")
326 }
327}
328
329/// Find the file offset of the image by looking up its address in the mappings.
330pub fn address_to_file_offset<E: Endian>(
331 address: u64,
332 endian: E,
333 mappings: &[macho::DyldCacheMappingInfo<E>],
334) -> Option<u64> {
335 for mapping: &DyldCacheMappingInfo in mappings {
336 let mapping_address: u64 = mapping.address.get(endian);
337 if address >= mapping_address
338 && address < mapping_address.wrapping_add(mapping.size.get(endian))
339 {
340 return Some(address - mapping_address + mapping.file_offset.get(endian));
341 }
342 }
343 None
344}
345