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#[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 { 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 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 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 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 pub fn architecture(&self) -> Architecture {
158 self.arch
159 }
160
161 #[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 #[inline]
173 pub fn data(&self) -> R {
174 self.data
175 }
176
177 pub fn is_little_endian(&self) -> bool {
179 self.endian.is_little_endian()
180 }
181
182 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 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 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#[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 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 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#[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#[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 pub fn info(&self) -> &'data macho::DyldCacheImageInfo<E> {
299 self.image_info
300 }
301
302 pub fn path(&self) -> Result<&'data str> {
304 let path = self.image_info.path(self.cache.endian, self.cache.data)?;
305 let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?;
307 Ok(path)
308 }
309
310 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 pub fn parse_object(&self) -> Result<File<'data, R>> {
321 File::parse_dyld_cache_image(self)
322 }
323}
324
325#[derive(Debug, Clone, Copy)]
330#[non_exhaustive]
331pub enum DyldCacheMappingSlice<'data, E: Endian = Endianness> {
332 V1(&'data [macho::DyldCacheMappingInfo<E>]),
334 V2(&'data [macho::DyldCacheMappingAndSlideInfo<E>]),
336}
337
338const MIN_HEADER_SIZE_MAPPINGS_V2: u32 = 0x140;
340
341#[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#[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 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 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 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 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 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 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 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#[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#[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 start_index: usize,
623 extra_index: usize,
625 page_offset: u64,
627 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 start_index: usize,
728 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 start_index: usize,
825 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
895pub struct DyldRelocation {
897 pub offset: u64,
902 pub value: u64,
904 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
918pub struct DyldRelocationAuth {
922 pub key: macho::PtrauthKey,
924 pub diversity: u16,
926 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 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 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 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 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 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 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 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 pub fn slide<'data, R: ReadRef<'data>>(
1072 &self,
1073 endian: E,
1074 data: R,
1075 ) -> Result<DyldCacheSlideInfo<'data, E>> {
1076 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}