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#[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 files: Vec<DyldFile<'data, E, R>>,
21 images: &'data [macho::DyldCacheImageInfo<E>],
22 arch: Architecture,
23}
24
25#[derive(Debug, Clone, Copy)]
30#[non_exhaustive]
31pub enum DyldSubCacheSlice<'data, E: Endian> {
32 V1(&'data [macho::DyldSubCacheEntryV1<E>]),
34 V2(&'data [macho::DyldSubCacheEntryV2<E>]),
36}
37
38const MIN_HEADER_SIZE_SUBCACHES_V1: u32 = 0x1c8;
40
41const 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 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 (1..subcaches.len() + 1).map(|i| format!(".{i}")).collect()
62 }
63 DyldSubCacheSlice::V2(subcaches) => {
64 subcaches
66 .iter()
67 .map(|s| {
68 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 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 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 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 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 pub fn architecture(&self) -> Architecture {
167 self.arch
168 }
169
170 #[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 #[inline]
182 pub fn data(&self) -> R {
183 self.data
184 }
185
186 pub fn is_little_endian(&self) -> bool {
188 self.endian.is_little_endian()
189 }
190
191 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 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 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#[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 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 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#[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#[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 pub fn info(&self) -> &'data macho::DyldCacheImageInfo<E> {
308 self.image_info
309 }
310
311 pub fn path(&self) -> Result<&'data str> {
313 let path = self.image_info.path(self.cache.endian, self.cache.data)?;
314 let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?;
316 Ok(path)
317 }
318
319 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 pub fn parse_object(&self) -> Result<File<'data, R>> {
330 File::parse_dyld_cache_image(self)
331 }
332}
333
334#[derive(Debug, Clone, Copy)]
339#[non_exhaustive]
340pub enum DyldCacheMappingSlice<'data, E: Endian = Endianness> {
341 V1(&'data [macho::DyldCacheMappingInfo<E>]),
343 V2(&'data [macho::DyldCacheMappingAndSlideInfo<E>]),
345}
346
347const MIN_HEADER_SIZE_MAPPINGS_V2: u32 = 0x140;
349
350#[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#[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 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 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 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 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 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 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 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#[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#[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 start_index: usize,
632 extra_index: usize,
634 page_offset: u64,
636 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 start_index: usize,
737 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 start_index: usize,
834 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
904pub struct DyldRelocation {
906 pub offset: u64,
911 pub value: u64,
913 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
927pub struct DyldRelocationAuth {
931 pub key: macho::PtrauthKey,
933 pub diversity: u16,
935 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 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 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 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 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 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 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 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 pub fn slide<'data, R: ReadRef<'data>>(
1081 &self,
1082 endian: E,
1083 data: R,
1084 ) -> Result<DyldCacheSlideInfo<'data, E>> {
1085 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}