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, ObjectSymbol, ObjectSymbolTable, ReadError, ReadRef, Result,
11 SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapEntry,
12 SymbolScope, SymbolSection,
13};
14
15use super::{MachHeader, MachOFile};
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 /// Iterate over the symbols.
53 #[inline]
54 pub fn iter(&self) -> slice::Iter<'data, Mach::Nlist> {
55 self.symbols.iter()
56 }
57
58 /// Return true if the symbol table is empty.
59 #[inline]
60 pub fn is_empty(&self) -> bool {
61 self.symbols.is_empty()
62 }
63
64 /// The number of symbols.
65 #[inline]
66 pub fn len(&self) -> usize {
67 self.symbols.len()
68 }
69
70 /// Return the symbol at the given index.
71 pub fn symbol(&self, index: usize) -> Result<&'data Mach::Nlist> {
72 self.symbols
73 .get(index)
74 .read_error("Invalid Mach-O symbol index")
75 }
76
77 /// Construct a map from addresses to a user-defined map entry.
78 pub fn map<Entry: SymbolMapEntry, F: Fn(&'data Mach::Nlist) -> Option<Entry>>(
79 &self,
80 f: F,
81 ) -> SymbolMap<Entry> {
82 let mut symbols = Vec::new();
83 for nlist in self.symbols {
84 if !nlist.is_definition() {
85 continue;
86 }
87 if let Some(entry) = f(nlist) {
88 symbols.push(entry);
89 }
90 }
91 SymbolMap::new(symbols)
92 }
93
94 /// Construct a map from addresses to symbol names and object file names.
95 pub fn object_map(&self, endian: Mach::Endian) -> ObjectMap<'data> {
96 let mut symbols = Vec::new();
97 let mut objects = Vec::new();
98 let mut object = None;
99 let mut current_function = None;
100 // Each module starts with one or two N_SO symbols (path, or directory + filename)
101 // and one N_OSO symbol. The module is terminated by an empty N_SO symbol.
102 for nlist in self.symbols {
103 let n_type = nlist.n_type();
104 if n_type & macho::N_STAB == 0 {
105 continue;
106 }
107 // TODO: includes variables too (N_GSYM, N_STSYM). These may need to get their
108 // address from regular symbols though.
109 match n_type {
110 macho::N_SO => {
111 object = None;
112 }
113 macho::N_OSO => {
114 object = None;
115 if let Ok(name) = nlist.name(endian, self.strings) {
116 if !name.is_empty() {
117 object = Some(objects.len());
118 objects.push(name);
119 }
120 }
121 }
122 macho::N_FUN => {
123 if let Ok(name) = nlist.name(endian, self.strings) {
124 if !name.is_empty() {
125 current_function = Some((name, nlist.n_value(endian).into()))
126 } else if let Some((name, address)) = current_function.take() {
127 if let Some(object) = object {
128 symbols.push(ObjectMapEntry {
129 address,
130 size: nlist.n_value(endian).into(),
131 name,
132 object,
133 });
134 }
135 }
136 }
137 }
138 _ => {}
139 }
140 }
141 ObjectMap {
142 symbols: SymbolMap::new(symbols),
143 objects,
144 }
145 }
146}
147
148/// A symbol table in a [`MachOFile32`](super::MachOFile32).
149pub type MachOSymbolTable32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
150 MachOSymbolTable<'data, 'file, macho::MachHeader32<Endian>, R>;
151/// A symbol table in a [`MachOFile64`](super::MachOFile64).
152pub type MachOSymbolTable64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
153 MachOSymbolTable<'data, 'file, macho::MachHeader64<Endian>, R>;
154
155/// A symbol table in a [`MachOFile`].
156#[derive(Debug, Clone, Copy)]
157pub struct MachOSymbolTable<'data, 'file, Mach, R = &'data [u8]>
158where
159 Mach: MachHeader,
160 R: ReadRef<'data>,
161{
162 pub(super) file: &'file MachOFile<'data, Mach, R>,
163}
164
165impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbolTable<'data, 'file, Mach, R>
166where
167 Mach: MachHeader,
168 R: ReadRef<'data>,
169{
170}
171
172impl<'data, 'file, Mach, R> ObjectSymbolTable<'data> for MachOSymbolTable<'data, 'file, Mach, R>
173where
174 Mach: MachHeader,
175 R: ReadRef<'data>,
176{
177 type Symbol = MachOSymbol<'data, 'file, Mach, R>;
178 type SymbolIterator = MachOSymbolIterator<'data, 'file, Mach, R>;
179
180 fn symbols(&self) -> Self::SymbolIterator {
181 MachOSymbolIterator {
182 file: self.file,
183 index: 0,
184 }
185 }
186
187 fn symbol_by_index(&self, index: SymbolIndex) -> Result<Self::Symbol> {
188 let nlist: &::Nlist = self.file.symbols.symbol(index:index.0)?;
189 MachOSymbol::new(self.file, index, nlist).read_error("Unsupported Mach-O symbol index")
190 }
191}
192
193/// An iterator for the symbols in a [`MachOFile32`](super::MachOFile32).
194pub type MachOSymbolIterator32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
195 MachOSymbolIterator<'data, 'file, macho::MachHeader32<Endian>, R>;
196/// An iterator for the symbols in a [`MachOFile64`](super::MachOFile64).
197pub type MachOSymbolIterator64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
198 MachOSymbolIterator<'data, 'file, macho::MachHeader64<Endian>, R>;
199
200/// An iterator for the symbols in a [`MachOFile`].
201pub struct MachOSymbolIterator<'data, 'file, Mach, R = &'data [u8]>
202where
203 Mach: MachHeader,
204 R: ReadRef<'data>,
205{
206 pub(super) file: &'file MachOFile<'data, Mach, R>,
207 pub(super) index: usize,
208}
209
210impl<'data, 'file, Mach, R> fmt::Debug for MachOSymbolIterator<'data, 'file, Mach, R>
211where
212 Mach: MachHeader,
213 R: ReadRef<'data>,
214{
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 f.debug_struct(name:"MachOSymbolIterator").finish()
217 }
218}
219
220impl<'data, 'file, Mach, R> Iterator for MachOSymbolIterator<'data, 'file, Mach, R>
221where
222 Mach: MachHeader,
223 R: ReadRef<'data>,
224{
225 type Item = MachOSymbol<'data, 'file, Mach, R>;
226
227 fn next(&mut self) -> Option<Self::Item> {
228 loop {
229 let index: usize = self.index;
230 let nlist: &::Nlist = self.file.symbols.symbols.get(index)?;
231 self.index += 1;
232 if let Some(symbol: MachOSymbol<'_, '_, Mach, …>) = MachOSymbol::new(self.file, index:SymbolIndex(index), nlist) {
233 return Some(symbol);
234 }
235 }
236 }
237}
238
239/// A symbol in a [`MachOFile32`](super::MachOFile32).
240pub type MachOSymbol32<'data, 'file, Endian = Endianness, R = &'data [u8]> =
241 MachOSymbol<'data, 'file, macho::MachHeader32<Endian>, R>;
242/// A symbol in a [`MachOFile64`](super::MachOFile64).
243pub type MachOSymbol64<'data, 'file, Endian = Endianness, R = &'data [u8]> =
244 MachOSymbol<'data, 'file, macho::MachHeader64<Endian>, R>;
245
246/// A symbol in a [`MachOFile`].
247///
248/// Most functionality is provided by the [`ObjectSymbol`] trait implementation.
249#[derive(Debug, Clone, Copy)]
250pub struct MachOSymbol<'data, 'file, Mach, R = &'data [u8]>
251where
252 Mach: MachHeader,
253 R: ReadRef<'data>,
254{
255 file: &'file MachOFile<'data, Mach, R>,
256 index: SymbolIndex,
257 nlist: &'data Mach::Nlist,
258}
259
260impl<'data, 'file, Mach, R> MachOSymbol<'data, 'file, Mach, R>
261where
262 Mach: MachHeader,
263 R: ReadRef<'data>,
264{
265 pub(super) fn new(
266 file: &'file MachOFile<'data, Mach, R>,
267 index: SymbolIndex,
268 nlist: &'data Mach::Nlist,
269 ) -> Option<Self> {
270 if nlist.n_type() & macho::N_STAB != 0 {
271 return None;
272 }
273 Some(MachOSymbol { file, index, nlist })
274 }
275}
276
277impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbol<'data, 'file, Mach, R>
278where
279 Mach: MachHeader,
280 R: ReadRef<'data>,
281{
282}
283
284impl<'data, 'file, Mach, R> ObjectSymbol<'data> for MachOSymbol<'data, 'file, Mach, R>
285where
286 Mach: MachHeader,
287 R: ReadRef<'data>,
288{
289 #[inline]
290 fn index(&self) -> SymbolIndex {
291 self.index
292 }
293
294 fn name_bytes(&self) -> Result<&'data [u8]> {
295 self.nlist.name(self.file.endian, self.file.symbols.strings)
296 }
297
298 fn name(&self) -> Result<&'data str> {
299 let name = self.name_bytes()?;
300 str::from_utf8(name)
301 .ok()
302 .read_error("Non UTF-8 Mach-O symbol name")
303 }
304
305 #[inline]
306 fn address(&self) -> u64 {
307 self.nlist.n_value(self.file.endian).into()
308 }
309
310 #[inline]
311 fn size(&self) -> u64 {
312 0
313 }
314
315 fn kind(&self) -> SymbolKind {
316 self.section()
317 .index()
318 .and_then(|index| self.file.section_internal(index).ok())
319 .map(|section| match section.kind {
320 SectionKind::Text => SymbolKind::Text,
321 SectionKind::Data
322 | SectionKind::ReadOnlyData
323 | SectionKind::ReadOnlyString
324 | SectionKind::UninitializedData
325 | SectionKind::Common => SymbolKind::Data,
326 SectionKind::Tls | SectionKind::UninitializedTls | SectionKind::TlsVariables => {
327 SymbolKind::Tls
328 }
329 _ => SymbolKind::Unknown,
330 })
331 .unwrap_or(SymbolKind::Unknown)
332 }
333
334 fn section(&self) -> SymbolSection {
335 match self.nlist.n_type() & macho::N_TYPE {
336 macho::N_UNDF => SymbolSection::Undefined,
337 macho::N_ABS => SymbolSection::Absolute,
338 macho::N_SECT => {
339 let n_sect = self.nlist.n_sect();
340 if n_sect != 0 {
341 SymbolSection::Section(SectionIndex(n_sect as usize))
342 } else {
343 SymbolSection::Unknown
344 }
345 }
346 _ => SymbolSection::Unknown,
347 }
348 }
349
350 #[inline]
351 fn is_undefined(&self) -> bool {
352 self.nlist.n_type() & macho::N_TYPE == macho::N_UNDF
353 }
354
355 #[inline]
356 fn is_definition(&self) -> bool {
357 self.nlist.is_definition()
358 }
359
360 #[inline]
361 fn is_common(&self) -> bool {
362 // Mach-O common symbols are based on section, not symbol
363 false
364 }
365
366 #[inline]
367 fn is_weak(&self) -> bool {
368 self.nlist.n_desc(self.file.endian) & (macho::N_WEAK_REF | macho::N_WEAK_DEF) != 0
369 }
370
371 fn scope(&self) -> SymbolScope {
372 let n_type = self.nlist.n_type();
373 if n_type & macho::N_TYPE == macho::N_UNDF {
374 SymbolScope::Unknown
375 } else if n_type & macho::N_EXT == 0 {
376 SymbolScope::Compilation
377 } else if n_type & macho::N_PEXT != 0 {
378 SymbolScope::Linkage
379 } else {
380 SymbolScope::Dynamic
381 }
382 }
383
384 #[inline]
385 fn is_global(&self) -> bool {
386 self.scope() != SymbolScope::Compilation
387 }
388
389 #[inline]
390 fn is_local(&self) -> bool {
391 self.scope() == SymbolScope::Compilation
392 }
393
394 #[inline]
395 fn flags(&self) -> SymbolFlags<SectionIndex, SymbolIndex> {
396 let n_desc = self.nlist.n_desc(self.file.endian);
397 SymbolFlags::MachO { n_desc }
398 }
399}
400
401/// A trait for generic access to [`macho::Nlist32`] and [`macho::Nlist64`].
402#[allow(missing_docs)]
403pub trait Nlist: Debug + Pod {
404 type Word: Into<u64>;
405 type Endian: endian::Endian;
406
407 fn n_strx(&self, endian: Self::Endian) -> u32;
408 fn n_type(&self) -> u8;
409 fn n_sect(&self) -> u8;
410 fn n_desc(&self, endian: Self::Endian) -> u16;
411 fn n_value(&self, endian: Self::Endian) -> Self::Word;
412
413 fn name<'data, R: ReadRef<'data>>(
414 &self,
415 endian: Self::Endian,
416 strings: StringTable<'data, R>,
417 ) -> Result<&'data [u8]> {
418 strings
419 .get(self.n_strx(endian))
420 .read_error("Invalid Mach-O symbol name offset")
421 }
422
423 /// Return true if this is a STAB symbol.
424 ///
425 /// This determines the meaning of the `n_type` field.
426 fn is_stab(&self) -> bool {
427 self.n_type() & macho::N_STAB != 0
428 }
429
430 /// Return true if this is an undefined symbol.
431 fn is_undefined(&self) -> bool {
432 let n_type = self.n_type();
433 n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_UNDF
434 }
435
436 /// Return true if the symbol is a definition of a function or data object.
437 fn is_definition(&self) -> bool {
438 let n_type = self.n_type();
439 n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_SECT
440 }
441
442 /// Return the library ordinal.
443 ///
444 /// This is either a 1-based index into the dylib load commands,
445 /// or a special ordinal.
446 #[inline]
447 fn library_ordinal(&self, endian: Self::Endian) -> u8 {
448 (self.n_desc(endian) >> 8) as u8
449 }
450}
451
452impl<Endian: endian::Endian> Nlist for macho::Nlist32<Endian> {
453 type Word = u32;
454 type Endian = Endian;
455
456 fn n_strx(&self, endian: Self::Endian) -> u32 {
457 self.n_strx.get(endian)
458 }
459 fn n_type(&self) -> u8 {
460 self.n_type
461 }
462 fn n_sect(&self) -> u8 {
463 self.n_sect
464 }
465 fn n_desc(&self, endian: Self::Endian) -> u16 {
466 self.n_desc.get(endian)
467 }
468 fn n_value(&self, endian: Self::Endian) -> Self::Word {
469 self.n_value.get(endian)
470 }
471}
472
473impl<Endian: endian::Endian> Nlist for macho::Nlist64<Endian> {
474 type Word = u64;
475 type Endian = Endian;
476
477 fn n_strx(&self, endian: Self::Endian) -> u32 {
478 self.n_strx.get(endian)
479 }
480 fn n_type(&self) -> u8 {
481 self.n_type
482 }
483 fn n_sect(&self) -> u8 {
484 self.n_sect
485 }
486 fn n_desc(&self, endian: Self::Endian) -> u16 {
487 self.n_desc.get(endian)
488 }
489 fn n_value(&self, endian: Self::Endian) -> Self::Word {
490 self.n_value.get(endian)
491 }
492}
493