Revision control
Copy as Markdown
Other Tools
//! "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.
//!
//! Symbols are essentially a type, offset, and the symbol name
use crate::container::{self, Container};
use crate::error;
use crate::mach::load_command;
use core::fmt::{self, Debug};
use scroll::ctx;
use scroll::ctx::SizeWith;
use scroll::{IOread, IOwrite, Pread, Pwrite, SizeWith};
// The n_type field really contains four fields which are used via the following masks.
/// if any of these bits set, a symbolic debugging entry
pub const N_STAB: u8 = 0xe0;
/// private external symbol bit
pub const N_PEXT: u8 = 0x10;
/// mask for the type bits
pub const N_TYPE: u8 = 0x0e;
/// external symbol bit, set for external symbols
pub const N_EXT: u8 = 0x01;
// If the type is N_SECT then the n_sect field contains an ordinal of the
// section the symbol is defined in. The sections are numbered from 1 and
// refer to sections in order they appear in the load commands for the file
// they are in. This means the same ordinal may very well refer to different
// sections in different files.
// The n_value field for all symbol table entries (including N_STAB's) gets
// updated by the link editor based on the value of it's n_sect field and where
// the section n_sect references gets relocated. If the value of the n_sect
// field is NO_SECT then it's n_value field is not changed by the link editor.
/// symbol is not in any section
pub const NO_SECT: u8 = 0;
/// 1 thru 255 inclusive
pub const MAX_SECT: u8 = 255;
/// undefined, n_sect == NO_SECT
pub const N_UNDF: u8 = 0x0;
/// absolute, n_sect == NO_SECT
pub const N_ABS: u8 = 0x2;
/// defined in section number n_sect
pub const N_SECT: u8 = 0xe;
/// prebound undefined (defined in a dylib)
pub const N_PBUD: u8 = 0xc;
/// indirect
pub const N_INDR: u8 = 0xa;
// n_types when N_STAB
pub const N_GSYM: u8 = 0x20;
pub const N_FNAME: u8 = 0x22;
pub const N_FUN: u8 = 0x24;
pub const N_STSYM: u8 = 0x26;
pub const N_LCSYM: u8 = 0x28;
pub const N_BNSYM: u8 = 0x2e;
pub const N_PC: u8 = 0x30;
pub const N_AST: u8 = 0x32;
pub const N_OPT: u8 = 0x3c;
pub const N_RSYM: u8 = 0x40;
pub const N_SLINE: u8 = 0x44;
pub const N_ENSYM: u8 = 0x4e;
pub const N_SSYM: u8 = 0x60;
pub const N_SO: u8 = 0x64;
pub const N_OSO: u8 = 0x66;
pub const N_LSYM: u8 = 0x80;
pub const N_BINCL: u8 = 0x82;
pub const N_SOL: u8 = 0x84;
pub const N_PARAMS: u8 = 0x86;
pub const N_VERSION: u8 = 0x88;
pub const N_OLEVEL: u8 = 0x8a;
pub const N_PSYM: u8 = 0xa0;
pub const N_EINCL: u8 = 0xa2;
pub const N_ENTRY: u8 = 0xa4;
pub const N_LBRAC: u8 = 0xc0;
pub const N_EXCL: u8 = 0xc2;
pub const N_RBRAC: u8 = 0xe0;
pub const N_BCOMM: u8 = 0xe2;
pub const N_ECOMM: u8 = 0xe4;
pub const N_ECOML: u8 = 0xe8;
pub const N_LENG: u8 = 0xfe;
pub const NLIST_TYPE_MASK: u8 = 0xe;
pub const NLIST_TYPE_GLOBAL: u8 = 0x1;
pub const NLIST_TYPE_LOCAL: u8 = 0x0;
/// Mask for reference flags of `n_desc` field.
pub const REFERENCE_TYPE: u16 = 0xf;
/// This symbol is a reference to an external non-lazy (data) symbol.
pub const REFERENCE_FLAG_UNDEFINED_NON_LAZY: u16 = 0x0;
/// This symbol is a reference to an external lazy symbol—that is, to a function call.
pub const REFERENCE_FLAG_UNDEFINED_LAZY: u16 = 0x1;
/// This symbol is defined in this module.
pub const REFERENCE_FLAG_DEFINED: u16 = 0x2;
/// This symbol is defined in this module and is visible only to modules within this
/// shared library.
pub const REFERENCE_FLAG_PRIVATE_DEFINED: u16 = 0x3;
/// This symbol is defined in another module in this file, is a non-lazy (data) symbol,
/// and is visible only to modules within this shared library.
pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY: u16 = 0x4;
/// This symbol is defined in another module in this file, is a lazy (function) symbol,
/// and is visible only to modules within this shared library.
pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY: u16 = 0x5;
// Additional flags of n_desc field.
/// Must be set for any defined symbol that is referenced by dynamic-loader APIs
/// (such as dlsym and NSLookupSymbolInImage) and not ordinary undefined symbol
/// references. The `strip` tool uses this bit to avoid removing symbols that must
/// exist: If the symbol has this bit set, `strip` does not strip it.
pub const REFERENCED_DYNAMICALLY: u16 = 0x10;
/// Sometimes used by the dynamic linker at runtime in a fully linked image. Do not
/// set this bit in a fully linked image.
pub const N_DESC_DISCARDED: u16 = 0x20;
/// When set in a relocatable object file (file type MH_OBJECT) on a defined symbol,
/// indicates to the static linker to never dead-strip the symbol.
// (Note that the same bit (0x20) is used for two nonoverlapping purposes.)
pub const N_NO_DEAD_STRIP: u16 = 0x20;
/// Indicates that this undefined symbol is a weak reference. If the dynamic linker
/// cannot find a definition for this symbol, it sets the address of this symbol to 0.
/// The static linker sets this symbol given the appropriate weak-linking flags.
pub const N_WEAK_REF: u16 = 0x40;
/// Indicates that this symbol is a weak definition. If the static linker or the
/// dynamic linker finds another (non-weak) definition for this symbol, the weak
/// definition is ignored. Only symbols in a coalesced section can be marked as a
/// weak definition.
pub const N_WEAK_DEF: u16 = 0x80;
pub fn n_type_to_str(n_type: u8) -> &'static str {
match n_type {
N_UNDF => "N_UNDF",
N_ABS => "N_ABS",
N_SECT => "N_SECT",
N_PBUD => "N_PBUD",
N_INDR => "N_INDR",
_ => "UNKNOWN_N_TYPE",
}
}
#[repr(C)]
#[derive(Clone, Copy, Pread, Pwrite, SizeWith, IOread, IOwrite)]
pub struct Nlist32 {
/// index into the string table
pub n_strx: u32,
/// type flag, see below
pub n_type: u8,
/// section number or NO_SECT
pub n_sect: u8,
/// see <mach-o/stab.h>
pub n_desc: u16,
/// value of this symbol (or stab offset)
pub n_value: u32,
}
pub const SIZEOF_NLIST_32: usize = 12;
impl Debug for Nlist32 {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Nlist32")
.field("n_strx", &format_args!("{:04}", self.n_strx))
.field("n_type", &format_args!("{:#02x}", self.n_type))
.field("n_sect", &format_args!("{:#x}", self.n_sect))
.field("n_desc", &format_args!("{:#03x}", self.n_desc))
.field("n_value", &format_args!("{:#x}", self.n_value))
.finish()
}
}
#[repr(C)]
#[derive(Clone, Copy, Pread, Pwrite, SizeWith, IOread, IOwrite)]
pub struct Nlist64 {
/// index into the string table
pub n_strx: u32,
/// type flag, see below
pub n_type: u8,
/// section number or NO_SECT
pub n_sect: u8,
/// see <mach-o/stab.h>
pub n_desc: u16,
/// value of this symbol (or stab offset)
pub n_value: u64,
}
pub const SIZEOF_NLIST_64: usize = 16;
impl Debug for Nlist64 {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Nlist64")
.field("n_strx", &format_args!("{:04}", self.n_strx))
.field("n_type", &format_args!("{:#02x}", self.n_type))
.field("n_sect", &format_args!("{:#x}", self.n_sect))
.field("n_desc", &format_args!("{:#03x}", self.n_desc))
.field("n_value", &format_args!("{:#x}", self.n_value))
.finish()
}
}
#[derive(Debug, Clone)]
pub struct Nlist {
/// index into the string table
pub n_strx: usize,
/// type flag, see below
pub n_type: u8,
/// section number or NO_SECT
pub n_sect: usize,
/// see <mach-o/stab.h>
pub n_desc: u16,
/// value of this symbol (or stab offset)
pub n_value: u64,
}
impl Nlist {
/// Gets this symbol's type in bits 0xe
pub fn get_type(&self) -> u8 {
self.n_type & N_TYPE
}
/// Gets the str representation of the type of this symbol
pub fn type_str(&self) -> &'static str {
n_type_to_str(self.get_type())
}
/// Whether this symbol is global or not
pub fn is_global(&self) -> bool {
self.n_type & N_EXT != 0
}
/// Whether this symbol is weak or not
pub fn is_weak(&self) -> bool {
self.n_desc & (N_WEAK_REF | N_WEAK_DEF) != 0
}
/// Whether this symbol is undefined or not
pub fn is_undefined(&self) -> bool {
self.n_sect == 0 && self.n_type & N_TYPE == N_UNDF
}
/// Whether this symbol is a symbolic debugging entry
pub fn is_stab(&self) -> bool {
self.n_type & N_STAB != 0
}
}
impl ctx::SizeWith<container::Ctx> for Nlist {
fn size_with(ctx: &container::Ctx) -> usize {
match ctx.container {
Container::Little => SIZEOF_NLIST_32,
Container::Big => SIZEOF_NLIST_64,
}
}
}
impl From<Nlist32> for Nlist {
fn from(nlist: Nlist32) -> Self {
Nlist {
n_strx: nlist.n_strx as usize,
n_type: nlist.n_type,
n_sect: nlist.n_sect as usize,
n_desc: nlist.n_desc,
n_value: u64::from(nlist.n_value),
}
}
}
impl From<Nlist64> for Nlist {
fn from(nlist: Nlist64) -> Self {
Nlist {
n_strx: nlist.n_strx as usize,
n_type: nlist.n_type,
n_sect: nlist.n_sect as usize,
n_desc: nlist.n_desc,
n_value: nlist.n_value,
}
}
}
impl From<Nlist> for Nlist32 {
fn from(nlist: Nlist) -> Self {
Nlist32 {
n_strx: nlist.n_strx as u32,
n_type: nlist.n_type,
n_sect: nlist.n_sect as u8,
n_desc: nlist.n_desc,
n_value: nlist.n_value as u32,
}
}
}
impl From<Nlist> for Nlist64 {
fn from(nlist: Nlist) -> Self {
Nlist64 {
n_strx: nlist.n_strx as u32,
n_type: nlist.n_type,
n_sect: nlist.n_sect as u8,
n_desc: nlist.n_desc,
n_value: nlist.n_value,
}
}
}
impl<'a> ctx::TryFromCtx<'a, container::Ctx> for Nlist {
type Error = crate::error::Error;
fn try_from_ctx(
bytes: &'a [u8],
container::Ctx { container, le }: container::Ctx,
) -> crate::error::Result<(Self, usize)> {
let nlist = match container {
Container::Little => (bytes.pread_with::<Nlist32>(0, le)?.into(), SIZEOF_NLIST_32),
Container::Big => (bytes.pread_with::<Nlist64>(0, le)?.into(), SIZEOF_NLIST_64),
};
Ok(nlist)
}
}
impl ctx::TryIntoCtx<container::Ctx> for Nlist {
type Error = crate::error::Error;
fn try_into_ctx(
self,
bytes: &mut [u8],
container::Ctx { container, le }: container::Ctx,
) -> Result<usize, Self::Error> {
let size = match container {
Container::Little => bytes.pwrite_with::<Nlist32>(self.into(), 0, le)?,
Container::Big => bytes.pwrite_with::<Nlist64>(self.into(), 0, le)?,
};
Ok(size)
}
}
impl ctx::IntoCtx<container::Ctx> for Nlist {
fn into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) {
bytes.pwrite_with(self, 0, ctx).unwrap();
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SymbolsCtx {
pub nsyms: usize,
pub strtab: usize,
pub ctx: container::Ctx,
}
impl<'a, T: ?Sized> ctx::TryFromCtx<'a, SymbolsCtx, T> for Symbols<'a>
where
T: AsRef<[u8]>,
{
type Error = crate::error::Error;
fn try_from_ctx(
bytes: &'a T,
SymbolsCtx { nsyms, strtab, ctx }: SymbolsCtx,
) -> crate::error::Result<(Self, usize)> {
let data = bytes.as_ref();
Ok((
Symbols {
data,
start: 0,
nsyms,
strtab,
ctx,
},
data.len(),
))
}
}
#[derive(Default)]
pub struct SymbolIterator<'a> {
data: &'a [u8],
nsyms: usize,
offset: usize,
count: usize,
ctx: container::Ctx,
strtab: usize,
}
impl<'a> Iterator for SymbolIterator<'a> {
type Item = error::Result<(&'a str, Nlist)>;
fn next(&mut self) -> Option<Self::Item> {
if self.count >= self.nsyms {
None
} else {
self.count += 1;
match self.data.gread_with::<Nlist>(&mut self.offset, self.ctx) {
Ok(symbol) => match self.data.pread(self.strtab + symbol.n_strx) {
Ok(name) => Some(Ok((name, symbol))),
Err(e) => Some(Err(e.into())),
},
Err(e) => Some(Err(e)),
}
}
}
}
/// A zero-copy "nlist" style symbol table ("stab"), including the string table
pub struct Symbols<'a> {
data: &'a [u8],
start: usize,
nsyms: usize,
// TODO: we can use an actual strtab here and tie it to symbols lifetime
strtab: usize,
ctx: container::Ctx,
}
impl<'a, 'b> IntoIterator for &'b Symbols<'a> {
type Item = <SymbolIterator<'a> as Iterator>::Item;
type IntoIter = SymbolIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> Symbols<'a> {
/// Creates a new symbol table with `count` elements, from the `start` offset, using the string table at `strtab`, with a _default_ ctx.
////
/// **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
pub fn new(
bytes: &'a [u8],
start: usize,
count: usize,
strtab: usize,
) -> error::Result<Symbols<'a>> {
let nsyms = count;
Ok(Symbols {
data: bytes,
start,
nsyms,
strtab,
ctx: container::Ctx::default(),
})
}
pub fn parse(
bytes: &'a [u8],
symtab: &load_command::SymtabCommand,
ctx: container::Ctx,
) -> error::Result<Symbols<'a>> {
// we need to normalize the strtab offset before we receive the truncated bytes in pread_with
let strtab = symtab
.stroff
.checked_sub(symtab.symoff)
.ok_or_else(|| error::Error::Malformed("invalid symbol table offset".into()))?;
bytes.pread_with(
symtab.symoff as usize,
SymbolsCtx {
nsyms: symtab.nsyms as usize,
strtab: strtab as usize,
ctx,
},
)
}
pub fn iter(&self) -> SymbolIterator<'a> {
SymbolIterator {
offset: self.start as usize,
nsyms: self.nsyms as usize,
count: 0,
data: self.data,
ctx: self.ctx,
strtab: self.strtab,
}
}
/// Parses a single Nlist symbol from the binary, with its accompanying name
pub fn get(&self, index: usize) -> crate::error::Result<(&'a str, Nlist)> {
let sym: Nlist = self
.data
.pread_with(self.start + (index * Nlist::size_with(&self.ctx)), self.ctx)?;
let name = self.data.pread(self.strtab + sym.n_strx)?;
Ok((name, sym))
}
}
impl<'a> Debug for Symbols<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Symbols")
.field("data", &self.data.len())
.field("start", &format_args!("{:#?}", self.start))
.field("nsyms", &self.nsyms)
.field("strtab", &format_args!("{:#x}", self.strtab))
.finish()?;
writeln!(fmt, "Symbol List {{")?;
for (i, res) in self.iter().enumerate() {
match res {
Ok((name, nlist)) => writeln!(
fmt,
"{: >10x} {} sect: {:#x} type: {:#02x} desc: {:#03x}",
nlist.n_value, name, nlist.n_sect, nlist.n_type, nlist.n_desc
)?,
Err(error) => writeln!(fmt, " Bad symbol, index: {}, sym: {:?}", i, error)?,
}
}
writeln!(fmt, "}}")
}
}