Skip to main content

macho_core/model/
symbol.rs

1use crate::error::{Error, Result};
2use crate::format::constants::*;
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6/// The SymbolType type.
7pub enum SymbolType {
8    /// The Undefined variant.
9    Undefined,
10    /// The Absolute variant.
11    Absolute,
12    /// The Section variant.
13    Section,
14    /// The PreboundUndefined variant.
15    PreboundUndefined,
16    /// The Indirect variant.
17    Indirect,
18    /// STAB debugging symbol. The inner value is the stab type code
19    /// (the full `n_type` byte with `N_STAB` bits set). STAB symbols are
20    /// not regular code/data symbols — `is_defined()` and `is_undefined()`
21    /// both return false for them.
22    Stab(u8),
23    /// Unknown symbol type. The inner value is the masked N_TYPE bits only
24    /// (not the full n_type byte).
25    Unknown(u8),
26}
27
28impl SymbolType {
29    /// Performs from_n_type.
30    pub fn from_n_type(n_type: u8) -> Self {
31        if n_type & N_STAB != 0 {
32            return Self::Stab(n_type);
33        }
34        match n_type & N_TYPE {
35            N_UNDF => Self::Undefined,
36            N_ABS => Self::Absolute,
37            N_SECT => Self::Section,
38            N_PBUD => Self::PreboundUndefined,
39            N_INDR => Self::Indirect,
40            other => Self::Unknown(other),
41        }
42    }
43
44    /// Performs name.
45    pub fn name(&self) -> &'static str {
46        match self {
47            Self::Undefined => "undef",
48            Self::Absolute => "abs",
49            Self::Section => "sect",
50            Self::PreboundUndefined => "pbud",
51            Self::Indirect => "indr",
52            Self::Stab(_) => "stab",
53            Self::Unknown(_) => "unk",
54        }
55    }
56
57    /// Performs is_stab.
58    pub fn is_stab(&self) -> bool {
59        matches!(self, Self::Stab(_))
60    }
61}
62
63impl fmt::Display for SymbolType {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "{}", self.name())
66    }
67}
68
69#[derive(Debug, Clone)]
70/// The Symbol type.
71pub struct Symbol<'data> {
72    /// The name field.
73    pub name: &'data str,
74    /// The sym_type field.
75    pub sym_type: SymbolType,
76    /// The external field.
77    pub external: bool,
78    /// The private_external field.
79    pub private_external: bool,
80    /// The section_index field.
81    pub section_index: u8,
82    /// The desc field.
83    pub desc: u16,
84    /// The value field.
85    pub value: u64,
86    /// Original index in the symbol table.
87    pub index: usize,
88}
89
90impl Symbol<'_> {
91    /// Performs is_stab.
92    pub fn is_stab(&self) -> bool {
93        self.sym_type.is_stab()
94    }
95
96    /// Performs is_defined.
97    pub fn is_defined(&self) -> bool {
98        matches!(self.sym_type, SymbolType::Section | SymbolType::Absolute)
99    }
100
101    /// Performs is_undefined.
102    pub fn is_undefined(&self) -> bool {
103        matches!(self.sym_type, SymbolType::Undefined)
104    }
105
106    /// Performs is_weak_def.
107    pub fn is_weak_def(&self) -> bool {
108        self.desc & N_WEAK_DEF != 0
109    }
110
111    /// Performs is_weak_ref.
112    pub fn is_weak_ref(&self) -> bool {
113        self.desc & N_WEAK_REF != 0
114    }
115
116    /// Performs is_no_dead_strip.
117    pub fn is_no_dead_strip(&self) -> bool {
118        self.desc & N_NO_DEAD_STRIP != 0
119    }
120
121    /// Performs is_alt_entry.
122    pub fn is_alt_entry(&self) -> bool {
123        self.desc & N_ALT_ENTRY != 0
124    }
125
126    /// Extract the library ordinal from bits 8-15 of n_desc.
127    pub fn library_ordinal(&self) -> u8 {
128        ((self.desc >> 8) & 0xFF) as u8
129    }
130}
131
132impl fmt::Display for Symbol<'_> {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(f, "{:#018x} {} {}", self.value, self.sym_type, self.name)
135    }
136}
137
138/// A view into the raw string table bytes, supporting index-based lookup.
139#[derive(Clone)]
140pub struct StringTable<'data> {
141    data: &'data [u8],
142}
143
144impl<'data> StringTable<'data> {
145    /// Performs new.
146    pub fn new(data: &'data [u8]) -> Self {
147        Self { data }
148    }
149
150    /// Performs get.
151    pub fn get(&self, index: u32) -> Result<&'data str> {
152        let start = index as usize;
153        if start >= self.data.len() {
154            return Err(Error::bounds(start as u64, 1, self.data.len() as u64));
155        }
156        let slice = &self.data[start..];
157        let end = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
158        std::str::from_utf8(&slice[..end]).map_err(|e| {
159            Error::format(format!(
160                "invalid UTF-8 in string table at index {index}: {e}"
161            ))
162        })
163    }
164
165    /// Performs bytes.
166    pub fn bytes(&self) -> &'data [u8] {
167        self.data
168    }
169
170    /// Performs len.
171    pub fn len(&self) -> usize {
172        self.data.len()
173    }
174
175    /// Performs is_empty.
176    pub fn is_empty(&self) -> bool {
177        self.data.is_empty()
178    }
179}
180
181impl fmt::Debug for StringTable<'_> {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        f.debug_struct("StringTable")
184            .field("len", &self.data.len())
185            .finish()
186    }
187}
188
189/// A parsed symbol table extracted from LC_SYMTAB.
190pub struct SymbolTable<'data> {
191    symbols: Vec<Symbol<'data>>,
192    string_table: StringTable<'data>,
193}
194
195impl<'data> SymbolTable<'data> {
196    pub(crate) fn new(symbols: Vec<Symbol<'data>>, string_table: StringTable<'data>) -> Self {
197        Self {
198            symbols,
199            string_table,
200        }
201    }
202
203    /// Performs symbols.
204    pub fn symbols(&self) -> &[Symbol<'data>] {
205        &self.symbols
206    }
207
208    /// Performs len.
209    pub fn len(&self) -> usize {
210        self.symbols.len()
211    }
212
213    /// Performs is_empty.
214    pub fn is_empty(&self) -> bool {
215        self.symbols.is_empty()
216    }
217
218    /// Performs get.
219    pub fn get(&self, index: usize) -> Option<&Symbol<'data>> {
220        self.symbols.get(index)
221    }
222
223    /// Performs string_table.
224    pub fn string_table(&self) -> &StringTable<'data> {
225        &self.string_table
226    }
227
228    /// Performs find_by_name.
229    pub fn find_by_name(&self, name: &str) -> Option<&Symbol<'data>> {
230        self.symbols.iter().find(|s| s.name == name)
231    }
232
233    /// Performs defined.
234    pub fn defined(&self) -> impl Iterator<Item = &Symbol<'data>> {
235        self.symbols.iter().filter(|s| s.is_defined())
236    }
237
238    /// Performs undefined.
239    pub fn undefined(&self) -> impl Iterator<Item = &Symbol<'data>> {
240        self.symbols.iter().filter(|s| s.is_undefined())
241    }
242
243    /// Performs stabs.
244    pub fn stabs(&self) -> impl Iterator<Item = &Symbol<'data>> {
245        self.symbols.iter().filter(|s| s.is_stab())
246    }
247
248    /// Performs external.
249    pub fn external(&self) -> impl Iterator<Item = &Symbol<'data>> {
250        self.symbols.iter().filter(|s| s.external)
251    }
252}
253
254impl fmt::Debug for SymbolTable<'_> {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.debug_struct("SymbolTable")
257            .field("num_symbols", &self.symbols.len())
258            .field("string_table", &self.string_table)
259            .finish()
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn string_table_get() {
269        let data = b"\0hello\0world\0";
270        let st = StringTable::new(data);
271        assert_eq!(st.get(0).unwrap(), "");
272        assert_eq!(st.get(1).unwrap(), "hello");
273        assert_eq!(st.get(7).unwrap(), "world");
274    }
275
276    #[test]
277    fn string_table_out_of_bounds() {
278        let data = b"\0hello\0";
279        let st = StringTable::new(data);
280        assert!(st.get(100).is_err());
281    }
282
283    #[test]
284    fn symbol_type_classification() {
285        assert_eq!(SymbolType::from_n_type(0x0f), SymbolType::Section);
286        assert_eq!(SymbolType::from_n_type(0x01), SymbolType::Undefined);
287        assert_eq!(SymbolType::from_n_type(0x03), SymbolType::Absolute);
288    }
289
290    #[test]
291    fn stab_type_classification() {
292        // N_FUN = 0x24 has N_STAB bits set (0x20 & 0xe0 != 0)
293        let st = SymbolType::from_n_type(0x24);
294        assert!(st.is_stab());
295        assert_eq!(st, SymbolType::Stab(0x24));
296        assert_eq!(st.name(), "stab");
297    }
298
299    #[test]
300    fn symbol_helpers() {
301        let sym = Symbol {
302            name: "_test",
303            sym_type: SymbolType::Section,
304            external: true,
305            private_external: false,
306            section_index: 1,
307            desc: N_WEAK_DEF,
308            value: 0x1000,
309            index: 0,
310        };
311        assert!(sym.is_defined());
312        assert!(!sym.is_undefined());
313        assert!(!sym.is_stab());
314        assert!(sym.is_weak_def());
315        assert!(!sym.is_weak_ref());
316        assert_eq!(sym.library_ordinal(), 0);
317    }
318}