use crate::common::VAddr;
use crate::format::elf::{Elf64File, ElfClass, SHT_DYNSYM, SHT_SYMTAB};
use crate::analysis::function_map::{Function, FunctionSource};
const SYM64_SIZE: usize = 24;
const SYM32_SIZE: usize = 16;
const SHN_UNDEF: u16 = 0;
const STT_FUNC: u8 = 2;
const STT_GNU_IFUNC: u8 = 10;
#[derive(Debug, thiserror::Error)]
pub enum SymbolError {
#[error("symbol table section #{idx} has unexpected sh_entsize {got}, expected {expected}")]
BadEntsize { idx: usize, got: u64, expected: u64 },
#[error("symbol table section #{idx} has size {size} not a multiple of entry size {entry}")]
BadTableSize { idx: usize, size: u64, entry: u64 },
#[error("symbol table section #{idx} links to invalid string table index {strtab_idx}")]
BadStrtabIndex { idx: usize, strtab_idx: usize },
#[error(
"symbol #{sym} in section #{idx} has st_name = {name} which exceeds string table size {strtab_size}"
)]
StrtabOutOfBounds {
idx: usize,
sym: usize,
name: u64,
strtab_size: usize,
},
#[error("symbol #{sym} in section #{idx} has a non-UTF-8 name at strtab offset {offset}")]
NameNotUtf8 {
idx: usize,
sym: usize,
offset: usize,
},
}
pub fn discover_from_symbol_tables(elf: &Elf64File) -> Result<Vec<Function>, SymbolError> {
let mut out = Vec::new();
for (idx, shdr, _data) in elf.sections() {
let source = match shdr.sh_type {
SHT_SYMTAB => FunctionSource::SymTab,
SHT_DYNSYM => FunctionSource::DynSym,
_ => continue,
};
collect_one_table(elf, idx, source, &mut out)?;
}
Ok(out)
}
fn collect_one_table(
elf: &Elf64File,
idx: usize,
source: FunctionSource,
out: &mut Vec<Function>,
) -> Result<(), SymbolError> {
let shdr = &elf.shdrs[idx];
let entry_size = match elf.class {
ElfClass::Elf32 => SYM32_SIZE,
ElfClass::Elf64 => SYM64_SIZE,
};
if shdr.sh_entsize != entry_size as u64 {
return Err(SymbolError::BadEntsize {
idx,
got: shdr.sh_entsize,
expected: entry_size as u64,
});
}
let table = elf.section_data(idx).unwrap_or(&[]);
if !table.len().is_multiple_of(entry_size) {
return Err(SymbolError::BadTableSize {
idx,
size: table.len() as u64,
entry: entry_size as u64,
});
}
let strtab_idx = shdr.sh_link as usize;
let strtab = elf
.section_data(strtab_idx)
.ok_or(SymbolError::BadStrtabIndex { idx, strtab_idx })?;
for (sym_idx, chunk) in table.chunks_exact(entry_size).enumerate() {
let (st_name, st_info, st_shndx, st_value, st_size) = match elf.class {
ElfClass::Elf64 => parse_sym64(chunk),
ElfClass::Elf32 => parse_sym32(chunk),
};
let st_type = st_info & 0xf;
if st_type != STT_FUNC && st_type != STT_GNU_IFUNC {
continue;
}
if st_shndx == SHN_UNDEF {
continue;
}
let name = read_strtab_cstring(strtab, st_name as usize).map_err(|()| {
SymbolError::StrtabOutOfBounds {
idx,
sym: sym_idx,
name: u64::from(st_name),
strtab_size: strtab.len(),
}
})?;
let name = std::str::from_utf8(name)
.map_err(|_| SymbolError::NameNotUtf8 {
idx,
sym: sym_idx,
offset: st_name as usize,
})?
.to_string();
out.push(Function {
addr: VAddr(st_value),
size: st_size,
name,
sources: vec![source],
});
}
Ok(())
}
fn parse_sym64(chunk: &[u8]) -> (u32, u8, u16, u64, u64) {
let st_name = u32::from_le_bytes(chunk[0..4].try_into().unwrap());
let st_info = chunk[4];
let st_shndx = u16::from_le_bytes(chunk[6..8].try_into().unwrap());
let st_value = u64::from_le_bytes(chunk[8..16].try_into().unwrap());
let st_size = u64::from_le_bytes(chunk[16..24].try_into().unwrap());
(st_name, st_info, st_shndx, st_value, st_size)
}
fn parse_sym32(chunk: &[u8]) -> (u32, u8, u16, u64, u64) {
let st_name = u32::from_le_bytes(chunk[0..4].try_into().unwrap());
let st_value = u32::from_le_bytes(chunk[4..8].try_into().unwrap());
let st_size = u32::from_le_bytes(chunk[8..12].try_into().unwrap());
let st_info = chunk[12];
let st_shndx = u16::from_le_bytes(chunk[14..16].try_into().unwrap());
(
st_name,
st_info,
st_shndx,
u64::from(st_value),
u64::from(st_size),
)
}
fn read_strtab_cstring(strtab: &[u8], offset: usize) -> Result<&[u8], ()> {
let tail = strtab.get(offset..).ok_or(())?;
let nul = tail.iter().position(|&b| b == 0).ok_or(())?;
Ok(&tail[..nul])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_strtab_returns_string_at_offset() {
let strtab = b"\0main\0puts\0";
assert_eq!(read_strtab_cstring(strtab, 1), Ok(&b"main"[..]));
assert_eq!(read_strtab_cstring(strtab, 6), Ok(&b"puts"[..]));
assert_eq!(read_strtab_cstring(strtab, 0), Ok(&b""[..]));
}
#[test]
fn read_strtab_rejects_offset_past_end() {
let strtab = b"\0main\0";
assert!(read_strtab_cstring(strtab, 100).is_err());
}
#[test]
fn read_strtab_rejects_unterminated() {
let strtab = b"main"; assert!(read_strtab_cstring(strtab, 0).is_err());
}
}