goblin_experimental/mach/
symbols.rs

1//! "Nlist" style symbols in this binary - beware, like most symbol tables in most binary formats, they are strippable, and should not be relied upon, see the imports and exports modules for something more permanent.
2//!
3//! Symbols are essentially a type, offset, and the symbol name
4
5use crate::container::{self, Container};
6use crate::error;
7use crate::mach::load_command;
8use core::fmt::{self, Debug};
9use scroll::ctx;
10use scroll::ctx::SizeWith;
11use scroll::{IOread, IOwrite, Pread, Pwrite, SizeWith};
12
13// The n_type field really contains four fields which are used via the following masks.
14/// if any of these bits set, a symbolic debugging entry
15pub const N_STAB: u8 = 0xe0;
16/// private external symbol bit
17pub const N_PEXT: u8 = 0x10;
18/// mask for the type bits
19pub const N_TYPE: u8 = 0x0e;
20/// external symbol bit, set for external symbols
21pub const N_EXT: u8 = 0x01;
22
23// If the type is N_SECT then the n_sect field contains an ordinal of the
24// section the symbol is defined in.  The sections are numbered from 1 and
25// refer to sections in order they appear in the load commands for the file
26// they are in.  This means the same ordinal may very well refer to different
27// sections in different files.
28
29// The n_value field for all symbol table entries (including N_STAB's) gets
30// updated by the link editor based on the value of it's n_sect field and where
31// the section n_sect references gets relocated.  If the value of the n_sect
32// field is NO_SECT then it's n_value field is not changed by the link editor.
33/// symbol is not in any section
34pub const NO_SECT: u8 = 0;
35/// 1 thru 255 inclusive
36pub const MAX_SECT: u8 = 255;
37
38/// undefined, n_sect == NO_SECT
39pub const N_UNDF: u8 = 0x0;
40/// absolute, n_sect == NO_SECT
41pub const N_ABS: u8 = 0x2;
42/// defined in section number n_sect
43pub const N_SECT: u8 = 0xe;
44/// prebound undefined (defined in a dylib)
45pub const N_PBUD: u8 = 0xc;
46/// indirect
47pub const N_INDR: u8 = 0xa;
48
49// n_types when N_STAB
50pub const N_GSYM: u8 = 0x20;
51pub const N_FNAME: u8 = 0x22;
52pub const N_FUN: u8 = 0x24;
53pub const N_STSYM: u8 = 0x26;
54pub const N_LCSYM: u8 = 0x28;
55pub const N_BNSYM: u8 = 0x2e;
56pub const N_PC: u8 = 0x30;
57pub const N_AST: u8 = 0x32;
58pub const N_OPT: u8 = 0x3c;
59pub const N_RSYM: u8 = 0x40;
60pub const N_SLINE: u8 = 0x44;
61pub const N_ENSYM: u8 = 0x4e;
62pub const N_SSYM: u8 = 0x60;
63pub const N_SO: u8 = 0x64;
64pub const N_OSO: u8 = 0x66;
65pub const N_LSYM: u8 = 0x80;
66pub const N_BINCL: u8 = 0x82;
67pub const N_SOL: u8 = 0x84;
68pub const N_PARAMS: u8 = 0x86;
69pub const N_VERSION: u8 = 0x88;
70pub const N_OLEVEL: u8 = 0x8a;
71pub const N_PSYM: u8 = 0xa0;
72pub const N_EINCL: u8 = 0xa2;
73pub const N_ENTRY: u8 = 0xa4;
74pub const N_LBRAC: u8 = 0xc0;
75pub const N_EXCL: u8 = 0xc2;
76pub const N_RBRAC: u8 = 0xe0;
77pub const N_BCOMM: u8 = 0xe2;
78pub const N_ECOMM: u8 = 0xe4;
79pub const N_ECOML: u8 = 0xe8;
80pub const N_LENG: u8 = 0xfe;
81
82pub const NLIST_TYPE_MASK: u8 = 0xe;
83pub const NLIST_TYPE_GLOBAL: u8 = 0x1;
84pub const NLIST_TYPE_LOCAL: u8 = 0x0;
85
86/// Mask for reference flags of `n_desc` field.
87pub const REFERENCE_TYPE: u16 = 0xf;
88/// This symbol is a reference to an external non-lazy (data) symbol.
89pub const REFERENCE_FLAG_UNDEFINED_NON_LAZY: u16 = 0x0;
90/// This symbol is a reference to an external lazy symbol—that is, to a function call.
91pub const REFERENCE_FLAG_UNDEFINED_LAZY: u16 = 0x1;
92/// This symbol is defined in this module.
93pub const REFERENCE_FLAG_DEFINED: u16 = 0x2;
94/// This symbol is defined in this module and is visible only to modules within this
95/// shared library.
96pub const REFERENCE_FLAG_PRIVATE_DEFINED: u16 = 0x3;
97/// This symbol is defined in another module in this file, is a non-lazy (data) symbol,
98/// and is visible only to modules within this shared library.
99pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY: u16 = 0x4;
100/// This symbol is defined in another module in this file, is a lazy (function) symbol,
101/// and is visible only to modules within this shared library.
102pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY: u16 = 0x5;
103
104// Additional flags of n_desc field.
105
106/// Must be set for any defined symbol that is referenced by dynamic-loader APIs
107/// (such as dlsym and NSLookupSymbolInImage) and not ordinary undefined symbol
108/// references. The `strip` tool uses this bit to avoid removing symbols that must
109/// exist: If the symbol has this bit set, `strip` does not strip it.
110pub const REFERENCED_DYNAMICALLY: u16 = 0x10;
111/// Sometimes used by the dynamic linker at runtime in a fully linked image. Do not
112/// set this bit in a fully linked image.
113pub const N_DESC_DISCARDED: u16 = 0x20;
114/// When set in a relocatable object file (file type MH_OBJECT) on a defined symbol,
115/// indicates to the static linker to never dead-strip the symbol.
116// (Note that the same bit (0x20) is used for two nonoverlapping purposes.)
117pub const N_NO_DEAD_STRIP: u16 = 0x20;
118/// Indicates that this undefined symbol is a weak reference. If the dynamic linker
119/// cannot find a definition for this symbol, it sets the address of this symbol to 0.
120/// The static linker sets this symbol given the appropriate weak-linking flags.
121pub const N_WEAK_REF: u16 = 0x40;
122/// Indicates that this symbol is a weak definition. If the static linker or the
123/// dynamic linker finds another (non-weak) definition for this symbol, the weak
124/// definition is ignored. Only symbols in a coalesced section can be marked as a
125/// weak definition.
126pub const N_WEAK_DEF: u16 = 0x80;
127
128pub fn n_type_to_str(n_type: u8) -> &'static str {
129    match n_type {
130        N_UNDF => "N_UNDF",
131        N_ABS => "N_ABS",
132        N_SECT => "N_SECT",
133        N_PBUD => "N_PBUD",
134        N_INDR => "N_INDR",
135        _ => "UNKNOWN_N_TYPE",
136    }
137}
138
139#[repr(C)]
140#[derive(Clone, Copy, Pread, Pwrite, SizeWith, IOread, IOwrite)]
141pub struct Nlist32 {
142    /// index into the string table
143    pub n_strx: u32,
144    /// type flag, see below
145    pub n_type: u8,
146    /// section number or NO_SECT
147    pub n_sect: u8,
148    /// see <mach-o/stab.h>
149    pub n_desc: u16,
150    /// value of this symbol (or stab offset)
151    pub n_value: u32,
152}
153
154pub const SIZEOF_NLIST_32: usize = 12;
155
156impl Debug for Nlist32 {
157    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
158        fmt.debug_struct("Nlist32")
159            .field("n_strx", &format_args!("{:04}", self.n_strx))
160            .field("n_type", &format_args!("{:#02x}", self.n_type))
161            .field("n_sect", &format_args!("{:#x}", self.n_sect))
162            .field("n_desc", &format_args!("{:#03x}", self.n_desc))
163            .field("n_value", &format_args!("{:#x}", self.n_value))
164            .finish()
165    }
166}
167
168#[repr(C)]
169#[derive(Clone, Copy, Pread, Pwrite, SizeWith, IOread, IOwrite)]
170pub struct Nlist64 {
171    /// index into the string table
172    pub n_strx: u32,
173    /// type flag, see below
174    pub n_type: u8,
175    /// section number or NO_SECT
176    pub n_sect: u8,
177    /// see <mach-o/stab.h>
178    pub n_desc: u16,
179    /// value of this symbol (or stab offset)
180    pub n_value: u64,
181}
182
183pub const SIZEOF_NLIST_64: usize = 16;
184
185impl Debug for Nlist64 {
186    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
187        fmt.debug_struct("Nlist64")
188            .field("n_strx", &format_args!("{:04}", self.n_strx))
189            .field("n_type", &format_args!("{:#02x}", self.n_type))
190            .field("n_sect", &format_args!("{:#x}", self.n_sect))
191            .field("n_desc", &format_args!("{:#03x}", self.n_desc))
192            .field("n_value", &format_args!("{:#x}", self.n_value))
193            .finish()
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct Nlist {
199    /// index into the string table
200    pub n_strx: usize,
201    /// type flag, see below
202    pub n_type: u8,
203    /// section number or NO_SECT
204    pub n_sect: usize,
205    /// see <mach-o/stab.h>
206    pub n_desc: u16,
207    /// value of this symbol (or stab offset)
208    pub n_value: u64,
209}
210
211impl Nlist {
212    /// Gets this symbol's type in bits 0xe
213    pub fn get_type(&self) -> u8 {
214        self.n_type & N_TYPE
215    }
216    /// Gets the str representation of the type of this symbol
217    pub fn type_str(&self) -> &'static str {
218        n_type_to_str(self.get_type())
219    }
220    /// Whether this symbol is global or not
221    pub fn is_global(&self) -> bool {
222        self.n_type & N_EXT != 0
223    }
224    /// Whether this symbol is weak or not
225    pub fn is_weak(&self) -> bool {
226        self.n_desc & (N_WEAK_REF | N_WEAK_DEF) != 0
227    }
228    /// Whether this symbol is undefined or not
229    pub fn is_undefined(&self) -> bool {
230        self.n_sect == 0 && self.n_type & N_TYPE == N_UNDF
231    }
232    /// Whether this symbol is a symbolic debugging entry
233    pub fn is_stab(&self) -> bool {
234        self.n_type & N_STAB != 0
235    }
236}
237
238impl ctx::SizeWith<container::Ctx> for Nlist {
239    fn size_with(ctx: &container::Ctx) -> usize {
240        match ctx.container {
241            Container::Little => SIZEOF_NLIST_32,
242            Container::Big => SIZEOF_NLIST_64,
243        }
244    }
245}
246
247impl From<Nlist32> for Nlist {
248    fn from(nlist: Nlist32) -> Self {
249        Nlist {
250            n_strx: nlist.n_strx as usize,
251            n_type: nlist.n_type,
252            n_sect: nlist.n_sect as usize,
253            n_desc: nlist.n_desc,
254            n_value: u64::from(nlist.n_value),
255        }
256    }
257}
258
259impl From<Nlist64> for Nlist {
260    fn from(nlist: Nlist64) -> Self {
261        Nlist {
262            n_strx: nlist.n_strx as usize,
263            n_type: nlist.n_type,
264            n_sect: nlist.n_sect as usize,
265            n_desc: nlist.n_desc,
266            n_value: nlist.n_value,
267        }
268    }
269}
270
271impl From<Nlist> for Nlist32 {
272    fn from(nlist: Nlist) -> Self {
273        Nlist32 {
274            n_strx: nlist.n_strx as u32,
275            n_type: nlist.n_type,
276            n_sect: nlist.n_sect as u8,
277            n_desc: nlist.n_desc,
278            n_value: nlist.n_value as u32,
279        }
280    }
281}
282
283impl From<Nlist> for Nlist64 {
284    fn from(nlist: Nlist) -> Self {
285        Nlist64 {
286            n_strx: nlist.n_strx as u32,
287            n_type: nlist.n_type,
288            n_sect: nlist.n_sect as u8,
289            n_desc: nlist.n_desc,
290            n_value: nlist.n_value,
291        }
292    }
293}
294
295impl<'a> ctx::TryFromCtx<'a, container::Ctx> for Nlist {
296    type Error = crate::error::Error;
297    fn try_from_ctx(
298        bytes: &'a [u8],
299        container::Ctx { container, le }: container::Ctx,
300    ) -> crate::error::Result<(Self, usize)> {
301        let nlist = match container {
302            Container::Little => (bytes.pread_with::<Nlist32>(0, le)?.into(), SIZEOF_NLIST_32),
303            Container::Big => (bytes.pread_with::<Nlist64>(0, le)?.into(), SIZEOF_NLIST_64),
304        };
305        Ok(nlist)
306    }
307}
308
309impl ctx::TryIntoCtx<container::Ctx> for Nlist {
310    type Error = crate::error::Error;
311    fn try_into_ctx(
312        self,
313        bytes: &mut [u8],
314        container::Ctx { container, le }: container::Ctx,
315    ) -> Result<usize, Self::Error> {
316        let size = match container {
317            Container::Little => bytes.pwrite_with::<Nlist32>(self.into(), 0, le)?,
318            Container::Big => bytes.pwrite_with::<Nlist64>(self.into(), 0, le)?,
319        };
320        Ok(size)
321    }
322}
323
324impl ctx::IntoCtx<container::Ctx> for Nlist {
325    fn into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) {
326        bytes.pwrite_with(self, 0, ctx).unwrap();
327    }
328}
329
330#[derive(Debug, Clone, Copy, Default)]
331pub struct SymbolsCtx {
332    pub nsyms: usize,
333    pub strtab: usize,
334    pub ctx: container::Ctx,
335}
336
337impl<'a, T: ?Sized> ctx::TryFromCtx<'a, SymbolsCtx, T> for Symbols<'a>
338where
339    T: AsRef<[u8]>,
340{
341    type Error = crate::error::Error;
342    fn try_from_ctx(
343        bytes: &'a T,
344        SymbolsCtx { nsyms, strtab, ctx }: SymbolsCtx,
345    ) -> crate::error::Result<(Self, usize)> {
346        let data = bytes.as_ref();
347        Ok((
348            Symbols {
349                data,
350                start: 0,
351                nsyms,
352                strtab,
353                ctx,
354            },
355            data.len(),
356        ))
357    }
358}
359
360#[derive(Default)]
361pub struct SymbolIterator<'a> {
362    data: &'a [u8],
363    nsyms: usize,
364    offset: usize,
365    count: usize,
366    ctx: container::Ctx,
367    strtab: usize,
368}
369
370impl<'a> Iterator for SymbolIterator<'a> {
371    type Item = error::Result<(&'a str, Nlist)>;
372    fn next(&mut self) -> Option<Self::Item> {
373        if self.count >= self.nsyms {
374            None
375        } else {
376            self.count += 1;
377            match self.data.gread_with::<Nlist>(&mut self.offset, self.ctx) {
378                Ok(symbol) => match self.data.pread(self.strtab + symbol.n_strx) {
379                    Ok(name) => Some(Ok((name, symbol))),
380                    Err(e) => Some(Err(e.into())),
381                },
382                Err(e) => Some(Err(e)),
383            }
384        }
385    }
386}
387
388/// A zero-copy "nlist" style symbol table ("stab"), including the string table
389pub struct Symbols<'a> {
390    data: &'a [u8],
391    start: usize,
392    nsyms: usize,
393    // TODO: we can use an actual strtab here and tie it to symbols lifetime
394    strtab: usize,
395    ctx: container::Ctx,
396}
397
398impl<'a, 'b> IntoIterator for &'b Symbols<'a> {
399    type Item = <SymbolIterator<'a> as Iterator>::Item;
400    type IntoIter = SymbolIterator<'a>;
401    fn into_iter(self) -> Self::IntoIter {
402        self.iter()
403    }
404}
405
406impl<'a> Symbols<'a> {
407    /// Creates a new symbol table with `count` elements, from the `start` offset, using the string table at `strtab`, with a _default_ ctx.
408    ////
409    /// **Beware**, this will provide incorrect results if you construct this on a 32-bit mach binary, using a 64-bit machine; use `parse` instead if you want 32/64 bit support
410    pub fn new(
411        bytes: &'a [u8],
412        start: usize,
413        count: usize,
414        strtab: usize,
415    ) -> error::Result<Symbols<'a>> {
416        let nsyms = count;
417        Ok(Symbols {
418            data: bytes,
419            start,
420            nsyms,
421            strtab,
422            ctx: container::Ctx::default(),
423        })
424    }
425    pub fn parse(
426        bytes: &'a [u8],
427        symtab: &load_command::SymtabCommand,
428        ctx: container::Ctx,
429    ) -> error::Result<Symbols<'a>> {
430        // we need to normalize the strtab offset before we receive the truncated bytes in pread_with
431        let strtab = symtab
432            .stroff
433            .checked_sub(symtab.symoff)
434            .ok_or_else(|| error::Error::Malformed("invalid symbol table offset".into()))?;
435        bytes.pread_with(
436            symtab.symoff as usize,
437            SymbolsCtx {
438                nsyms: symtab.nsyms as usize,
439                strtab: strtab as usize,
440                ctx,
441            },
442        )
443    }
444
445    pub fn iter(&self) -> SymbolIterator<'a> {
446        SymbolIterator {
447            offset: self.start as usize,
448            nsyms: self.nsyms as usize,
449            count: 0,
450            data: self.data,
451            ctx: self.ctx,
452            strtab: self.strtab,
453        }
454    }
455
456    /// Parses a single Nlist symbol from the binary, with its accompanying name
457    pub fn get(&self, index: usize) -> crate::error::Result<(&'a str, Nlist)> {
458        let sym: Nlist = self
459            .data
460            .pread_with(self.start + (index * Nlist::size_with(&self.ctx)), self.ctx)?;
461        let name = self.data.pread(self.strtab + sym.n_strx)?;
462        Ok((name, sym))
463    }
464}
465
466impl<'a> Debug for Symbols<'a> {
467    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
468        fmt.debug_struct("Symbols")
469            .field("data", &self.data.len())
470            .field("start", &format_args!("{:#?}", self.start))
471            .field("nsyms", &self.nsyms)
472            .field("strtab", &format_args!("{:#x}", self.strtab))
473            .finish()?;
474
475        writeln!(fmt, "Symbol List {{")?;
476        for (i, res) in self.iter().enumerate() {
477            match res {
478                Ok((name, nlist)) => writeln!(
479                    fmt,
480                    "{: >10x} {} sect: {:#x} type: {:#02x} desc: {:#03x}",
481                    nlist.n_value, name, nlist.n_sect, nlist.n_type, nlist.n_desc
482                )?,
483                Err(error) => writeln!(fmt, "  Bad symbol, index: {}, sym: {:?}", i, error)?,
484            }
485        }
486        writeln!(fmt, "}}")
487    }
488}