use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum ElfError {
NotElf,
NotArm32,
Truncated(&'static str),
Unsupported(String),
}
impl fmt::Display for ElfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ElfError::NotElf => f.write_str("not an ELF file"),
ElfError::NotArm32 => f.write_str("not a 32-bit little-endian ARM ELF"),
ElfError::Truncated(what) => write!(f, "truncated: {what}"),
ElfError::Unsupported(why) => write!(f, "unsupported: {why}"),
}
}
}
#[derive(Debug, Clone)]
pub(super) struct Object {
pub(super) image: Vec<u8>,
#[allow(dead_code)]
pub(super) symbols: Vec<(String, u32)>,
}
fn le(bytes: &[u8], at: usize, len: usize, what: &'static str) -> Result<u64, ElfError> {
let end = at.checked_add(len).ok_or(ElfError::Truncated(what))?;
let slice = bytes.get(at..end).ok_or(ElfError::Truncated(what))?;
let mut value = 0u64;
for (i, byte) in slice.iter().enumerate() {
value |= u64::from(*byte) << (8 * i);
}
Ok(value)
}
fn cstr(bytes: &[u8], at: usize) -> String {
let rest = bytes.get(at..).unwrap_or(&[]);
let end = rest.iter().position(|b| *b == 0).unwrap_or(rest.len());
String::from_utf8_lossy(&rest[..end]).into_owned()
}
impl Object {
pub(super) fn parse(bytes: &[u8]) -> Result<Object, ElfError> {
if bytes.len() < 52 || bytes[..4] != [0x7f, b'E', b'L', b'F'] {
return Err(ElfError::NotElf);
}
if bytes[4] != 1 || bytes[5] != 1 || le(bytes, 18, 2, "e_machine")? != 40 {
return Err(ElfError::NotArm32);
}
let shoff = le(bytes, 32, 4, "e_shoff")? as usize;
let shentsize = le(bytes, 46, 2, "e_shentsize")? as usize;
let shnum = le(bytes, 48, 2, "e_shnum")? as usize;
let shstrndx = le(bytes, 50, 2, "e_shstrndx")? as usize;
let section = |i: usize| -> Result<Section, ElfError> {
let at = shoff + i * shentsize;
Ok(Section {
name: le(bytes, at, 4, "sh_name")? as usize,
kind: le(bytes, at + 4, 4, "sh_type")?,
flags: le(bytes, at + 8, 4, "sh_flags")?,
offset: le(bytes, at + 16, 4, "sh_offset")? as usize,
size: le(bytes, at + 20, 4, "sh_size")? as usize,
link: le(bytes, at + 24, 4, "sh_link")? as usize,
info: le(bytes, at + 28, 4, "sh_info")? as usize,
entsize: le(bytes, at + 36, 4, "sh_entsize")? as usize,
})
};
let shstr = section(shstrndx)?.offset;
let mut image: Option<Vec<u8>> = None;
let mut alloc_index = None;
let mut symbols = Vec::new();
for i in 0..shnum {
let sh = section(i)?;
if sh.flags & 0x2 == 0 {
continue;
}
if alloc_index.is_some() {
return Err(ElfError::Unsupported(alloc::format!(
"more than one allocatable section; `{}` is the second. \
Keep the whole test in `.text`.",
cstr(bytes, shstr + sh.name)
)));
}
alloc_index = Some(i);
if sh.kind != 1 {
return Err(ElfError::Unsupported(
"the allocatable section has no contents".to_string(),
));
}
let end = sh.offset + sh.size;
image = Some(
bytes
.get(sh.offset..end)
.ok_or(ElfError::Truncated("section contents"))?
.to_vec(),
);
}
let Some(image) = image else {
return Err(ElfError::Unsupported("no allocatable section".to_string()));
};
let alloc_index = alloc_index.expect("set with the image");
for i in 0..shnum {
let sh = section(i)?;
if (sh.kind == 9 || sh.kind == 4) && sh.info == alloc_index && sh.size != 0 {
return Err(ElfError::Unsupported(alloc::format!(
"{} relocation(s) against the loaded section; the test must not \
reference a symbol whose address the assembler cannot compute",
sh.size / sh.entsize.max(1)
)));
}
if sh.kind != 2 {
continue;
}
let strtab = section(sh.link)?.offset;
let entsize = sh.entsize.max(16);
for k in 0..(sh.size / entsize) {
let at = sh.offset + k * entsize;
let name = le(bytes, at, 4, "st_name")? as usize;
let value = le(bytes, at + 4, 4, "st_value")? as u32;
let shndx = le(bytes, at + 14, 2, "st_shndx")? as usize;
if shndx != alloc_index || name == 0 {
continue;
}
symbols.push((cstr(bytes, strtab + name), value));
}
}
Ok(Object { image, symbols })
}
#[must_use]
#[allow(dead_code)]
pub(super) fn symbol(&self, name: &str) -> Option<u32> {
self.symbols
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| *v)
}
}
struct Section {
name: usize,
kind: u64,
flags: u64,
offset: usize,
size: usize,
link: usize,
info: usize,
entsize: usize,
}