1 use core::slice;
2 
3 use crate::read::{Error, File, ReadError, ReadRef, Result};
4 use crate::{macho, Architecture, Endian, Endianness};
5 
6 /// A parsed representation of the dyld shared cache.
7 #[derive(Debug)]
8 pub struct DyldCache<'data, E = Endianness, R = &'data [u8]>
9 where
10     E: Endian,
11     R: ReadRef<'data>,
12 {
13     endian: E,
14     data: R,
15     header: &'data macho::DyldCacheHeader<E>,
16     mappings: &'data [macho::DyldCacheMappingInfo<E>],
17     images: &'data [macho::DyldCacheImageInfo<E>],
18     arch: Architecture,
19 }
20 
21 impl<'data, E, R> DyldCache<'data, E, R>
22 where
23     E: Endian,
24     R: ReadRef<'data>,
25 {
26     /// Parse the raw dyld shared cache data.
parse(data: R) -> Result<Self>27     pub fn parse(data: R) -> Result<Self> {
28         let header = macho::DyldCacheHeader::parse(data)?;
29         let (arch, endian) = header.parse_magic()?;
30         let mappings = header.mappings(endian, data)?;
31         let images = header.images(endian, data)?;
32         Ok(DyldCache {
33             endian,
34             data,
35             header,
36             mappings,
37             images,
38             arch,
39         })
40     }
41 
42     /// Get the architecture type of the file.
architecture(&self) -> Architecture43     pub fn architecture(&self) -> Architecture {
44         self.arch
45     }
46 
47     /// Get the endianness of the file.
48     #[inline]
endianness(&self) -> Endianness49     pub fn endianness(&self) -> Endianness {
50         if self.is_little_endian() {
51             Endianness::Little
52         } else {
53             Endianness::Big
54         }
55     }
56 
57     /// Return true if the file is little endian, false if it is big endian.
is_little_endian(&self) -> bool58     pub fn is_little_endian(&self) -> bool {
59         self.endian.is_little_endian()
60     }
61 
62     /// Iterate over the images in this cache.
images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R>63     pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> {
64         DyldCacheImageIterator {
65             cache: self,
66             iter: self.images.iter(),
67         }
68     }
69 }
70 
71 /// An iterator over all the images (dylibs) in the dyld shared cache.
72 #[derive(Debug)]
73 pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]>
74 where
75     E: Endian,
76     R: ReadRef<'data>,
77 {
78     cache: &'cache DyldCache<'data, E, R>,
79     iter: slice::Iter<'data, macho::DyldCacheImageInfo<E>>,
80 }
81 
82 impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, E, R>
83 where
84     E: Endian,
85     R: ReadRef<'data>,
86 {
87     type Item = DyldCacheImage<'data, E, R>;
88 
next(&mut self) -> Option<DyldCacheImage<'data, E, R>>89     fn next(&mut self) -> Option<DyldCacheImage<'data, E, R>> {
90         let image_info = self.iter.next()?;
91         Some(DyldCacheImage {
92             endian: self.cache.endian,
93             data: self.cache.data,
94             mappings: self.cache.mappings,
95             image_info,
96         })
97     }
98 }
99 
100 /// One image (dylib) from inside the dyld shared cache.
101 #[derive(Debug)]
102 pub struct DyldCacheImage<'data, E = Endianness, R = &'data [u8]>
103 where
104     E: Endian,
105     R: ReadRef<'data>,
106 {
107     endian: E,
108     data: R,
109     mappings: &'data [macho::DyldCacheMappingInfo<E>],
110     image_info: &'data macho::DyldCacheImageInfo<E>,
111 }
112 
113 impl<'data, E, R> DyldCacheImage<'data, E, R>
114 where
115     E: Endian,
116     R: ReadRef<'data>,
117 {
118     /// The file system path of this image.
path(&self) -> Result<&'data str>119     pub fn path(&self) -> Result<&'data str> {
120         let path = self.image_info.path(self.endian, self.data)?;
121         // The path should always be ascii, so from_utf8 should alway succeed.
122         let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?;
123         Ok(path)
124     }
125 
126     /// The offset in the dyld cache file where this image starts.
file_offset(&self) -> Result<u64>127     pub fn file_offset(&self) -> Result<u64> {
128         self.image_info.file_offset(self.endian, self.mappings)
129     }
130 
131     /// Parse this image into an Object.
parse_object(&self) -> Result<File<'data, R>>132     pub fn parse_object(&self) -> Result<File<'data, R>> {
133         File::parse_at(self.data, self.file_offset()?)
134     }
135 }
136 
137 impl<E: Endian> macho::DyldCacheHeader<E> {
138     /// Read the dyld cache header.
parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self>139     pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> {
140         data.read_at::<macho::DyldCacheHeader<E>>(0)
141             .read_error("Invalid dyld cache header size or alignment")
142     }
143 
144     /// Returns (arch, endian) based on the magic string.
parse_magic(&self) -> Result<(Architecture, E)>145     pub fn parse_magic(&self) -> Result<(Architecture, E)> {
146         let (arch, is_big_endian) = match &self.magic {
147             b"dyld_v1    i386\0" => (Architecture::I386, false),
148             b"dyld_v1  x86_64\0" => (Architecture::X86_64, false),
149             b"dyld_v1 x86_64h\0" => (Architecture::X86_64, false),
150             b"dyld_v1     ppc\0" => (Architecture::PowerPc, true),
151             b"dyld_v1   armv6\0" => (Architecture::Arm, false),
152             b"dyld_v1   armv7\0" => (Architecture::Arm, false),
153             b"dyld_v1  armv7f\0" => (Architecture::Arm, false),
154             b"dyld_v1  armv7s\0" => (Architecture::Arm, false),
155             b"dyld_v1  armv7k\0" => (Architecture::Arm, false),
156             b"dyld_v1   arm64\0" => (Architecture::Aarch64, false),
157             b"dyld_v1  arm64e\0" => (Architecture::Aarch64, false),
158             _ => return Err(Error("Unrecognized dyld cache magic")),
159         };
160         let endian =
161             E::from_big_endian(is_big_endian).read_error("Unsupported dyld cache endian")?;
162         Ok((arch, endian))
163     }
164 
165     /// Return the mapping information table.
mappings<'data, R: ReadRef<'data>>( &self, endian: E, data: R, ) -> Result<&'data [macho::DyldCacheMappingInfo<E>]>166     pub fn mappings<'data, R: ReadRef<'data>>(
167         &self,
168         endian: E,
169         data: R,
170     ) -> Result<&'data [macho::DyldCacheMappingInfo<E>]> {
171         data.read_slice_at::<macho::DyldCacheMappingInfo<E>>(
172             self.mapping_offset.get(endian).into(),
173             self.mapping_count.get(endian) as usize,
174         )
175         .read_error("Invalid dyld cache mapping size or alignment")
176     }
177 
178     /// Return the image information table.
images<'data, R: ReadRef<'data>>( &self, endian: E, data: R, ) -> Result<&'data [macho::DyldCacheImageInfo<E>]>179     pub fn images<'data, R: ReadRef<'data>>(
180         &self,
181         endian: E,
182         data: R,
183     ) -> Result<&'data [macho::DyldCacheImageInfo<E>]> {
184         data.read_slice_at::<macho::DyldCacheImageInfo<E>>(
185             self.images_offset.get(endian).into(),
186             self.images_count.get(endian) as usize,
187         )
188         .read_error("Invalid dyld cache image size or alignment")
189     }
190 }
191 
192 impl<E: Endian> macho::DyldCacheImageInfo<E> {
193     /// The file system path of this image.
path<'data, R: ReadRef<'data>>(&self, endian: E, data: R) -> Result<&'data [u8]>194     pub fn path<'data, R: ReadRef<'data>>(&self, endian: E, data: R) -> Result<&'data [u8]> {
195         let r_start = self.path_file_offset.get(endian).into();
196         let r_end = data.len().read_error("Couldn't get data len()")?;
197         data.read_bytes_at_until(r_start..r_end, 0)
198             .read_error("Couldn't read dyld cache image path")
199     }
200 
201     /// Find the file offset of the image by looking up its address in the mappings.
file_offset( &self, endian: E, mappings: &[macho::DyldCacheMappingInfo<E>], ) -> Result<u64>202     pub fn file_offset(
203         &self,
204         endian: E,
205         mappings: &[macho::DyldCacheMappingInfo<E>],
206     ) -> Result<u64> {
207         let address = self.address.get(endian);
208         for mapping in mappings {
209             let mapping_address = mapping.address.get(endian);
210             if address >= mapping_address
211                 && address < mapping_address.wrapping_add(mapping.size.get(endian))
212             {
213                 return Ok(address - mapping_address + mapping.file_offset.get(endian));
214             }
215         }
216         Err(Error("Invalid dyld cache image address"))
217     }
218 }
219