use super::bytes::{Endian, Source, cstr, to_u64};
use super::consts::{
N_ABS, N_ALT_ENTRY, N_COLD_FUNC, N_EXT, N_INDR, N_NO_DEAD_STRIP, N_PBUD, N_PEXT, N_SECT,
N_STAB, N_SYMBOL_RESOLVER, N_TYPE, N_UNDF, N_WEAK_DEF, N_WEAK_REF, REFERENCED_DYNAMICALLY,
};
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SymbolKind {
Undefined,
Absolute,
Section,
PreboundUndefined,
Indirect,
Unknown(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Symbol<'a> {
pub index: u32,
pub name: &'a [u8],
pub n_strx: u32,
pub n_type: u8,
pub n_sect: u8,
pub n_desc: u16,
pub n_value: u64,
}
impl Symbol<'_> {
#[inline]
#[must_use]
pub fn is_stab(&self) -> bool {
self.n_type & N_STAB != 0
}
#[inline]
#[must_use]
pub fn kind(&self) -> SymbolKind {
match self.n_type & N_TYPE {
N_UNDF => SymbolKind::Undefined,
N_ABS => SymbolKind::Absolute,
N_SECT => SymbolKind::Section,
N_PBUD => SymbolKind::PreboundUndefined,
N_INDR => SymbolKind::Indirect,
other => SymbolKind::Unknown(other),
}
}
#[inline]
#[must_use]
pub fn is_external(&self) -> bool {
!self.is_stab() && self.n_type & N_EXT != 0
}
#[inline]
#[must_use]
pub fn is_private_external(&self) -> bool {
!self.is_stab() && self.n_type & N_PEXT != 0
}
#[inline]
#[must_use]
pub fn is_defined(&self) -> bool {
!self.is_stab()
&& matches!(
self.kind(),
SymbolKind::Section | SymbolKind::Absolute | SymbolKind::Indirect
)
}
#[inline]
#[must_use]
pub fn is_undefined(&self) -> bool {
!self.is_stab() && self.kind() == SymbolKind::Undefined && !self.is_common()
}
#[inline]
#[must_use]
pub fn is_common(&self) -> bool {
!self.is_stab()
&& self.n_type & N_EXT != 0
&& self.kind() == SymbolKind::Undefined
&& self.n_value != 0
}
#[inline]
#[must_use]
pub fn common_align(&self) -> u8 {
((self.n_desc >> 8) & 0x0f) as u8
}
#[inline]
#[must_use]
pub fn library_ordinal(&self) -> u8 {
(self.n_desc >> 8) as u8
}
#[inline]
#[must_use]
pub fn is_weak_def(&self) -> bool {
!self.is_stab() && self.n_desc & N_WEAK_DEF != 0
}
#[inline]
#[must_use]
pub fn is_weak_ref(&self) -> bool {
!self.is_stab() && self.n_desc & N_WEAK_REF != 0
}
#[inline]
#[must_use]
pub fn is_no_dead_strip(&self) -> bool {
!self.is_stab() && self.n_desc & N_NO_DEAD_STRIP != 0
}
#[inline]
#[must_use]
pub fn is_alt_entry(&self) -> bool {
!self.is_stab() && self.n_desc & N_ALT_ENTRY != 0
}
#[inline]
#[must_use]
pub fn is_cold_func(&self) -> bool {
!self.is_stab() && self.n_desc & N_COLD_FUNC != 0
}
#[inline]
#[must_use]
pub fn is_referenced_dynamically(&self) -> bool {
!self.is_stab() && self.n_desc & REFERENCED_DYNAMICALLY != 0
}
#[inline]
#[must_use]
pub fn is_symbol_resolver(&self) -> bool {
!self.is_stab() && self.n_desc & N_SYMBOL_RESOLVER != 0
}
#[inline]
#[must_use]
pub fn stab_type(&self) -> Option<u8> {
self.is_stab().then_some(self.n_type)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SymbolTable<'a> {
records: &'a [u8],
strtab: &'a [u8],
file_offset: u64,
endian: Endian,
is64: bool,
source: Source<'a>,
}
impl<'a> SymbolTable<'a> {
#[must_use]
pub fn empty(endian: Endian, is64: bool, source: Source<'a>) -> Self {
Self {
records: &[],
strtab: &[],
file_offset: 0,
endian,
is64,
source,
}
}
pub(crate) fn new(
records: &'a [u8],
strtab: &'a [u8],
file_offset: u64,
endian: Endian,
is64: bool,
source: Source<'a>,
) -> Self {
Self {
records,
strtab,
file_offset,
endian,
is64,
source,
}
}
#[must_use]
pub fn entry_size(&self) -> usize {
if self.is64 { 16 } else { 12 }
}
#[must_use]
pub fn len(&self) -> usize {
self.records
.len()
.checked_div(self.entry_size())
.unwrap_or(0)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn strtab(&self) -> &'a [u8] {
self.strtab
}
#[must_use]
pub fn string(&self, offset: u32) -> Option<&'a [u8]> {
cstr(self.strtab.get(usize::try_from(offset).ok()?..)?)
}
#[inline]
pub fn get(&self, index: u32) -> Result<Symbol<'a>> {
let size = self.entry_size();
let record = usize::try_from(index)
.ok()
.and_then(|i| i.checked_mul(size))
.and_then(|start| self.records.get(start..)?.get(..size))
.ok_or_else(|| {
self.source.malformed(
self.file_offset,
format!("symbol index {index} (out of range)"),
)
})?;
let e = self.endian;
let n_strx = e.u32(record, 0).unwrap_or(0);
let n_value = if self.is64 {
e.u64(record, 8).unwrap_or(0)
} else {
u64::from(e.u32(record, 8).unwrap_or(0))
};
let name = if n_strx == 0 {
&[][..]
} else {
self.string(n_strx).ok_or_else(|| {
self.source.malformed(
self.file_offset
.saturating_add(u64::from(index).saturating_mul(to_u64(size))),
format!("symbol name offset {n_strx:#x} (symbol {index})"),
)
})?
};
Ok(Symbol {
index,
name,
n_strx,
n_type: *record.get(4).unwrap_or(&0),
n_sect: *record.get(5).unwrap_or(&0),
n_desc: e.u16(record, 6).unwrap_or(0),
n_value,
})
}
pub fn indirect_name(&self, symbol: &Symbol<'_>) -> Result<&'a [u8]> {
if symbol.is_stab() || symbol.kind() != SymbolKind::Indirect {
return Err(self.source.malformed(
self.file_offset,
format!("symbol {} (not an indirect symbol)", symbol.index),
));
}
u32::try_from(symbol.n_value)
.ok()
.and_then(|offset| self.string(offset))
.ok_or_else(|| {
self.source.malformed(
self.file_offset,
format!("indirect symbol name (symbol {})", symbol.index),
)
})
}
#[must_use]
pub fn iter_all(&self) -> SymbolIter<'a> {
SymbolIter {
table: *self,
index: 0,
skip_stabs: false,
}
}
#[must_use]
pub fn iter(&self) -> SymbolIter<'a> {
SymbolIter {
table: *self,
index: 0,
skip_stabs: true,
}
}
}
#[derive(Clone, Debug)]
pub struct SymbolIter<'a> {
table: SymbolTable<'a>,
index: u32,
skip_stabs: bool,
}
impl<'a> Iterator for SymbolIter<'a> {
type Item = Result<Symbol<'a>>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
let index = self.index;
if usize::try_from(index).ok()? >= self.table.len() {
return None;
}
self.index = index.checked_add(1)?;
if self.skip_stabs {
let n_type = usize::try_from(index)
.ok()
.and_then(|i| i.checked_mul(self.table.entry_size()))
.and_then(|at| at.checked_add(4))
.and_then(|at| self.table.records.get(at))
.copied()
.unwrap_or(0);
if n_type & N_STAB != 0 {
continue;
}
}
return Some(self.table.get(index));
}
}
}
#[cfg(test)]
#[allow(clippy::arithmetic_side_effects)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn decodes_and_skips_stabs() {
let src = Source::new(Path::new("t.o"));
let strtab = b"\0_a\0_b\0/tmp/x.o\0";
let mut records = Vec::new();
let mut push = |strx: u32, n_type: u8, sect: u8, desc: u16, value: u64| {
records.extend_from_slice(&strx.to_le_bytes());
records.push(n_type);
records.push(sect);
records.extend_from_slice(&desc.to_le_bytes());
records.extend_from_slice(&value.to_le_bytes());
};
push(7, 0x66, 3, 1, 0); push(1, N_SECT | N_EXT, 1, N_WEAK_DEF | N_ALT_ENTRY, 0x10);
push(4, N_UNDF | N_EXT, 0, 3 << 8, 8); push(100, N_UNDF, 0, 0, 0); let table = SymbolTable::new(&records, strtab, 0, Endian::LITTLE, true, src);
assert_eq!(table.len(), 4);
let all: Vec<_> = table.iter_all().collect();
assert_eq!(all.len(), 4);
assert!(all[0].as_ref().unwrap().is_stab());
assert!(all[3].is_err());
let syms: Vec<_> = table.iter().filter_map(Result::ok).collect();
assert_eq!(syms.len(), 2);
assert_eq!(syms[0].name, b"_a");
assert!(syms[0].is_weak_def() && syms[0].is_alt_entry() && syms[0].is_defined());
assert!(syms[1].is_common() && !syms[1].is_undefined());
assert_eq!(syms[1].common_align(), 3);
assert!(table.get(4).is_err());
}
}