Skip to main content

macho_core/model/
macho_file.rs

1use 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
11/// The MachoFile type.
12pub 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    /// Performs header.
92    pub fn header(&self) -> &MachoHeader {
93        &self.header
94    }
95
96    /// Performs load_commands.
97    pub fn load_commands(&self) -> &[ParsedLoadCommand] {
98        &self.load_commands
99    }
100
101    /// Performs segments.
102    pub fn segments(&self) -> &[Segment] {
103        &self.segments
104    }
105
106    /// Performs endian.
107    pub fn endian(&self) -> Endian {
108        self.endian
109    }
110
111    /// Performs bitness.
112    pub fn bitness(&self) -> Bitness {
113        self.bitness
114    }
115
116    /// Performs bytes.
117    pub fn bytes(&self) -> &'data [u8] {
118        self.bytes
119    }
120
121    /// Performs file_size.
122    pub fn file_size(&self) -> usize {
123        self.bytes.len()
124    }
125
126    /// Performs is_64bit.
127    pub fn is_64bit(&self) -> bool {
128        self.bitness == Bitness::Bits64
129    }
130
131    /// Flat iterator over all sections across all segments.
132    pub fn all_sections(&self) -> impl Iterator<Item = &Section> {
133        self.segments.iter().flat_map(|s| s.sections.iter())
134    }
135
136    /// Find the first load command whose kind matches the predicate.
137    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    /// Return the exact numeric command word retained in the input.
145    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    /// Performs address_map.
156    pub fn address_map(&self) -> &AddressMap {
157        &self.derived().address_map
158    }
159
160    /// Performs uuid.
161    pub fn uuid(&self) -> Option<&[u8; 16]> {
162        self.derived().uuid.as_ref()
163    }
164
165    /// Performs image_base.
166    pub fn image_base(&self) -> Va {
167        self.derived().image_base
168    }
169
170    /// Performs section.
171    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    /// Performs section_bytes.
182    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    /// Performs read_bytes_at.
195    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    /// Performs read_bytes_at_va.
207    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    /// Performs read_bytes_at_rva.
213    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
232// Safety: MachFile is Send + Sync because:
233// - &[u8] is Send + Sync
234// - OnceLock<DerivedIndexes> is Sync (and Send)
235// - All other fields are Send + Sync
236// This is verified by the static assertions below.
237const _: fn() = || {
238    fn assert_send_sync<T: Send + Sync>() {}
239    assert_send_sync::<MachoFile<'static>>();
240};