Skip to main content

object/read/macho/
section.rs

1use core::fmt::Debug;
2use core::{fmt, mem, result, slice, str};
3
4use crate::endian::{self, Endianness, U32};
5use crate::macho;
6use crate::pod::Pod;
7use crate::read::{
8    self, gnu_compression, CompressedData, CompressedFileRange, Error, ObjectSection, ReadError,
9    ReadRef, RelocationMap, Result, SectionFlags, SectionIndex, SectionKind,
10};
11
12use super::{MachHeader, MachOFile, MachORelocationIterator};
13
14/// An iterator for the sections in a [`MachOFile32`](super::MachOFile32).
15pub type MachOSectionIterator32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
16    MachOSectionIterator<'data, 'file, macho::MachHeader32<Endian>, R>;
17/// An iterator for the sections in a [`MachOFile64`](super::MachOFile64).
18pub type MachOSectionIterator64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
19    MachOSectionIterator<'data, 'file, macho::MachHeader64<Endian>, R>;
20
21/// An iterator for the sections in a [`MachOFile`].
22pub struct MachOSectionIterator<'data, 'file, Mach, R = &'data [u8]>
23where
24    Mach: MachHeader,
25    R: ReadRef<'data>,
26{
27    pub(super) file: &'file MachOFile<'data, Mach, R>,
28    pub(super) iter: slice::Iter<'file, MachOSectionInternal<'data, Mach, R>>,
29}
30
31impl<'data, 'file, Mach, R> fmt::Debug for MachOSectionIterator<'data, 'file, Mach, R>
32where
33    Mach: MachHeader,
34    R: ReadRef<'data>,
35{
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        // It's painful to do much better than this
38        f.debug_struct("MachOSectionIterator").finish()
39    }
40}
41
42impl<'data, 'file, Mach, R> Iterator for MachOSectionIterator<'data, 'file, Mach, R>
43where
44    Mach: MachHeader,
45    R: ReadRef<'data>,
46{
47    type Item = MachOSection<'data, 'file, Mach, R>;
48
49    fn next(&mut self) -> Option<Self::Item> {
50        self.iter.next().map(|&internal| MachOSection {
51            file: self.file,
52            internal,
53        })
54    }
55}
56
57/// A section in a [`MachOFile32`](super::MachOFile32).
58pub type MachOSection32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
59    MachOSection<'data, 'file, macho::MachHeader32<Endian>, R>;
60/// A section in a [`MachOFile64`](super::MachOFile64).
61pub type MachOSection64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
62    MachOSection<'data, 'file, macho::MachHeader64<Endian>, R>;
63
64/// A section in a [`MachOFile`].
65///
66/// Most functionality is provided by the [`ObjectSection`] trait implementation.
67#[derive(Debug)]
68pub struct MachOSection<'data, 'file, Mach, R = &'data [u8]>
69where
70    Mach: MachHeader,
71    R: ReadRef<'data>,
72{
73    pub(super) file: &'file MachOFile<'data, Mach, R>,
74    pub(super) internal: MachOSectionInternal<'data, Mach, R>,
75}
76
77impl<'data, 'file, Mach, R> MachOSection<'data, 'file, Mach, R>
78where
79    Mach: MachHeader,
80    R: ReadRef<'data>,
81{
82    /// Get the Mach-O file containing this section.
83    pub fn macho_file(&self) -> &'file MachOFile<'data, Mach, R> {
84        self.file
85    }
86
87    /// Get the raw Mach-O section structure.
88    pub fn macho_section(&self) -> &'data Mach::Section {
89        self.internal.section
90    }
91
92    /// Get the raw Mach-O relocation entries.
93    pub fn macho_relocations(&self) -> Result<&'data [macho::Relocation<Mach::Endian>]> {
94        self.internal
95            .section
96            .relocations(self.file.endian, self.internal.data)
97    }
98
99    fn bytes(&self) -> Result<&'data [u8]> {
100        self.internal
101            .section
102            .data(self.file.endian, self.internal.data)
103            .read_error("Invalid Mach-O section size or offset")
104    }
105
106    // Try GNU-style "ZLIB" header decompression.
107    fn maybe_compressed_gnu(&self) -> Result<Option<CompressedFileRange>> {
108        if !self
109            .name()
110            .map_or(false, |name| name.starts_with("__zdebug_"))
111        {
112            return Ok(None);
113        }
114        let (section_offset, section_size) = self
115            .file_range()
116            .read_error("Invalid ELF GNU compressed section type")?;
117        gnu_compression::compressed_file_range(self.internal.data, section_offset, section_size)
118            .map(Some)
119    }
120}
121
122impl<'data, 'file, Mach, R> read::private::Sealed for MachOSection<'data, 'file, Mach, R>
123where
124    Mach: MachHeader,
125    R: ReadRef<'data>,
126{
127}
128
129impl<'data, 'file, Mach, R> ObjectSection<'data> for MachOSection<'data, 'file, Mach, R>
130where
131    Mach: MachHeader,
132    R: ReadRef<'data>,
133{
134    type RelocationIterator = MachORelocationIterator<'data, 'file, Mach, R>;
135
136    #[inline]
137    fn index(&self) -> SectionIndex {
138        self.internal.index
139    }
140
141    #[inline]
142    fn address(&self) -> u64 {
143        self.internal.section.addr(self.file.endian).into()
144    }
145
146    #[inline]
147    fn size(&self) -> u64 {
148        self.internal.section.size(self.file.endian).into()
149    }
150
151    #[inline]
152    fn align(&self) -> u64 {
153        let align = self.internal.section.align(self.file.endian);
154        if align < 64 {
155            1 << align
156        } else {
157            0
158        }
159    }
160
161    #[inline]
162    fn file_range(&self) -> Option<(u64, u64)> {
163        self.internal.section.file_range(self.file.endian)
164    }
165
166    #[inline]
167    fn data(&self) -> Result<&'data [u8]> {
168        self.bytes()
169    }
170
171    fn data_range(&self, address: u64, size: u64) -> Result<Option<&'data [u8]>> {
172        Ok(read::util::data_range(
173            self.bytes()?,
174            self.address(),
175            address,
176            size,
177        ))
178    }
179
180    fn compressed_file_range(&self) -> Result<CompressedFileRange> {
181        Ok(if let Some(data) = self.maybe_compressed_gnu()? {
182            data
183        } else {
184            CompressedFileRange::none(self.file_range())
185        })
186    }
187
188    fn compressed_data(&self) -> read::Result<CompressedData<'data>> {
189        self.compressed_file_range()?.data(self.file.data.0)
190    }
191
192    #[inline]
193    fn name_bytes(&self) -> Result<&'data [u8]> {
194        Ok(self.internal.section.name())
195    }
196
197    #[inline]
198    fn name(&self) -> Result<&'data str> {
199        str::from_utf8(self.internal.section.name())
200            .ok()
201            .read_error("Non UTF-8 Mach-O section name")
202    }
203
204    #[inline]
205    fn segment_name_bytes(&self) -> Result<Option<&[u8]>> {
206        Ok(Some(self.internal.section.segment_name()))
207    }
208
209    #[inline]
210    fn segment_name(&self) -> Result<Option<&str>> {
211        Ok(Some(
212            str::from_utf8(self.internal.section.segment_name())
213                .ok()
214                .read_error("Non UTF-8 Mach-O segment name")?,
215        ))
216    }
217
218    fn kind(&self) -> SectionKind {
219        self.internal.kind
220    }
221
222    fn relocations(&self) -> MachORelocationIterator<'data, 'file, Mach, R> {
223        MachORelocationIterator {
224            file: self.file,
225            relocations: self.macho_relocations().unwrap_or(&[]).iter(),
226        }
227    }
228
229    fn relocation_map(&self) -> read::Result<RelocationMap> {
230        RelocationMap::new(self.file, self)
231    }
232
233    fn flags(&self) -> SectionFlags {
234        SectionFlags::MachO {
235            flags: self.internal.section.flags(self.file.endian),
236        }
237    }
238}
239
240#[derive(Debug, Clone, Copy)]
241pub(super) struct MachOSectionInternal<'data, Mach: MachHeader, R: ReadRef<'data>> {
242    pub index: SectionIndex,
243    pub kind: SectionKind,
244    pub section: &'data Mach::Section,
245    /// The data for the file that contains the section data.
246    ///
247    /// This is required for dyld caches, where this may be a different subcache
248    /// from the file containing the Mach-O load commands.
249    pub data: R,
250}
251
252impl<'data, Mach: MachHeader, R: ReadRef<'data>> MachOSectionInternal<'data, Mach, R> {
253    pub(super) fn parse(index: SectionIndex, section: &'data Mach::Section, data: R) -> Self {
254        // TODO: we don't validate flags, should we?
255        let kind = match (section.segment_name(), section.name()) {
256            (b"__TEXT", b"__text") => SectionKind::Text,
257            (b"__TEXT", b"__const") => SectionKind::ReadOnlyData,
258            (b"__TEXT", b"__cstring") => SectionKind::ReadOnlyString,
259            (b"__TEXT", b"__literal4") => SectionKind::ReadOnlyData,
260            (b"__TEXT", b"__literal8") => SectionKind::ReadOnlyData,
261            (b"__TEXT", b"__literal16") => SectionKind::ReadOnlyData,
262            (b"__TEXT", b"__eh_frame") => SectionKind::ReadOnlyData,
263            (b"__TEXT", b"__gcc_except_tab") => SectionKind::ReadOnlyData,
264            (b"__DATA", b"__data") => SectionKind::Data,
265            (b"__DATA", b"__const") => SectionKind::ReadOnlyData,
266            (b"__DATA", b"__bss") => SectionKind::UninitializedData,
267            (b"__DATA", b"__common") => SectionKind::Common,
268            (b"__DATA", b"__thread_data") => SectionKind::Tls,
269            (b"__DATA", b"__thread_bss") => SectionKind::UninitializedTls,
270            (b"__DATA", b"__thread_vars") => SectionKind::TlsVariables,
271            (b"__DWARF", _) => SectionKind::Debug,
272            _ => SectionKind::Unknown,
273        };
274        MachOSectionInternal {
275            index,
276            kind,
277            section,
278            data,
279        }
280    }
281}
282
283/// A trait for generic access to [`macho::Section32`] and [`macho::Section64`].
284#[allow(missing_docs)]
285pub trait Section: Debug + Pod {
286    type Word: Into<u64>;
287    type Endian: endian::Endian;
288
289    fn sectname(&self) -> &[u8; 16];
290    fn segname(&self) -> &[u8; 16];
291    fn addr(&self, endian: Self::Endian) -> Self::Word;
292    fn size(&self, endian: Self::Endian) -> Self::Word;
293    fn offset(&self, endian: Self::Endian) -> u32;
294    fn align(&self, endian: Self::Endian) -> u32;
295    fn reloff(&self, endian: Self::Endian) -> u32;
296    fn nreloc(&self, endian: Self::Endian) -> u32;
297    fn flags(&self, endian: Self::Endian) -> u32;
298    fn reserved1(&self, endian: Self::Endian) -> u32;
299    fn reserved2(&self, endian: Self::Endian) -> u32;
300
301    /// Return the `sectname` bytes up until the null terminator.
302    fn name(&self) -> &[u8] {
303        let sectname = &self.sectname()[..];
304        match memchr::memchr(b'\0', sectname) {
305            Some(end) => &sectname[..end],
306            None => sectname,
307        }
308    }
309
310    /// Return the `segname` bytes up until the null terminator.
311    fn segment_name(&self) -> &[u8] {
312        let segname = &self.segname()[..];
313        match memchr::memchr(b'\0', segname) {
314            Some(end) => &segname[..end],
315            None => segname,
316        }
317    }
318
319    /// Return the section type from the flags field.
320    fn section_type(&self, endian: Self::Endian) -> u32 {
321        self.flags(endian) & macho::SECTION_TYPE
322    }
323
324    /// Return the offset and size of the section in the file.
325    ///
326    /// Returns `None` for sections that have no data in the file.
327    fn file_range(&self, endian: Self::Endian) -> Option<(u64, u64)> {
328        match self.section_type(endian) {
329            macho::S_ZEROFILL | macho::S_GB_ZEROFILL | macho::S_THREAD_LOCAL_ZEROFILL => None,
330            _ => Some((self.offset(endian).into(), self.size(endian).into())),
331        }
332    }
333
334    /// Return the section data.
335    ///
336    /// Returns `Ok(&[])` if the section has no data.
337    /// Returns `Err` for invalid values.
338    fn data<'data, R: ReadRef<'data>>(
339        &self,
340        endian: Self::Endian,
341        data: R,
342    ) -> result::Result<&'data [u8], ()> {
343        if let Some((offset, size)) = self.file_range(endian) {
344            data.read_bytes_at(offset, size)
345        } else {
346            Ok(&[])
347        }
348    }
349
350    /// Return the relocation array.
351    ///
352    /// Returns `Err` for invalid values.
353    fn relocations<'data, R: ReadRef<'data>>(
354        &self,
355        endian: Self::Endian,
356        data: R,
357    ) -> Result<&'data [macho::Relocation<Self::Endian>]> {
358        data.read_slice_at(self.reloff(endian).into(), self.nreloc(endian) as usize)
359            .read_error("Invalid Mach-O relocations offset or number")
360    }
361
362    /// Return the size of symbol stubs in this section.
363    ///
364    /// Returns 0 if this section does not contain symbol stubs.
365    fn symbol_stub_size(&self, endian: Self::Endian) -> u32 {
366        if self.section_type(endian) == macho::S_SYMBOL_STUBS {
367            self.reserved2(endian)
368        } else {
369            0
370        }
371    }
372
373    /// Return the indirect symbols referenced by this section.
374    ///
375    /// Returns an empty slice if this section does not reference indirect symbols.
376    fn indirect_symbols<'data>(
377        &self,
378        endian: Self::Endian,
379        indirect_symbols: &'data [U32<Self::Endian>],
380    ) -> Result<&'data [U32<Self::Endian>]> {
381        let entry_size = match self.section_type(endian) {
382            macho::S_NON_LAZY_SYMBOL_POINTERS
383            | macho::S_LAZY_SYMBOL_POINTERS
384            | macho::S_LAZY_DYLIB_SYMBOL_POINTERS
385            | macho::S_THREAD_LOCAL_VARIABLE_POINTERS => mem::size_of::<Self::Word>(),
386            macho::S_SYMBOL_STUBS => {
387                let reserved2 = self.reserved2(endian);
388                if reserved2 == 0 {
389                    return Err(Error("Invalid Mach-O stub size"));
390                }
391                reserved2 as usize
392            }
393            _ => return Ok(&[]),
394        };
395        let start = self.reserved1(endian) as usize;
396        let count = self.size(endian).into() as usize / entry_size;
397        indirect_symbols
398            .get(start..)
399            .and_then(|symbols| symbols.get(..count))
400            .read_error("Invalid Mach-O indirect symbol index or count")
401    }
402}
403
404impl<Endian: endian::Endian> Section for macho::Section32<Endian> {
405    type Word = u32;
406    type Endian = Endian;
407
408    fn sectname(&self) -> &[u8; 16] {
409        &self.sectname
410    }
411    fn segname(&self) -> &[u8; 16] {
412        &self.segname
413    }
414    fn addr(&self, endian: Self::Endian) -> Self::Word {
415        self.addr.get(endian)
416    }
417    fn size(&self, endian: Self::Endian) -> Self::Word {
418        self.size.get(endian)
419    }
420    fn offset(&self, endian: Self::Endian) -> u32 {
421        self.offset.get(endian)
422    }
423    fn align(&self, endian: Self::Endian) -> u32 {
424        self.align.get(endian)
425    }
426    fn reloff(&self, endian: Self::Endian) -> u32 {
427        self.reloff.get(endian)
428    }
429    fn nreloc(&self, endian: Self::Endian) -> u32 {
430        self.nreloc.get(endian)
431    }
432    fn flags(&self, endian: Self::Endian) -> u32 {
433        self.flags.get(endian)
434    }
435    fn reserved1(&self, endian: Self::Endian) -> u32 {
436        self.reserved1.get(endian)
437    }
438    fn reserved2(&self, endian: Self::Endian) -> u32 {
439        self.reserved2.get(endian)
440    }
441}
442
443impl<Endian: endian::Endian> Section for macho::Section64<Endian> {
444    type Word = u64;
445    type Endian = Endian;
446
447    fn sectname(&self) -> &[u8; 16] {
448        &self.sectname
449    }
450    fn segname(&self) -> &[u8; 16] {
451        &self.segname
452    }
453    fn addr(&self, endian: Self::Endian) -> Self::Word {
454        self.addr.get(endian)
455    }
456    fn size(&self, endian: Self::Endian) -> Self::Word {
457        self.size.get(endian)
458    }
459    fn offset(&self, endian: Self::Endian) -> u32 {
460        self.offset.get(endian)
461    }
462    fn align(&self, endian: Self::Endian) -> u32 {
463        self.align.get(endian)
464    }
465    fn reloff(&self, endian: Self::Endian) -> u32 {
466        self.reloff.get(endian)
467    }
468    fn nreloc(&self, endian: Self::Endian) -> u32 {
469        self.nreloc.get(endian)
470    }
471    fn flags(&self, endian: Self::Endian) -> u32 {
472        self.flags.get(endian)
473    }
474    fn reserved1(&self, endian: Self::Endian) -> u32 {
475        self.reserved1.get(endian)
476    }
477    fn reserved2(&self, endian: Self::Endian) -> u32 {
478        self.reserved2.get(endian)
479    }
480}