Skip to main content

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