use std::collections::BTreeMap;
use crate::elf::{self, File, Section};
const SYMBOL_TABLE: u32 = 2;
const FUNCTION: u8 = 2;
const LOCAL: u8 = 0;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("the firmware {0}")]
Elf(#[from] elf::Error),
#[error("the firmware has no symbol {0}")]
Missing(String),
#[error("the firmware has two symbols {0}, each local to its own file")]
Twice(String),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Symbols(BTreeMap<String, Named>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Named {
Global(u64),
Local(u64),
Twice,
}
impl Named {
fn with(self, next: Self) -> Self {
match (self, next) {
(Self::Global(_), _) => self,
(_, Self::Global(_)) => next,
(Self::Local(held), Self::Local(value)) if held == value => self,
_ => Self::Twice,
}
}
}
impl Symbols {
pub fn read(image: &[u8], code_address: fn(u64) -> u64) -> Result<Self, Error> {
let file = File::new(image)?;
let mut symbols = BTreeMap::new();
for section in 0..file.sections()? {
let Section {
kind,
offset,
size,
link,
entry,
..
} = file.section(section)?;
if kind != SYMBOL_TABLE || entry == 0 {
continue;
}
let strings = file.section(link)?;
let names = file.bytes(strings.offset, strings.size, "symbol names")?;
file.bytes(offset, size, "symbols")?;
for index in 0..size / entry {
let (name, value, info) = file.symbol(offset + index * entry)?;
let Some(name) = names
.get(
usize::try_from(name)
.map_err(|_too_large| elf::Error::Short("symbol names"))?..,
)
.and_then(|rest| rest.split(|&byte| byte == 0).next())
.filter(|name| !name.is_empty())
else {
continue;
};
let value = if info & 0xF == FUNCTION {
code_address(value)
} else {
value
};
let named = if info >> 4 == LOCAL {
Named::Local(value)
} else {
Named::Global(value)
};
symbols
.entry(String::from_utf8_lossy(name).into_owned())
.and_modify(|held: &mut Named| *held = held.with(named))
.or_insert(named);
}
}
Ok(Self(symbols))
}
pub(crate) fn any(&self) -> bool {
!self.0.is_empty()
}
pub fn get(&self, name: &str) -> Result<u64, Error> {
match self.0.get(name) {
Some(Named::Global(value) | Named::Local(value)) => Ok(*value),
Some(Named::Twice) => Err(Error::Twice(name.to_owned())),
None => Err(Error::Missing(name.to_owned())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn elf(class: u8, order: u8) -> Vec<u8> {
let (wide, big) = (class == 2, order == 2);
let put = |out: &mut Vec<u8>, value: u64, width: usize| {
let bytes = value.to_le_bytes();
let mut field = bytes[..width].to_vec();
if big {
field.reverse();
}
out.extend(field);
};
let word = if wide { 8 } else { 4 };
let header = if wide { 64 } else { 52 };
let section_size: usize = if wide { 64 } else { 40 };
let symbol_size: usize = if wide { 24 } else { 16 };
let strings = b"\0f\0d\0g\0l\0".to_vec();
let global = 0x10;
let mut symbols = Vec::new();
for (name, value, info) in [
(0, 0, 0),
(1, 0x1001, FUNCTION),
(3, 0x2001, 1),
(5, 0x4000, FUNCTION),
(5, 0x3001, global | FUNCTION),
(7, 0x5000, 1),
(7, 0x6000, 1),
(0, 9, 0),
] {
put(&mut symbols, name, 4);
if wide {
put(&mut symbols, info.into(), 1);
put(&mut symbols, 0, 3);
put(&mut symbols, value, 8);
put(&mut symbols, 0, 8);
} else {
put(&mut symbols, value, 4);
put(&mut symbols, 0, 4);
put(&mut symbols, info.into(), 1);
put(&mut symbols, 0, 3);
}
}
let strings_at = header;
let symbols_at = strings_at + strings.len();
let sections_at = symbols_at + symbols.len();
let mut out = vec![
0x7f,
b'E',
b'L',
b'F',
if wide { 2 } else { 1 },
if big { 2 } else { 1 },
];
out.resize(if wide { 40 } else { 32 }, 0);
put(&mut out, sections_at as u64, word);
out.resize(if wide { 58 } else { 46 }, 0);
put(&mut out, section_size as u64, 2);
put(&mut out, 3, 2);
out.resize(header, 0);
out.extend(&strings);
out.extend(&symbols);
let section =
|out: &mut Vec<u8>, kind: u64, at: usize, size: usize, link: u64, entry: usize| {
put(out, 0, 4);
put(out, kind, 4);
if wide {
put(out, 0, 8);
put(out, 0, 8);
put(out, at as u64, 8);
put(out, size as u64, 8);
put(out, link, 4);
put(out, 0, 4);
put(out, 0, 8);
put(out, entry as u64, 8);
} else {
put(out, 0, 8);
put(out, at as u64, 4);
put(out, size as u64, 4);
put(out, link, 4);
put(out, 0, 8);
put(out, entry as u64, 4);
}
};
section(&mut out, 0, 0, 0, 0, 0);
section(&mut out, 3, strings_at, strings.len(), 0, 0);
section(
&mut out,
u64::from(SYMBOL_TABLE),
symbols_at,
symbols.len(),
1,
symbol_size,
);
out
}
fn without_bit_0(value: u64) -> u64 {
value & !1
}
#[test]
fn every_class_and_byte_order_reads_its_named_symbols_with_a_functions_bit_0_clear() {
for class in [1, 2] {
for order in [1, 2] {
let symbols = Symbols::read(&elf(class, order), without_bit_0).unwrap();
assert_eq!(symbols.get("f"), Ok(0x1000), "class {class}, order {order}");
let kept = Symbols::read(&elf(class, order), std::convert::identity).unwrap();
assert_eq!(kept.get("f"), Ok(0x1001), "a family that keeps bit 0");
assert_eq!(symbols.get("d"), Ok(0x2001), "an object keeps its bit 0");
assert_eq!(symbols.0.len(), 4, "a symbol with no name isn't one");
}
}
}
#[test]
fn a_global_name_wins_over_a_local_one_and_two_local_ones_name_neither() {
let symbols = Symbols::read(&elf(2, 1), without_bit_0).unwrap();
assert_eq!(symbols.get("g"), Ok(0x3000));
assert_eq!(symbols.get("l"), Err(Error::Twice("l".to_owned())));
assert_eq!(symbols.get("m"), Err(Error::Missing("m".to_owned())));
}
#[test]
fn a_file_that_isnt_elf_or_ends_early_is_refused() {
assert_eq!(
Symbols::read(b"#!/bin/sh", without_bit_0),
Err(Error::Elf(elf::Error::NotElf))
);
let whole = elf(1, 1);
assert!(matches!(
Symbols::read(&whole[..whole.len() - 8], without_bit_0),
Err(Error::Elf(elf::Error::Short(_)))
));
let mut far = elf(2, 1);
far[40..48].copy_from_slice(&(u64::MAX - 8).to_le_bytes());
assert_eq!(
Symbols::read(&far, without_bit_0),
Err(Error::Elf(elf::Error::Short("section headers"))),
"section headers said to start near the end of the address space"
);
}
}