macho_core/model/
macho_file.rs1use crate::error::{Error, Result};
2use crate::format::io::endian::Endian;
3use crate::model::addr::map::{AddressMap, MappingEntry};
4use crate::model::addr::types::{Rva, ThinFileOffset, Va};
5use crate::model::header::{Bitness, MachoHeader};
6use crate::model::load_command::{LoadCommand, ParsedLoadCommand};
7use crate::model::names::SegmentName;
8use crate::model::section::Section;
9use crate::model::segment::Segment;
10
11pub struct MachoFile<'data> {
13 bytes: &'data [u8],
14 header: MachoHeader,
15 load_commands: Vec<ParsedLoadCommand>,
16 segments: Vec<Segment>,
17 endian: Endian,
18 bitness: Bitness,
19 derived: DerivedIndexes,
20}
21
22struct DerivedIndexes {
23 address_map: AddressMap,
24 uuid: Option<[u8; 16]>,
25 image_base: Va,
26}
27
28impl DerivedIndexes {
29 fn build(segments: &[Segment], load_commands: &[ParsedLoadCommand]) -> Result<Self> {
30 let entries: Vec<MappingEntry> = segments
31 .iter()
32 .map(|segment| {
33 MappingEntry::try_new(
34 segment.file_offset,
35 segment.file_size,
36 segment.vm_addr,
37 segment.vm_size,
38 )
39 })
40 .collect::<Result<_>>()?;
41
42 let address_map = AddressMap::try_new(entries)?;
43
44 let uuid = load_commands.iter().find_map(|lc| {
45 if let LoadCommand::Uuid(ref d) = lc.kind {
46 Some(d.uuid)
47 } else {
48 None
49 }
50 });
51
52 let image_base = segments
53 .iter()
54 .find(|seg| seg.name == SegmentName::TEXT)
55 .map(|seg| seg.vm_addr)
56 .unwrap_or(Va(0));
57
58 Ok(Self {
59 address_map,
60 uuid,
61 image_base,
62 })
63 }
64}
65
66impl<'data> MachoFile<'data> {
67 pub(crate) fn new(
68 bytes: &'data [u8],
69 header: MachoHeader,
70 load_commands: Vec<ParsedLoadCommand>,
71 segments: Vec<Segment>,
72 endian: Endian,
73 bitness: Bitness,
74 ) -> Result<Self> {
75 let derived = DerivedIndexes::build(&segments, &load_commands)?;
76 Ok(Self {
77 bytes,
78 header,
79 load_commands,
80 segments,
81 endian,
82 bitness,
83 derived,
84 })
85 }
86
87 fn derived(&self) -> &DerivedIndexes {
88 &self.derived
89 }
90
91 pub fn header(&self) -> &MachoHeader {
93 &self.header
94 }
95
96 pub fn load_commands(&self) -> &[ParsedLoadCommand] {
98 &self.load_commands
99 }
100
101 pub fn segments(&self) -> &[Segment] {
103 &self.segments
104 }
105
106 pub fn endian(&self) -> Endian {
108 self.endian
109 }
110
111 pub fn bitness(&self) -> Bitness {
113 self.bitness
114 }
115
116 pub fn bytes(&self) -> &'data [u8] {
118 self.bytes
119 }
120
121 pub fn file_size(&self) -> usize {
123 self.bytes.len()
124 }
125
126 pub fn is_64bit(&self) -> bool {
128 self.bitness == Bitness::Bits64
129 }
130
131 pub fn all_sections(&self) -> impl Iterator<Item = &Section> {
133 self.segments.iter().flat_map(|s| s.sections.iter())
134 }
135
136 pub fn find_load_command(
138 &self,
139 pred: impl Fn(&LoadCommand) -> bool,
140 ) -> Option<&ParsedLoadCommand> {
141 self.load_commands.iter().find(|lc| pred(&lc.kind))
142 }
143
144 pub fn load_command_code(&self, command: &ParsedLoadCommand) -> Result<u32> {
146 let start = command.file_offset().as_usize();
147 let raw: [u8; 4] = self
148 .bytes
149 .get(start..start.saturating_add(4))
150 .and_then(|bytes| bytes.try_into().ok())
151 .ok_or_else(|| Error::bounds(start as u64, 4, self.bytes.len() as u64))?;
152 Ok(self.endian.read_u32(raw))
153 }
154
155 pub fn address_map(&self) -> &AddressMap {
157 &self.derived().address_map
158 }
159
160 pub fn uuid(&self) -> Option<&[u8; 16]> {
162 self.derived().uuid.as_ref()
163 }
164
165 pub fn image_base(&self) -> Va {
167 self.derived().image_base
168 }
169
170 pub fn section(&self, seg_name: &str, sect_name: &str) -> Option<&Section> {
172 self.segments.iter().find_map(|seg| {
173 if seg.name == seg_name {
174 seg.sections.iter().find(|s| s.section_name == sect_name)
175 } else {
176 None
177 }
178 })
179 }
180
181 pub fn section_bytes(&self, seg_name: &str, sect_name: &str) -> Result<&'data [u8]> {
183 let section = self
184 .section(seg_name, sect_name)
185 .ok_or_else(|| Error::format(format!("section {seg_name},{sect_name} not found")))?;
186 if section.section_type.is_zerofill() {
187 return Err(Error::format(format!(
188 "section {seg_name},{sect_name} is zero-fill and has no file data"
189 )));
190 }
191 self.read_bytes_at(section.offset, section.size as usize)
192 }
193
194 pub fn read_bytes_at(&self, offset: ThinFileOffset, len: usize) -> Result<&'data [u8]> {
196 let start = offset.as_usize();
197 let end = start
198 .checked_add(len)
199 .ok_or_else(|| Error::bounds(offset.0, len as u64, self.bytes.len() as u64))?;
200 if end > self.bytes.len() {
201 return Err(Error::bounds(offset.0, len as u64, self.bytes.len() as u64));
202 }
203 Ok(&self.bytes[start..end])
204 }
205
206 pub fn read_bytes_at_va(&self, va: Va, len: usize) -> Result<&'data [u8]> {
208 let offset = self.address_map().va_to_thin_offset(va)?;
209 self.read_bytes_at(offset, len)
210 }
211
212 pub fn read_bytes_at_rva(&self, rva: Rva, len: usize) -> Result<&'data [u8]> {
214 let va = AddressMap::rva_to_va(rva, self.image_base())?;
215 self.read_bytes_at_va(va, len)
216 }
217}
218
219impl std::fmt::Debug for MachoFile<'_> {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 f.debug_struct("MachFile")
222 .field("header", &self.header)
223 .field("endian", &self.endian)
224 .field("bitness", &self.bitness)
225 .field("num_load_commands", &self.load_commands.len())
226 .field("num_segments", &self.segments.len())
227 .field("size", &self.bytes.len())
228 .finish()
229 }
230}
231
232const _: fn() = || {
238 fn assert_send_sync<T: Send + Sync>() {}
239 assert_send_sync::<MachoFile<'static>>();
240};