Skip to main content

object/read/macho/
symbol.rs

1use alloc::vec::Vec;
2use core::fmt::Debug;
3use core::{fmt, slice, str};
4
5use crate::endian::{self, Endianness};
6use crate::macho;
7use crate::pod::Pod;
8use crate::read::util::StringTable;
9use crate::read::{
10    self, ObjectMap, ObjectMapEntry, ObjectMapFile, ObjectSymbol, ObjectSymbolTable, ReadError,
11    ReadRef, Result, SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap,
12    SymbolMapEntry, SymbolScope, SymbolSection,
13};
14
15use super::{MachHeader, MachOFile, Section};
16
17/// A table of symbol entries in a Mach-O file.
18///
19/// Also includes the string table used for the symbol names.
20///
21/// Returned by [`macho::SymtabCommand::symbols`].
22#[derive(Debug, Clone, Copy)]
23pub struct SymbolTable<'data, Mach: MachHeader, R = &'data [u8]>
24where
25    R: ReadRef<'data>,
26{
27    symbols: &'data [Mach::Nlist],
28    strings: StringTable<'data, R>,
29}
30
31impl<'data, Mach: MachHeader, R: ReadRef<'data>> Default for SymbolTable<'data, Mach, R> {
32    fn default() -> Self {
33        SymbolTable {
34            symbols: &[],
35            strings: Default::default(),
36        }
37    }
38}
39
40impl<'data, Mach: MachHeader, R: ReadRef<'data>> SymbolTable<'data, Mach, R> {
41    #[inline]
42    pub(super) fn new(symbols: &'data [Mach::Nlist], strings: StringTable<'data, R>) -> Self {
43        SymbolTable { symbols, strings }
44    }
45
46    /// Return the string table used for the symbol names.
47    #[inline]
48    pub fn strings(&self) -> StringTable<'data, R> {
49        self.strings
50    }
51
52    /// Return the symbol table.
53    #[inline]
54    pub fn symbols(&self) -> &'data [Mach::Nlist] {
55        self.symbols
56    }
57
58    /// Iterate over the symbols.
59    #[inline]
60    pub fn iter(&self) -> slice::Iter<'data, Mach::Nlist> {
61        self.symbols.iter()
62    }
63
64    /// Iterate over the symbols and their indices.
65    #[inline]
66    pub fn enumerate(
67        &self,
68    ) -> impl Iterator<Item = (SymbolIndex, &'data Mach::Nlist)> + use<'data, Mach, R> {
69        self.iter()
70            .enumerate()
71            .map(|(i, sym)| (SymbolIndex(i), sym))
72    }
73
74    /// Return true if the symbol table is empty.
75    #[inline]
76    pub fn is_empty(&self) -> bool {
77        self.symbols.is_empty()
78    }
79
80    /// The number of symbols.
81    #[inline]
82    pub fn len(&self) -> usize {
83        self.symbols.len()
84    }
85
86    /// Return the symbol at the given index.
87    pub fn symbol(&self, index: SymbolIndex) -> Result<&'data Mach::Nlist> {
88        self.symbols
89            .get(index.0)
90            .read_error("Invalid Mach-O symbol index")
91    }
92
93    /// Return the symbol name for the given symbol.
94    pub fn symbol_name(
95        &self,
96        endian: Mach::Endian,
97        symbol: &'data Mach::Nlist,
98    ) -> read::Result<&'data [u8]> {
99        symbol.name(endian, self.strings)
100    }
101
102    /// Construct a map from addresses to a user-defined map entry.
103    pub fn map<Entry: SymbolMapEntry, F: Fn(&'data Mach::Nlist) -> Option<Entry>>(
104        &self,
105        f: F,
106    ) -> SymbolMap<Entry> {
107        let mut symbols = Vec::new();
108        for nlist in self.symbols {
109            if !nlist.is_definition() {
110                continue;
111            }
112            if let Some(entry) = f(nlist) {
113                symbols.push(entry);
114            }
115        }
116        SymbolMap::new(symbols)
117    }
118
119    /// Construct a map from addresses to symbol names and object file names.
120    pub fn object_map(&self, endian: Mach::Endian) -> ObjectMap<'data> {
121        let mut symbols = Vec::new();
122        let mut objects = Vec::new();
123        let mut object = None;
124        let mut current_function = None;
125        // Each module starts with one or two N_SO symbols (path, or directory + filename)
126        // and one N_OSO symbol. The module is terminated by an empty N_SO symbol.
127        for nlist in self.symbols {
128            let Some(n_type) = nlist.n_type().stab() else {
129                continue;
130            };
131            // TODO: includes global symbols too (N_GSYM). These may need to get their
132            // address from regular symbols though.
133            match n_type {
134                macho::N_SO => {
135                    object = None;
136                }
137                macho::N_OSO => {
138                    object = None;
139                    if let Ok(name) = nlist.name(endian, self.strings) {
140                        if !name.is_empty() {
141                            object = Some(objects.len());
142                            // `N_OSO` symbol names can be either `/path/to/object.o`
143                            // or `/path/to/archive.a(object.o)`.
144                            let (path, member) = name
145                                .split_last()
146                                .and_then(|(last, head)| {
147                                    if *last != b')' {
148                                        return None;
149                                    }
150                                    let index = head.iter().position(|&x| x == b'(')?;
151                                    let (archive, rest) = head.split_at(index);
152                                    Some((archive, Some(&rest[1..])))
153                                })
154                                .unwrap_or((name, None));
155                            objects.push(ObjectMapFile::new(path, member));
156                        }
157                    }
158                }
159                macho::N_FUN => {
160                    if let Ok(name) = nlist.name(endian, self.strings) {
161                        if !name.is_empty() {
162                            current_function = Some((name, nlist.n_value(endian).into()))
163                        } else if let Some((name, address)) = current_function.take() {
164                            if let Some(object) = object {
165                                symbols.push(ObjectMapEntry::new(
166                                    address,
167                                    nlist.n_value(endian).into(),
168                                    name,
169                                    object,
170                                ));
171                            }
172                        }
173                    }
174                }
175                macho::N_STSYM => {
176                    // Static symbols have a single entry with the address of the symbol
177                    // but no size
178                    if let Ok(name) = nlist.name(endian, self.strings) {
179                        if let Some(object) = object {
180                            symbols.push(ObjectMapEntry::new(
181                                nlist.n_value(endian).into(),
182                                0,
183                                name,
184                                object,
185                            ));
186                        }
187                    }
188                }
189                _ => {}
190            }
191        }
192        ObjectMap::new(symbols, objects)
193    }
194}
195
196/// A symbol table in a [`MachOFile32`](super::MachOFile32).
197pub type MachOSymbolTable32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
198    MachOSymbolTable<'data, 'file, macho::MachHeader32<Endian>, R>;
199/// A symbol table in a [`MachOFile64`](super::MachOFile64).
200pub type MachOSymbolTable64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
201    MachOSymbolTable<'data, 'file, macho::MachHeader64<Endian>, R>;
202
203/// A symbol table in a [`MachOFile`].
204#[derive(Debug, Clone, Copy)]
205pub struct MachOSymbolTable<'data, 'file, Mach, R = &'data [u8]>
206where
207    Mach: MachHeader,
208    R: ReadRef<'data>,
209{
210    pub(super) file: &'file MachOFile<'data, Mach, R>,
211}
212
213impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbolTable<'data, 'file, Mach, R>
214where
215    Mach: MachHeader,
216    R: ReadRef<'data>,
217{
218}
219
220impl<'data, 'file, Mach, R> ObjectSymbolTable<'data> for MachOSymbolTable<'data, 'file, Mach, R>
221where
222    Mach: MachHeader,
223    R: ReadRef<'data>,
224{
225    type Symbol = MachOSymbol<'data, 'file, Mach, R>;
226    type SymbolIterator = MachOSymbolIterator<'data, 'file, Mach, R>;
227
228    fn symbols(&self) -> Self::SymbolIterator {
229        MachOSymbolIterator::new(self.file)
230    }
231
232    fn symbol_by_index(&self, index: SymbolIndex) -> Result<Self::Symbol> {
233        let nlist = self.file.symbols.symbol(index)?;
234        MachOSymbol::new(self.file, index, nlist).read_error("Unsupported Mach-O symbol index")
235    }
236}
237
238/// An iterator for the symbols in a [`MachOFile32`](super::MachOFile32).
239pub type MachOSymbolIterator32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
240    MachOSymbolIterator<'data, 'file, macho::MachHeader32<Endian>, R>;
241/// An iterator for the symbols in a [`MachOFile64`](super::MachOFile64).
242pub type MachOSymbolIterator64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
243    MachOSymbolIterator<'data, 'file, macho::MachHeader64<Endian>, R>;
244
245/// An iterator for the symbols in a [`MachOFile`].
246pub struct MachOSymbolIterator<'data, 'file, Mach, R = &'data [u8]>
247where
248    Mach: MachHeader,
249    R: ReadRef<'data>,
250{
251    file: &'file MachOFile<'data, Mach, R>,
252    index: SymbolIndex,
253}
254
255impl<'data, 'file, Mach, R> MachOSymbolIterator<'data, 'file, Mach, R>
256where
257    Mach: MachHeader,
258    R: ReadRef<'data>,
259{
260    pub(super) fn new(file: &'file MachOFile<'data, Mach, R>) -> Self {
261        MachOSymbolIterator {
262            file,
263            index: SymbolIndex(0),
264        }
265    }
266
267    pub(super) fn empty(file: &'file MachOFile<'data, Mach, R>) -> Self {
268        MachOSymbolIterator {
269            file,
270            index: SymbolIndex(file.symbols.len()),
271        }
272    }
273}
274
275impl<'data, 'file, Mach, R> fmt::Debug for MachOSymbolIterator<'data, 'file, Mach, R>
276where
277    Mach: MachHeader,
278    R: ReadRef<'data>,
279{
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.debug_struct("MachOSymbolIterator").finish()
282    }
283}
284
285impl<'data, 'file, Mach, R> Iterator for MachOSymbolIterator<'data, 'file, Mach, R>
286where
287    Mach: MachHeader,
288    R: ReadRef<'data>,
289{
290    type Item = MachOSymbol<'data, 'file, Mach, R>;
291
292    fn next(&mut self) -> Option<Self::Item> {
293        loop {
294            let index = self.index;
295            let nlist = self.file.symbols.symbols.get(index.0)?;
296            self.index.0 += 1;
297            if let Some(symbol) = MachOSymbol::new(self.file, index, nlist) {
298                return Some(symbol);
299            }
300        }
301    }
302}
303
304/// A symbol in a [`MachOFile32`](super::MachOFile32).
305pub type MachOSymbol32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
306    MachOSymbol<'data, 'file, macho::MachHeader32<Endian>, R>;
307/// A symbol in a [`MachOFile64`](super::MachOFile64).
308pub type MachOSymbol64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
309    MachOSymbol<'data, 'file, macho::MachHeader64<Endian>, R>;
310
311/// A symbol in a [`MachOFile`].
312///
313/// Most functionality is provided by the [`ObjectSymbol`] trait implementation.
314#[derive(Debug, Clone, Copy)]
315pub struct MachOSymbol<'data, 'file, Mach, R = &'data [u8]>
316where
317    Mach: MachHeader,
318    R: ReadRef<'data>,
319{
320    file: &'file MachOFile<'data, Mach, R>,
321    index: SymbolIndex,
322    nlist: &'data Mach::Nlist,
323}
324
325impl<'data, 'file, Mach, R> MachOSymbol<'data, 'file, Mach, R>
326where
327    Mach: MachHeader,
328    R: ReadRef<'data>,
329{
330    pub(super) fn new(
331        file: &'file MachOFile<'data, Mach, R>,
332        index: SymbolIndex,
333        nlist: &'data Mach::Nlist,
334    ) -> Option<Self> {
335        if nlist.n_type().is_stab() {
336            return None;
337        }
338        Some(MachOSymbol { file, index, nlist })
339    }
340
341    /// Get the Mach-O file containing this symbol.
342    pub fn macho_file(&self) -> &'file MachOFile<'data, Mach, R> {
343        self.file
344    }
345
346    /// Get the raw Mach-O symbol structure.
347    pub fn macho_symbol(&self) -> &'data Mach::Nlist {
348        self.nlist
349    }
350}
351
352impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbol<'data, 'file, Mach, R>
353where
354    Mach: MachHeader,
355    R: ReadRef<'data>,
356{
357}
358
359impl<'data, 'file, Mach, R> ObjectSymbol<'data> for MachOSymbol<'data, 'file, Mach, R>
360where
361    Mach: MachHeader,
362    R: ReadRef<'data>,
363{
364    #[inline]
365    fn index(&self) -> SymbolIndex {
366        self.index
367    }
368
369    fn name_bytes(&self) -> Result<&'data [u8]> {
370        self.nlist.name(self.file.endian, self.file.symbols.strings)
371    }
372
373    fn name(&self) -> Result<&'data str> {
374        let name = self.name_bytes()?;
375        str::from_utf8(name)
376            .ok()
377            .read_error("Non UTF-8 Mach-O symbol name")
378    }
379
380    #[inline]
381    fn address(&self) -> u64 {
382        if self.is_common() {
383            // The value is the size, not an address.
384            return 0;
385        }
386        self.nlist.n_value(self.file.endian).into()
387    }
388
389    #[inline]
390    fn size(&self) -> u64 {
391        if self.is_common() {
392            return self.nlist.n_value(self.file.endian).into();
393        }
394        0
395    }
396
397    fn kind(&self) -> SymbolKind {
398        if let Some(section) = self
399            .section()
400            .index()
401            .and_then(|index| self.file.section_internal(index).ok())
402        {
403            if let Ok(name) = self.name_bytes() {
404                // Heuristic to match LLVM's convention for section symbols; may misclassify.
405                if self.is_local()
406                    && name.len() > 4
407                    && name.starts_with(b"ltmp")
408                    && name[4..].iter().all(|b| b.is_ascii_digit())
409                    && self.address() == section.section.addr(self.file.endian).into()
410                {
411                    return SymbolKind::Section;
412                }
413            }
414            match section.kind {
415                SectionKind::Text => SymbolKind::Text,
416                SectionKind::Data
417                | SectionKind::ReadOnlyData
418                | SectionKind::ReadOnlyString
419                | SectionKind::UninitializedData => SymbolKind::Data,
420                SectionKind::Tls | SectionKind::UninitializedTls | SectionKind::TlsVariables => {
421                    SymbolKind::Tls
422                }
423                _ => SymbolKind::Unknown,
424            }
425        } else if self.is_common() {
426            SymbolKind::Data
427        } else {
428            SymbolKind::Unknown
429        }
430    }
431
432    fn section(&self) -> SymbolSection {
433        match self.nlist.n_type().typ() {
434            macho::N_UNDF => {
435                if self.is_common() {
436                    SymbolSection::Common
437                } else {
438                    SymbolSection::Undefined
439                }
440            }
441            macho::N_ABS => SymbolSection::Absolute,
442            macho::N_SECT => {
443                let n_sect = self.nlist.n_sect();
444                if n_sect != 0 {
445                    SymbolSection::Section(SectionIndex(n_sect as usize))
446                } else {
447                    SymbolSection::Unknown
448                }
449            }
450            _ => SymbolSection::Unknown,
451        }
452    }
453
454    #[inline]
455    fn is_undefined(&self) -> bool {
456        self.nlist.is_undefined()
457    }
458
459    #[inline]
460    fn is_definition(&self) -> bool {
461        self.nlist.is_definition()
462    }
463
464    #[inline]
465    fn is_common(&self) -> bool {
466        self.nlist.is_common()
467    }
468
469    #[inline]
470    fn is_weak(&self) -> bool {
471        let n_desc = self.nlist.n_desc(self.file.endian);
472        n_desc.intersects(macho::N_WEAK_REF | macho::N_WEAK_DEF)
473    }
474
475    fn scope(&self) -> SymbolScope {
476        let n_type = self.nlist.n_type();
477        if self.is_undefined() {
478            SymbolScope::Unknown
479        } else if !n_type.is_ext() {
480            SymbolScope::Compilation
481        } else if n_type.is_pext() {
482            SymbolScope::Linkage
483        } else {
484            SymbolScope::Dynamic
485        }
486    }
487
488    #[inline]
489    fn is_global(&self) -> bool {
490        self.scope() != SymbolScope::Compilation
491    }
492
493    #[inline]
494    fn is_local(&self) -> bool {
495        self.scope() == SymbolScope::Compilation
496    }
497
498    #[inline]
499    fn flags(&self) -> SymbolFlags<SectionIndex, SymbolIndex> {
500        let n_type = self.nlist.n_type();
501        let n_desc = self.nlist.n_desc(self.file.endian);
502        SymbolFlags::MachO { n_type, n_desc }
503    }
504}
505
506/// A trait for generic access to [`macho::Nlist32`] and [`macho::Nlist64`].
507#[allow(missing_docs)]
508pub trait Nlist: Debug + Pod + read::private::Sealed {
509    type Word: Into<u64>;
510    type Endian: endian::Endian;
511
512    fn n_strx(&self, endian: Self::Endian) -> u32;
513    fn n_type(&self) -> macho::SymbolFlags;
514    fn n_sect(&self) -> u8;
515    fn n_desc(&self, endian: Self::Endian) -> macho::SymbolDesc;
516    fn n_value(&self, endian: Self::Endian) -> Self::Word;
517
518    fn name<'data, R: ReadRef<'data>>(
519        &self,
520        endian: Self::Endian,
521        strings: StringTable<'data, R>,
522    ) -> Result<&'data [u8]> {
523        strings
524            .get(self.n_strx(endian))
525            .read_error("Invalid Mach-O symbol name offset")
526    }
527
528    /// Return true if this is a STAB symbol.
529    ///
530    /// This determines the meaning of the `n_type` field.
531    fn is_stab(&self) -> bool {
532        self.n_type().is_stab()
533    }
534
535    /// Return the STAB symbol type.
536    fn stab(&self) -> Option<macho::SymbolStab> {
537        self.n_type().stab()
538    }
539
540    /// Return true if this is an undefined symbol.
541    ///
542    /// This returns false for common symbols.
543    fn is_undefined(&self) -> bool {
544        let n_type = self.n_type();
545        !n_type.is_stab()
546            && n_type.typ() == macho::N_UNDF
547            // Comparing `n_value` with 0 gives the same result for any endian.
548            && self.n_value(Self::Endian::default()).into() == 0
549    }
550
551    /// Return true if this is a common symbol.
552    fn is_common(&self) -> bool {
553        let n_type = self.n_type();
554        // Don't require N_EXT, to match the behavior of lld.
555        !n_type.is_stab()
556            && n_type.typ() == macho::N_UNDF
557            // Comparing `n_value` with 0 gives the same result for any endian.
558            && self.n_value(Self::Endian::default()).into() != 0
559    }
560
561    /// Return true if the symbol is a definition of a function or data object.
562    fn is_definition(&self) -> bool {
563        let n_type = self.n_type();
564        !n_type.is_stab() && n_type.typ() == macho::N_SECT
565    }
566
567    /// Return the library ordinal.
568    ///
569    /// This is either a 1-based index into the dylib load commands,
570    /// or a special ordinal.
571    #[inline]
572    fn library_ordinal(&self, endian: Self::Endian) -> macho::SymbolLibrary {
573        self.n_desc(endian).library()
574    }
575}
576
577impl<Endian: endian::Endian> read::private::Sealed for macho::Nlist32<Endian> {}
578
579impl<Endian: endian::Endian> Nlist for macho::Nlist32<Endian> {
580    type Word = u32;
581    type Endian = Endian;
582
583    fn n_strx(&self, endian: Self::Endian) -> u32 {
584        self.n_strx.get(endian)
585    }
586    fn n_type(&self) -> macho::SymbolFlags {
587        self.n_type
588    }
589    fn n_sect(&self) -> u8 {
590        self.n_sect
591    }
592    fn n_desc(&self, endian: Self::Endian) -> macho::SymbolDesc {
593        self.n_desc.get(endian)
594    }
595    fn n_value(&self, endian: Self::Endian) -> Self::Word {
596        self.n_value.get(endian)
597    }
598}
599
600impl<Endian: endian::Endian> read::private::Sealed for macho::Nlist64<Endian> {}
601
602impl<Endian: endian::Endian> Nlist for macho::Nlist64<Endian> {
603    type Word = u64;
604    type Endian = Endian;
605
606    fn n_strx(&self, endian: Self::Endian) -> u32 {
607        self.n_strx.get(endian)
608    }
609    fn n_type(&self) -> macho::SymbolFlags {
610        self.n_type
611    }
612    fn n_sect(&self) -> u8 {
613        self.n_sect
614    }
615    fn n_desc(&self, endian: Self::Endian) -> macho::SymbolDesc {
616        self.n_desc.get(endian)
617    }
618    fn n_value(&self, endian: Self::Endian) -> Self::Word {
619        self.n_value.get(endian)
620    }
621}