object/read/macho/
dyld_cache.rs

1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3use core::fmt::{self, Debug};
4use core::{mem, slice};
5
6use crate::endian::{Endian, Endianness, U16, U32, U64};
7use crate::macho;
8use crate::read::{Architecture, Error, File, ReadError, ReadRef, Result};
9
10/// A parsed representation of the dyld shared cache.
11#[derive(Debug)]
12pub struct DyldCache<'data, E = Endianness, R = &'data [u8]>
13where
14    E: Endian,
15    R: ReadRef<'data>,
16{
17    endian: E,
18    data: R,
19    /// The first entry is the main cache file, and the rest are subcaches.
20    files: Vec<DyldFile<'data, E, R>>,
21    images: &'data [macho::DyldCacheImageInfo<E>],
22    arch: Architecture,
23}
24
25/// A slice of structs describing each subcache.
26///
27/// The struct gained an additional field (the file suffix) in dyld-1042.1 (macOS 13 / iOS 16),
28/// so this is an enum of the two possible slice types.
29#[derive(Debug, Clone, Copy)]
30#[non_exhaustive]
31pub enum DyldSubCacheSlice<'data, E: Endian> {
32    /// V1, used between dyld-940 and dyld-1042.1.
33    V1(&'data [macho::DyldSubCacheEntryV1<E>]),
34    /// V2, used since dyld-1042.1.
35    V2(&'data [macho::DyldSubCacheEntryV2<E>]),
36}
37
38// This is the offset of the end of the images_count field.
39const MIN_HEADER_SIZE_SUBCACHES_V1: u32 = 0x1c8;
40
41// This is the offset of the end of the cache_sub_type field.
42const MIN_HEADER_SIZE_SUBCACHES_V2: u32 = 0x1d0;
43
44impl<'data, E, R> DyldCache<'data, E, R>
45where
46    E: Endian,
47    R: ReadRef<'data>,
48{
49    /// Return the suffixes of the subcache files given the data of the main cache file.
50    ///
51    /// Each of these should be appended to the path of the main cache file.
52    pub fn subcache_suffixes(data: R) -> Result<Vec<String>> {
53        let header = macho::DyldCacheHeader::<E>::parse(data)?;
54        let (_arch, endian) = header.parse_magic()?;
55        let Some(subcaches_info) = header.subcaches(endian, data)? else {
56            return Ok(Vec::new());
57        };
58        let mut subcache_suffixes: Vec<String> = match subcaches_info {
59            DyldSubCacheSlice::V1(subcaches) => {
60                // macOS 12: Subcaches have the file suffixes .1, .2, .3 etc.
61                (1..subcaches.len() + 1).map(|i| format!(".{i}")).collect()
62            }
63            DyldSubCacheSlice::V2(subcaches) => {
64                // macOS 13+: The subcache file suffix is written down in the header of the main cache.
65                subcaches
66                    .iter()
67                    .map(|s| {
68                        // The suffix is a nul-terminated string in a fixed-size byte array.
69                        let suffix = s.file_suffix;
70                        let len = suffix.iter().position(|&c| c == 0).unwrap_or(suffix.len());
71                        String::from_utf8_lossy(&suffix[..len]).to_string()
72                    })
73                    .collect()
74            }
75        };
76        if header.symbols_subcache_uuid(endian).is_some() {
77            subcache_suffixes.push(".symbols".to_string());
78        }
79        Ok(subcache_suffixes)
80    }
81
82    /// Parse the raw dyld shared cache data.
83    ///
84    /// For shared caches from macOS 12 / iOS 15 and above, the subcache files need to be
85    /// supplied as well, in the correct order. Use [`Self::subcache_suffixes`] to obtain
86    /// the suffixes for the path of the files.
87    pub fn parse(data: R, subcache_data: &[R]) -> Result<Self> {
88        let header = macho::DyldCacheHeader::parse(data)?;
89        let (arch, endian) = header.parse_magic()?;
90
91        let mut files = Vec::new();
92        let mappings = header.mappings(endian, data)?;
93        files.push(DyldFile { data, mappings });
94
95        let symbols_subcache_uuid = header.symbols_subcache_uuid(endian);
96        let subcaches_info = header.subcaches(endian, data)?;
97        let subcaches_count = match subcaches_info {
98            Some(DyldSubCacheSlice::V1(subcaches)) => subcaches.len(),
99            Some(DyldSubCacheSlice::V2(subcaches)) => subcaches.len(),
100            None => 0,
101        };
102        if subcache_data.len() != subcaches_count + symbols_subcache_uuid.is_some() as usize {
103            return Err(Error("Incorrect number of SubCaches"));
104        }
105
106        // Split out the .symbols subcache data from the other subcaches.
107        let (symbols_subcache_data_and_uuid, subcache_data) =
108            if let Some(symbols_uuid) = symbols_subcache_uuid {
109                let (sym_data, rest_data) = subcache_data.split_last().unwrap();
110                (Some((*sym_data, symbols_uuid)), rest_data)
111            } else {
112                (None, subcache_data)
113            };
114
115        // Read the regular SubCaches, if present.
116        if let Some(subcaches_info) = subcaches_info {
117            let (v1, v2) = match subcaches_info {
118                DyldSubCacheSlice::V1(s) => (s, &[][..]),
119                DyldSubCacheSlice::V2(s) => (&[][..], s),
120            };
121            let uuids = v1.iter().map(|e| &e.uuid).chain(v2.iter().map(|e| &e.uuid));
122            for (&data, uuid) in subcache_data.iter().zip(uuids) {
123                let header = macho::DyldCacheHeader::<E>::parse(data)?;
124                if &header.uuid != uuid {
125                    return Err(Error("Unexpected SubCache UUID"));
126                }
127                let mappings = header.mappings(endian, data)?;
128                files.push(DyldFile { data, mappings });
129            }
130        }
131
132        // Read the .symbols SubCache, if present.
133        // Other than the UUID verification, the symbols SubCache is currently unused.
134        let _symbols_subcache = match symbols_subcache_data_and_uuid {
135            Some((data, uuid)) => {
136                let header = macho::DyldCacheHeader::<E>::parse(data)?;
137                if header.uuid != uuid {
138                    return Err(Error("Unexpected .symbols SubCache UUID"));
139                }
140                let mappings = header.mappings(endian, data)?;
141                Some(DyldFile { data, mappings })
142            }
143            None => None,
144        };
145
146        let images = header.images(endian, data)?;
147        Ok(DyldCache {
148            endian,
149            data,
150            files,
151            images,
152            arch,
153        })
154    }
155
156    /// Get the architecture type of the file.
157    pub fn architecture(&self) -> Architecture {
158        self.arch
159    }
160
161    /// Get the endianness of the file.
162    #[inline]
163    pub fn endianness(&self) -> Endianness {
164        if self.is_little_endian() {
165            Endianness::Little
166        } else {
167            Endianness::Big
168        }
169    }
170
171    /// Get the data of the main cache file.
172    #[inline]
173    pub fn data(&self) -> R {
174        self.data
175    }
176
177    /// Return true if the file is little endian, false if it is big endian.
178    pub fn is_little_endian(&self) -> bool {
179        self.endian.is_little_endian()
180    }
181
182    /// Iterate over the images in this cache.
183    pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> {
184        DyldCacheImageIterator {
185            cache: self,
186            iter: self.images.iter(),
187        }
188    }
189
190    /// Return all the mappings in this cache.
191    pub fn mappings<'cache>(
192        &'cache self,
193    ) -> impl Iterator<Item = DyldCacheMapping<'data, E, R>> + 'cache {
194        let endian = self.endian;
195        self.files
196            .iter()
197            .flat_map(move |file| file.mappings(endian))
198    }
199
200    /// Find the address in a mapping and return the cache or subcache data it was found in,
201    /// together with the translated file offset.
202    pub fn data_and_offset_for_address(&self, address: u64) -> Option<(R, u64)> {
203        for file in &self.files {
204            if let Some(file_offset) = file.address_to_file_offset(self.endian, address) {
205                return Some((file.data, file_offset));
206            }
207        }
208        None
209    }
210}
211
212/// The data for one file in the cache.
213#[derive(Debug)]
214struct DyldFile<'data, E = Endianness, R = &'data [u8]>
215where
216    E: Endian,
217    R: ReadRef<'data>,
218{
219    data: R,
220    mappings: DyldCacheMappingSlice<'data, E>,
221}
222
223impl<'data, E, R> DyldFile<'data, E, R>
224where
225    E: Endian,
226    R: ReadRef<'data>,
227{
228    /// Return an iterator for the mappings.
229    fn mappings(&self, endian: E) -> DyldCacheMappingIterator<'data, E, R> {
230        let iter = match self.mappings {
231            DyldCacheMappingSlice::V1(info) => DyldCacheMappingVersionIterator::V1(info.iter()),
232            DyldCacheMappingSlice::V2(info) => DyldCacheMappingVersionIterator::V2(info.iter()),
233        };
234        DyldCacheMappingIterator {
235            endian,
236            data: self.data,
237            iter,
238        }
239    }
240
241    /// Find the file offset an address in the mappings.
242    fn address_to_file_offset(&self, endian: E, address: u64) -> Option<u64> {
243        for mapping in self.mappings(endian) {
244            let mapping_address = mapping.address();
245            if address >= mapping_address && address < mapping_address.wrapping_add(mapping.size())
246            {
247                return Some(address - mapping_address + mapping.file_offset());
248            }
249        }
250        None
251    }
252}
253
254/// An iterator over all the images (dylibs) in the dyld shared cache.
255#[derive(Debug)]
256pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]>
257where
258    E: Endian,
259    R: ReadRef<'data>,
260{
261    cache: &'cache DyldCache<'data, E, R>,
262    iter: slice::Iter<'data, macho::DyldCacheImageInfo<E>>,
263}
264
265impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, E, R>
266where
267    E: Endian,
268    R: ReadRef<'data>,
269{
270    type Item = DyldCacheImage<'data, 'cache, E, R>;
271
272    fn next(&mut self) -> Option<DyldCacheImage<'data, 'cache, E, R>> {
273        let image_info = self.iter.next()?;
274        Some(DyldCacheImage {
275            cache: self.cache,
276            image_info,
277        })
278    }
279}
280
281/// One image (dylib) from inside the dyld shared cache.
282#[derive(Debug)]
283pub struct DyldCacheImage<'data, 'cache, E = Endianness, R = &'data [u8]>
284where
285    E: Endian,
286    R: ReadRef<'data>,
287{
288    pub(crate) cache: &'cache DyldCache<'data, E, R>,
289    image_info: &'data macho::DyldCacheImageInfo<E>,
290}
291
292impl<'data, 'cache, E, R> DyldCacheImage<'data, 'cache, E, R>
293where
294    E: Endian,
295    R: ReadRef<'data>,
296{
297    /// Return the raw data structure for this image.
298    pub fn info(&self) -> &'data macho::DyldCacheImageInfo<E> {
299        self.image_info
300    }
301
302    /// The file system path of this image.
303    pub fn path(&self) -> Result<&'data str> {
304        let path = self.image_info.path(self.cache.endian, self.cache.data)?;
305        // The path should always be ascii, so from_utf8 should always succeed.
306        let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?;
307        Ok(path)
308    }
309
310    /// The subcache data which contains the Mach-O header for this image,
311    /// together with the file offset at which this image starts.
312    pub fn image_data_and_offset(&self) -> Result<(R, u64)> {
313        let address = self.image_info.address.get(self.cache.endian);
314        self.cache
315            .data_and_offset_for_address(address)
316            .ok_or(Error("Address not found in any mapping"))
317    }
318
319    /// Parse this image into an Object.
320    pub fn parse_object(&self) -> Result<File<'data, R>> {
321        File::parse_dyld_cache_image(self)
322    }
323}
324
325/// The array of mappings for a single dyld cache file.
326///
327/// The mappings gained slide info in dyld-832.7 (macOS 11)
328/// so this is an enum of the two possible slice types.
329#[derive(Debug, Clone, Copy)]
330#[non_exhaustive]
331pub enum DyldCacheMappingSlice<'data, E: Endian = Endianness> {
332    /// V1, used before dyld-832.7.
333    V1(&'data [macho::DyldCacheMappingInfo<E>]),
334    /// V2, used since dyld-832.7.
335    V2(&'data [macho::DyldCacheMappingAndSlideInfo<E>]),
336}
337
338// This is the offset of the end of the mapping_with_slide_count field.
339const MIN_HEADER_SIZE_MAPPINGS_V2: u32 = 0x140;
340
341/// An iterator over all the mappings for one subcache in a dyld shared cache.
342#[derive(Debug)]
343pub struct DyldCacheMappingIterator<'data, E = Endianness, R = &'data [u8]>
344where
345    E: Endian,
346    R: ReadRef<'data>,
347{
348    endian: E,
349    data: R,
350    iter: DyldCacheMappingVersionIterator<'data, E>,
351}
352
353#[derive(Debug)]
354enum DyldCacheMappingVersionIterator<'data, E = Endianness>
355where
356    E: Endian,
357{
358    V1(slice::Iter<'data, macho::DyldCacheMappingInfo<E>>),
359    V2(slice::Iter<'data, macho::DyldCacheMappingAndSlideInfo<E>>),
360}
361
362impl<'data, E, R> Iterator for DyldCacheMappingIterator<'data, E, R>
363where
364    E: Endian,
365    R: ReadRef<'data>,
366{
367    type Item = DyldCacheMapping<'data, E, R>;
368
369    fn next(&mut self) -> Option<Self::Item> {
370        let info = match &mut self.iter {
371            DyldCacheMappingVersionIterator::V1(iter) => DyldCacheMappingVersion::V1(iter.next()?),
372            DyldCacheMappingVersionIterator::V2(iter) => DyldCacheMappingVersion::V2(iter.next()?),
373        };
374        Some(DyldCacheMapping {
375            endian: self.endian,
376            data: self.data,
377            info,
378        })
379    }
380}
381
382/// Information about a mapping.
383#[derive(Clone, Copy)]
384pub struct DyldCacheMapping<'data, E = Endianness, R = &'data [u8]>
385where
386    E: Endian,
387    R: ReadRef<'data>,
388{
389    endian: E,
390    data: R,
391    info: DyldCacheMappingVersion<'data, E>,
392}
393
394#[derive(Clone, Copy)]
395enum DyldCacheMappingVersion<'data, E = Endianness>
396where
397    E: Endian,
398{
399    V1(&'data macho::DyldCacheMappingInfo<E>),
400    V2(&'data macho::DyldCacheMappingAndSlideInfo<E>),
401}
402
403impl<'data, E, R> Debug for DyldCacheMapping<'data, E, R>
404where
405    E: Endian,
406    R: ReadRef<'data>,
407{
408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409        f.debug_struct("DyldCacheMapping")
410            .field("address", &format_args!("{:#x}", self.address()))
411            .field("size", &format_args!("{:#x}", self.size()))
412            .field("file_offset", &format_args!("{:#x}", self.file_offset()))
413            .field("max_prot", &format_args!("{:#x}", self.max_prot()))
414            .field("init_prot", &format_args!("{:#x}", self.init_prot()))
415            .finish()
416    }
417}
418
419impl<'data, E, R> DyldCacheMapping<'data, E, R>
420where
421    E: Endian,
422    R: ReadRef<'data>,
423{
424    /// The mapping address
425    pub fn address(&self) -> u64 {
426        match self.info {
427            DyldCacheMappingVersion::V1(info) => info.address.get(self.endian),
428            DyldCacheMappingVersion::V2(info) => info.address.get(self.endian),
429        }
430    }
431
432    /// The mapping size
433    pub fn size(&self) -> u64 {
434        match self.info {
435            DyldCacheMappingVersion::V1(info) => info.size.get(self.endian),
436            DyldCacheMappingVersion::V2(info) => info.size.get(self.endian),
437        }
438    }
439
440    /// The mapping file offset
441    pub fn file_offset(&self) -> u64 {
442        match self.info {
443            DyldCacheMappingVersion::V1(info) => info.file_offset.get(self.endian),
444            DyldCacheMappingVersion::V2(info) => info.file_offset.get(self.endian),
445        }
446    }
447
448    /// The mapping maximum protection
449    pub fn max_prot(&self) -> u32 {
450        match self.info {
451            DyldCacheMappingVersion::V1(info) => info.max_prot.get(self.endian),
452            DyldCacheMappingVersion::V2(info) => info.max_prot.get(self.endian),
453        }
454    }
455
456    /// The mapping initial protection
457    pub fn init_prot(&self) -> u32 {
458        match self.info {
459            DyldCacheMappingVersion::V1(info) => info.init_prot.get(self.endian),
460            DyldCacheMappingVersion::V2(info) => info.init_prot.get(self.endian),
461        }
462    }
463
464    /// The mapping data
465    pub fn data(&self) -> Result<&'data [u8]> {
466        self.data
467            .read_bytes_at(self.file_offset(), self.size())
468            .read_error("Failed to read bytes for mapping")
469    }
470
471    /// Relocations for the mapping
472    pub fn relocations(&self) -> Result<DyldCacheRelocationIterator<'data, E, R>> {
473        let data = self.data;
474        let endian = self.endian;
475        let version = match self.info {
476            DyldCacheMappingVersion::V1(_) => DyldCacheRelocationIteratorVersion::None,
477            DyldCacheMappingVersion::V2(mapping) => match mapping.slide(self.endian, self.data)? {
478                DyldCacheSlideInfo::None => DyldCacheRelocationIteratorVersion::None,
479                DyldCacheSlideInfo::V2 {
480                    slide,
481                    page_starts,
482                    page_extras,
483                } => {
484                    let delta_mask = slide.delta_mask.get(endian);
485                    let delta_shift = delta_mask.trailing_zeros();
486                    DyldCacheRelocationIteratorVersion::V2(DyldCacheRelocationIteratorV2 {
487                        data,
488                        endian,
489                        mapping_file_offset: mapping.file_offset.get(endian),
490                        page_size: slide.page_size.get(endian).into(),
491                        delta_mask,
492                        delta_shift,
493                        value_add: slide.value_add.get(endian),
494                        page_starts,
495                        page_extras,
496                        state: RelocationStateV2::Start,
497                        start_index: 0,
498                        extra_index: 0,
499                        page_offset: 0,
500                        offset: 0,
501                    })
502                }
503                DyldCacheSlideInfo::V3 { slide, page_starts } => {
504                    DyldCacheRelocationIteratorVersion::V3(DyldCacheRelocationIteratorV3 {
505                        data,
506                        endian,
507                        mapping_file_offset: mapping.file_offset.get(endian),
508                        page_size: slide.page_size.get(endian).into(),
509                        auth_value_add: slide.auth_value_add.get(endian),
510                        page_starts,
511                        state: RelocationStateV3::Start,
512                        start_index: 0,
513                        offset: 0,
514                    })
515                }
516                DyldCacheSlideInfo::V5 { slide, page_starts } => {
517                    DyldCacheRelocationIteratorVersion::V5(DyldCacheRelocationIteratorV5 {
518                        data,
519                        endian,
520                        mapping_file_offset: mapping.file_offset.get(endian),
521                        page_size: slide.page_size.get(endian).into(),
522                        value_add: slide.value_add.get(endian),
523                        page_starts,
524                        state: RelocationStateV5::Start,
525                        start_index: 0,
526                        offset: 0,
527                    })
528                }
529            },
530        };
531        Ok(DyldCacheRelocationIterator { version })
532    }
533}
534
535/// The slide info for a dyld cache mapping, including variable length arrays.
536#[derive(Debug, Clone, Copy)]
537#[non_exhaustive]
538#[allow(missing_docs)]
539pub enum DyldCacheSlideInfo<'data, E: Endian> {
540    None,
541    V2 {
542        slide: &'data macho::DyldCacheSlideInfo2<E>,
543        page_starts: &'data [U16<E>],
544        page_extras: &'data [U16<E>],
545    },
546    V3 {
547        slide: &'data macho::DyldCacheSlideInfo3<E>,
548        page_starts: &'data [U16<E>],
549    },
550    V5 {
551        slide: &'data macho::DyldCacheSlideInfo5<E>,
552        page_starts: &'data [U16<E>],
553    },
554}
555
556/// An iterator over relocations in a mapping
557#[derive(Debug)]
558pub struct DyldCacheRelocationIterator<'data, E = Endianness, R = &'data [u8]>
559where
560    E: Endian,
561    R: ReadRef<'data>,
562{
563    version: DyldCacheRelocationIteratorVersion<'data, E, R>,
564}
565
566impl<'data, E, R> Iterator for DyldCacheRelocationIterator<'data, E, R>
567where
568    E: Endian,
569    R: ReadRef<'data>,
570{
571    type Item = Result<DyldRelocation>;
572
573    fn next(&mut self) -> Option<Self::Item> {
574        match &mut self.version {
575            DyldCacheRelocationIteratorVersion::None => Ok(None),
576            DyldCacheRelocationIteratorVersion::V2(iter) => iter.next(),
577            DyldCacheRelocationIteratorVersion::V3(iter) => iter.next(),
578            DyldCacheRelocationIteratorVersion::V5(iter) => iter.next(),
579        }
580        .transpose()
581    }
582}
583
584#[derive(Debug)]
585enum DyldCacheRelocationIteratorVersion<'data, E = Endianness, R = &'data [u8]>
586where
587    E: Endian,
588    R: ReadRef<'data>,
589{
590    None,
591    V2(DyldCacheRelocationIteratorV2<'data, E, R>),
592    V3(DyldCacheRelocationIteratorV3<'data, E, R>),
593    V5(DyldCacheRelocationIteratorV5<'data, E, R>),
594}
595
596#[derive(Debug, PartialEq, Eq, Clone, Copy)]
597enum RelocationStateV2 {
598    Start,
599    Extra,
600    Page,
601    PageExtra,
602}
603
604#[derive(Debug)]
605struct DyldCacheRelocationIteratorV2<'data, E = Endianness, R = &'data [u8]>
606where
607    E: Endian,
608    R: ReadRef<'data>,
609{
610    data: R,
611    endian: E,
612    mapping_file_offset: u64,
613    page_size: u64,
614    delta_mask: u64,
615    delta_shift: u32,
616    value_add: u64,
617    page_starts: &'data [U16<E>],
618    page_extras: &'data [U16<E>],
619
620    state: RelocationStateV2,
621    /// The next index within page_starts.
622    start_index: usize,
623    /// The next index within page_extras.
624    extra_index: usize,
625    /// The current page offset within the mapping.
626    page_offset: u64,
627    /// The offset of the next linked list entry within the page.
628    offset: u64,
629}
630
631impl<'data, E, R> DyldCacheRelocationIteratorV2<'data, E, R>
632where
633    E: Endian,
634    R: ReadRef<'data>,
635{
636    fn next(&mut self) -> Result<Option<DyldRelocation>> {
637        loop {
638            match self.state {
639                RelocationStateV2::Start => {
640                    let Some(page_start) = self.page_starts.get(self.start_index) else {
641                        return Ok(None);
642                    };
643                    self.page_offset = self.start_index as u64 * self.page_size;
644                    self.start_index += 1;
645
646                    let page_start = page_start.get(self.endian);
647                    if page_start & macho::DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE != 0 {
648                        self.state = RelocationStateV2::Start;
649                    } else if page_start & macho::DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA != 0 {
650                        self.state = RelocationStateV2::Extra;
651                        self.extra_index =
652                            usize::from(page_start & !macho::DYLD_CACHE_SLIDE_PAGE_ATTRS);
653                    } else {
654                        self.state = RelocationStateV2::Page;
655                        self.offset =
656                            u64::from(page_start & !macho::DYLD_CACHE_SLIDE_PAGE_ATTRS) * 4;
657                    }
658                }
659                RelocationStateV2::Extra => {
660                    let Some(page_extra) = self.page_extras.get(self.extra_index) else {
661                        return Ok(None);
662                    };
663                    self.extra_index += 1;
664
665                    let page_extra = page_extra.get(self.endian);
666                    self.offset = u64::from(page_extra & !macho::DYLD_CACHE_SLIDE_PAGE_ATTRS) * 4;
667                    if page_extra & macho::DYLD_CACHE_SLIDE_PAGE_ATTR_END != 0 {
668                        self.state = RelocationStateV2::Page;
669                    } else {
670                        self.state = RelocationStateV2::PageExtra;
671                    }
672                }
673                RelocationStateV2::Page | RelocationStateV2::PageExtra => {
674                    let offset = self.offset;
675                    let pointer = self
676                        .data
677                        .read_at::<U64<E>>(self.mapping_file_offset + self.page_offset + offset)
678                        .read_error("Invalid dyld cache slide pointer offset")?
679                        .get(self.endian);
680
681                    let next = (pointer & self.delta_mask) >> self.delta_shift;
682                    if next == 0 {
683                        if self.state == RelocationStateV2::PageExtra {
684                            self.state = RelocationStateV2::Extra
685                        } else {
686                            self.state = RelocationStateV2::Start
687                        };
688                    } else {
689                        self.offset = offset + next * 4;
690                    };
691
692                    let value = pointer & !self.delta_mask;
693                    if value != 0 {
694                        return Ok(Some(DyldRelocation {
695                            offset,
696                            value: value + self.value_add,
697                            auth: None,
698                        }));
699                    }
700                }
701            }
702        }
703    }
704}
705
706#[derive(Debug, PartialEq, Eq, Clone, Copy)]
707enum RelocationStateV3 {
708    Start,
709    Page,
710}
711
712#[derive(Debug)]
713struct DyldCacheRelocationIteratorV3<'data, E = Endianness, R = &'data [u8]>
714where
715    E: Endian,
716    R: ReadRef<'data>,
717{
718    data: R,
719    endian: E,
720    mapping_file_offset: u64,
721    auth_value_add: u64,
722    page_size: u64,
723    page_starts: &'data [U16<E>],
724
725    state: RelocationStateV3,
726    /// Index of the page within the mapping.
727    start_index: usize,
728    /// The current offset within the mapping.
729    offset: u64,
730}
731
732impl<'data, E, R> DyldCacheRelocationIteratorV3<'data, E, R>
733where
734    E: Endian,
735    R: ReadRef<'data>,
736{
737    fn next(&mut self) -> Result<Option<DyldRelocation>> {
738        loop {
739            match self.state {
740                RelocationStateV3::Start => {
741                    let Some(page_start) = self.page_starts.get(self.start_index) else {
742                        return Ok(None);
743                    };
744                    let page_offset = self.start_index as u64 * self.page_size;
745                    self.start_index += 1;
746
747                    let page_start = page_start.get(self.endian);
748                    if page_start == macho::DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE {
749                        self.state = RelocationStateV3::Start;
750                    } else {
751                        self.state = RelocationStateV3::Page;
752                        self.offset = page_offset + u64::from(page_start);
753                    }
754                }
755                RelocationStateV3::Page => {
756                    let offset = self.offset;
757                    let pointer = self
758                        .data
759                        .read_at::<U64<E>>(self.mapping_file_offset + offset)
760                        .read_error("Invalid dyld cache slide pointer offset")?
761                        .get(self.endian);
762                    let pointer = macho::DyldCacheSlidePointer3(pointer);
763
764                    let next = pointer.next();
765                    if next == 0 {
766                        self.state = RelocationStateV3::Start;
767                    } else {
768                        self.offset = offset + next * 8;
769                    }
770
771                    if pointer.is_auth() {
772                        let value = pointer.runtime_offset() + self.auth_value_add;
773                        let key = match pointer.key() {
774                            1 => macho::PtrauthKey::IB,
775                            2 => macho::PtrauthKey::DA,
776                            3 => macho::PtrauthKey::DB,
777                            _ => macho::PtrauthKey::IA,
778                        };
779                        let auth = Some(DyldRelocationAuth {
780                            key,
781                            diversity: pointer.diversity(),
782                            addr_div: pointer.addr_div(),
783                        });
784                        return Ok(Some(DyldRelocation {
785                            offset,
786                            value,
787                            auth,
788                        }));
789                    } else {
790                        let value = pointer.target() | pointer.high8() << 56;
791                        return Ok(Some(DyldRelocation {
792                            offset,
793                            value,
794                            auth: None,
795                        }));
796                    };
797                }
798            }
799        }
800    }
801}
802
803#[derive(Debug, PartialEq, Eq, Clone, Copy)]
804enum RelocationStateV5 {
805    Start,
806    Page,
807}
808
809#[derive(Debug)]
810struct DyldCacheRelocationIteratorV5<'data, E = Endianness, R = &'data [u8]>
811where
812    E: Endian,
813    R: ReadRef<'data>,
814{
815    data: R,
816    endian: E,
817    mapping_file_offset: u64,
818    page_size: u64,
819    value_add: u64,
820    page_starts: &'data [U16<E>],
821
822    state: RelocationStateV5,
823    /// The next index within page_starts.
824    start_index: usize,
825    /// The current offset within the mapping.
826    offset: u64,
827}
828
829impl<'data, E, R> DyldCacheRelocationIteratorV5<'data, E, R>
830where
831    E: Endian,
832    R: ReadRef<'data>,
833{
834    fn next(&mut self) -> Result<Option<DyldRelocation>> {
835        loop {
836            match self.state {
837                RelocationStateV5::Start => {
838                    let Some(page_start) = self.page_starts.get(self.start_index) else {
839                        return Ok(None);
840                    };
841                    let page_offset = self.start_index as u64 * self.page_size;
842                    self.start_index += 1;
843
844                    let page_start = page_start.get(self.endian);
845                    if page_start == macho::DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE {
846                        self.state = RelocationStateV5::Start;
847                    } else {
848                        self.state = RelocationStateV5::Page;
849                        self.offset = page_offset + u64::from(page_start);
850                    }
851                }
852                RelocationStateV5::Page => {
853                    let offset = self.offset;
854                    let pointer = self
855                        .data
856                        .read_at::<U64<E>>(self.mapping_file_offset + offset)
857                        .read_error("Invalid dyld cache slide pointer offset")?
858                        .get(self.endian);
859                    let pointer = macho::DyldCacheSlidePointer5(pointer);
860
861                    let next = pointer.next();
862                    if next == 0 {
863                        self.state = RelocationStateV5::Start;
864                    } else {
865                        self.offset = offset + next * 8;
866                    }
867
868                    let mut value = pointer.runtime_offset() + self.value_add;
869                    let auth = if pointer.is_auth() {
870                        let key = if pointer.key_is_data() {
871                            macho::PtrauthKey::DA
872                        } else {
873                            macho::PtrauthKey::IA
874                        };
875                        Some(DyldRelocationAuth {
876                            key,
877                            diversity: pointer.diversity(),
878                            addr_div: pointer.addr_div(),
879                        })
880                    } else {
881                        value |= pointer.high8() << 56;
882                        None
883                    };
884                    return Ok(Some(DyldRelocation {
885                        offset,
886                        value,
887                        auth,
888                    }));
889                }
890            }
891        }
892    }
893}
894
895/// A cache mapping relocation.
896pub struct DyldRelocation {
897    /// The offset of the relocation within the mapping.
898    ///
899    /// This can be added to either the mapping file offset or the
900    /// mapping address.
901    pub offset: u64,
902    /// The value to be relocated.
903    pub value: u64,
904    /// The pointer authentication data, if present.
905    pub auth: Option<DyldRelocationAuth>,
906}
907
908impl Debug for DyldRelocation {
909    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
910        f.debug_struct("DyldRelocation")
911            .field("offset", &format_args!("{:#x}", self.offset))
912            .field("value", &format_args!("{:#x}", self.value))
913            .field("auth", &self.auth)
914            .finish()
915    }
916}
917
918/// Pointer authentication data.
919///
920/// This is used for signing pointers for the arm64e ABI.
921pub struct DyldRelocationAuth {
922    /// The key used to generate the signed value.
923    pub key: macho::PtrauthKey,
924    /// The integer diversity value.
925    pub diversity: u16,
926    /// Whether the address should be blended with the diversity value.
927    pub addr_div: bool,
928}
929
930impl Debug for DyldRelocationAuth {
931    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
932        f.debug_struct("Ptrauth")
933            .field("key", &self.key)
934            .field("diversity", &format_args!("{:#x}", self.diversity))
935            .field("addr_div", &self.addr_div)
936            .finish()
937    }
938}
939
940impl<E: Endian> macho::DyldCacheHeader<E> {
941    /// Read the dyld cache header.
942    pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> {
943        data.read_at::<macho::DyldCacheHeader<E>>(0)
944            .read_error("Invalid dyld cache header size or alignment")
945    }
946
947    /// Returns (arch, endian) based on the magic string.
948    pub fn parse_magic(&self) -> Result<(Architecture, E)> {
949        let (arch, is_big_endian) = match &self.magic {
950            b"dyld_v1    i386\0" => (Architecture::I386, false),
951            b"dyld_v1  x86_64\0" => (Architecture::X86_64, false),
952            b"dyld_v1 x86_64h\0" => (Architecture::X86_64, false),
953            b"dyld_v1     ppc\0" => (Architecture::PowerPc, true),
954            b"dyld_v1   armv6\0" => (Architecture::Arm, false),
955            b"dyld_v1   armv7\0" => (Architecture::Arm, false),
956            b"dyld_v1  armv7f\0" => (Architecture::Arm, false),
957            b"dyld_v1  armv7s\0" => (Architecture::Arm, false),
958            b"dyld_v1  armv7k\0" => (Architecture::Arm, false),
959            b"dyld_v1   arm64\0" => (Architecture::Aarch64, false),
960            b"dyld_v1  arm64e\0" => (Architecture::Aarch64, false),
961            _ => return Err(Error("Unrecognized dyld cache magic")),
962        };
963        let endian =
964            E::from_big_endian(is_big_endian).read_error("Unsupported dyld cache endian")?;
965        Ok((arch, endian))
966    }
967
968    /// Return the mapping information table.
969    pub fn mappings<'data, R: ReadRef<'data>>(
970        &self,
971        endian: E,
972        data: R,
973    ) -> Result<DyldCacheMappingSlice<'data, E>> {
974        let header_size = self.mapping_offset.get(endian);
975        if header_size >= MIN_HEADER_SIZE_MAPPINGS_V2 {
976            let info = data
977                .read_slice_at::<macho::DyldCacheMappingAndSlideInfo<E>>(
978                    self.mapping_with_slide_offset.get(endian).into(),
979                    self.mapping_with_slide_count.get(endian) as usize,
980                )
981                .read_error("Invalid dyld cache mapping size or alignment")?;
982            Ok(DyldCacheMappingSlice::V2(info))
983        } else {
984            let info = data
985                .read_slice_at::<macho::DyldCacheMappingInfo<E>>(
986                    self.mapping_offset.get(endian).into(),
987                    self.mapping_count.get(endian) as usize,
988                )
989                .read_error("Invalid dyld cache mapping size or alignment")?;
990            Ok(DyldCacheMappingSlice::V1(info))
991        }
992    }
993
994    /// Return the information about subcaches, if present.
995    ///
996    /// Returns `None` for dyld caches produced before dyld-940 (macOS 12).
997    pub fn subcaches<'data, R: ReadRef<'data>>(
998        &self,
999        endian: E,
1000        data: R,
1001    ) -> Result<Option<DyldSubCacheSlice<'data, E>>> {
1002        let header_size = self.mapping_offset.get(endian);
1003        if header_size >= MIN_HEADER_SIZE_SUBCACHES_V2 {
1004            let subcaches = data
1005                .read_slice_at::<macho::DyldSubCacheEntryV2<E>>(
1006                    self.sub_cache_array_offset.get(endian).into(),
1007                    self.sub_cache_array_count.get(endian) as usize,
1008                )
1009                .read_error("Invalid dyld subcaches size or alignment")?;
1010            Ok(Some(DyldSubCacheSlice::V2(subcaches)))
1011        } else if header_size >= MIN_HEADER_SIZE_SUBCACHES_V1 {
1012            let subcaches = data
1013                .read_slice_at::<macho::DyldSubCacheEntryV1<E>>(
1014                    self.sub_cache_array_offset.get(endian).into(),
1015                    self.sub_cache_array_count.get(endian) as usize,
1016                )
1017                .read_error("Invalid dyld subcaches size or alignment")?;
1018            Ok(Some(DyldSubCacheSlice::V1(subcaches)))
1019        } else {
1020            Ok(None)
1021        }
1022    }
1023
1024    /// Return the UUID for the .symbols subcache, if present.
1025    pub fn symbols_subcache_uuid(&self, endian: E) -> Option<[u8; 16]> {
1026        if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES_V1 {
1027            let uuid = self.symbol_file_uuid;
1028            if uuid != [0; 16] {
1029                return Some(uuid);
1030            }
1031        }
1032        None
1033    }
1034
1035    /// Return the image information table.
1036    pub fn images<'data, R: ReadRef<'data>>(
1037        &self,
1038        endian: E,
1039        data: R,
1040    ) -> Result<&'data [macho::DyldCacheImageInfo<E>]> {
1041        if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES_V1 {
1042            data.read_slice_at::<macho::DyldCacheImageInfo<E>>(
1043                self.images_offset.get(endian).into(),
1044                self.images_count.get(endian) as usize,
1045            )
1046            .read_error("Invalid dyld cache image size or alignment")
1047        } else {
1048            data.read_slice_at::<macho::DyldCacheImageInfo<E>>(
1049                self.images_offset_old.get(endian).into(),
1050                self.images_count_old.get(endian) as usize,
1051            )
1052            .read_error("Invalid dyld cache image size or alignment")
1053        }
1054    }
1055}
1056
1057impl<E: Endian> macho::DyldCacheImageInfo<E> {
1058    /// The file system path of this image.
1059    ///
1060    /// `data` should be the main cache file, not the subcache containing the image.
1061    pub fn path<'data, R: ReadRef<'data>>(&self, endian: E, data: R) -> Result<&'data [u8]> {
1062        let r_start = self.path_file_offset.get(endian).into();
1063        let r_end = data.len().read_error("Couldn't get data len()")?;
1064        data.read_bytes_at_until(r_start..r_end, 0)
1065            .read_error("Couldn't read dyld cache image path")
1066    }
1067}
1068
1069impl<E: Endian> macho::DyldCacheMappingAndSlideInfo<E> {
1070    /// Return the (optional) array of slide information structs
1071    pub fn slide<'data, R: ReadRef<'data>>(
1072        &self,
1073        endian: E,
1074        data: R,
1075    ) -> Result<DyldCacheSlideInfo<'data, E>> {
1076        // TODO: limit further reads to this size?
1077        if self.slide_info_file_size.get(endian) == 0 {
1078            return Ok(DyldCacheSlideInfo::None);
1079        }
1080
1081        let slide_info_file_offset = self.slide_info_file_offset.get(endian);
1082        let version = data
1083            .read_at::<U32<E>>(slide_info_file_offset)
1084            .read_error("Invalid slide info file offset size or alignment")?
1085            .get(endian);
1086        match version {
1087            2 => {
1088                let slide = data
1089                    .read_at::<macho::DyldCacheSlideInfo2<E>>(slide_info_file_offset)
1090                    .read_error("Invalid dyld cache slide info offset or alignment")?;
1091                let page_starts_offset = slide_info_file_offset
1092                    .checked_add(slide.page_starts_offset.get(endian) as u64)
1093                    .read_error("Invalid dyld cache page starts offset")?;
1094                let page_starts = data
1095                    .read_slice_at::<U16<E>>(
1096                        page_starts_offset,
1097                        slide.page_starts_count.get(endian) as usize,
1098                    )
1099                    .read_error("Invalid dyld cache page starts size or alignment")?;
1100                let page_extras_offset = slide_info_file_offset
1101                    .checked_add(slide.page_extras_offset.get(endian) as u64)
1102                    .read_error("Invalid dyld cache page extras offset")?;
1103                let page_extras = data
1104                    .read_slice_at::<U16<E>>(
1105                        page_extras_offset,
1106                        slide.page_extras_count.get(endian) as usize,
1107                    )
1108                    .read_error("Invalid dyld cache page extras size or alignment")?;
1109                Ok(DyldCacheSlideInfo::V2 {
1110                    slide,
1111                    page_starts,
1112                    page_extras,
1113                })
1114            }
1115            3 => {
1116                let slide = data
1117                    .read_at::<macho::DyldCacheSlideInfo3<E>>(slide_info_file_offset)
1118                    .read_error("Invalid dyld cache slide info offset or alignment")?;
1119                let page_starts_offset = slide_info_file_offset
1120                    .checked_add(mem::size_of::<macho::DyldCacheSlideInfo3<E>>() as u64)
1121                    .read_error("Invalid dyld cache page starts offset")?;
1122                let page_starts = data
1123                    .read_slice_at::<U16<E>>(
1124                        page_starts_offset,
1125                        slide.page_starts_count.get(endian) as usize,
1126                    )
1127                    .read_error("Invalid dyld cache page starts size or alignment")?;
1128                Ok(DyldCacheSlideInfo::V3 { slide, page_starts })
1129            }
1130            5 => {
1131                let slide = data
1132                    .read_at::<macho::DyldCacheSlideInfo5<E>>(slide_info_file_offset)
1133                    .read_error("Invalid dyld cache slide info offset or alignment")?;
1134                let page_starts_offset = slide_info_file_offset
1135                    .checked_add(mem::size_of::<macho::DyldCacheSlideInfo5<E>>() as u64)
1136                    .read_error("Invalid dyld cache page starts offset")?;
1137                let page_starts = data
1138                    .read_slice_at::<U16<E>>(
1139                        page_starts_offset,
1140                        slide.page_starts_count.get(endian) as usize,
1141                    )
1142                    .read_error("Invalid dyld cache page starts size or alignment")?;
1143                Ok(DyldCacheSlideInfo::V5 { slide, page_starts })
1144            }
1145            _ => Err(Error("Unsupported dyld cache slide info version")),
1146        }
1147    }
1148}