use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ElfError {
NotElf,
BadClass(u8),
BigEndian,
NotRiscv(u16),
Truncated(&'static str),
}
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::BadClass(c) => write!(f, "unknown ELF class {c}"),
ElfError::BigEndian => f.write_str("big-endian ELF; RISC-V images are little-endian"),
ElfError::NotRiscv(m) => write!(f, "ELF machine {m} is not RISC-V (243)"),
ElfError::Truncated(what) => write!(f, "truncated ELF: {what} runs off the end"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub addr: u64,
pub bytes: Vec<u8>,
pub mem_len: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Elf {
pub is_64: bool,
pub entry: u64,
pub segments: Vec<Segment>,
pub symbols: Vec<(String, u64)>,
}
fn le(bytes: &[u8], at: usize, len: usize, what: &'static str) -> Result<u64, ElfError> {
let slice = bytes.get(at..at + len).ok_or(ElfError::Truncated(what))?;
let mut v = 0u64;
for (i, b) in slice.iter().enumerate() {
v |= u64::from(*b) << (8 * i);
}
Ok(v)
}
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 Elf {
pub fn parse(bytes: &[u8]) -> Result<Elf, ElfError> {
if bytes.len() < 20 || bytes[..4] != [0x7f, b'E', b'L', b'F'] {
return Err(ElfError::NotElf);
}
let is_64 = match bytes[4] {
1 => false,
2 => true,
other => return Err(ElfError::BadClass(other)),
};
if bytes[5] != 1 {
return Err(ElfError::BigEndian);
}
let machine = le(bytes, 18, 2, "machine")? as u16;
if machine != 243 {
return Err(ElfError::NotRiscv(machine));
}
let (w, e_entry, e_phoff, e_shoff, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx) =
if is_64 {
(
8usize, 24usize, 32usize, 40usize, 54usize, 56usize, 58usize, 60usize, 62usize,
)
} else {
(
4usize, 24usize, 28usize, 32usize, 42usize, 44usize, 46usize, 48usize, 50usize,
)
};
let _ = e_shstrndx;
let entry = le(bytes, e_entry, w, "entry")?;
let phoff = le(bytes, e_phoff, w, "program header offset")? as usize;
let shoff = le(bytes, e_shoff, w, "section header offset")? as usize;
let phentsize = le(bytes, e_phentsize, 2, "program header size")? as usize;
let phnum = le(bytes, e_phnum, 2, "program header count")? as usize;
let shentsize = le(bytes, e_shentsize, 2, "section header size")? as usize;
let shnum = le(bytes, e_shnum, 2, "section header count")? as usize;
let mut segments = Vec::new();
for i in 0..phnum {
let at = phoff + i * phentsize;
let p_type = le(bytes, at, 4, "segment type")?;
if p_type != 1 {
continue; }
let (offset, paddr, filesz, memsz) = if is_64 {
(
le(bytes, at + 8, 8, "segment offset")?,
le(bytes, at + 24, 8, "segment address")?,
le(bytes, at + 32, 8, "segment file size")?,
le(bytes, at + 40, 8, "segment memory size")?,
)
} else {
(
le(bytes, at + 4, 4, "segment offset")?,
le(bytes, at + 12, 4, "segment address")?,
le(bytes, at + 16, 4, "segment file size")?,
le(bytes, at + 20, 4, "segment memory size")?,
)
};
let start = offset as usize;
let end = start + filesz as usize;
let data = bytes
.get(start..end)
.ok_or(ElfError::Truncated("a segment"))?;
segments.push(Segment {
addr: paddr,
bytes: data.to_vec(),
mem_len: memsz,
});
}
let mut symbols = Vec::new();
for i in 0..shnum {
let at = shoff + i * shentsize;
let Ok(sh_type) = le(bytes, at + 4, 4, "section type") else {
break;
};
if sh_type != 2 {
continue;
}
let (link, offset, size, entsize) = if is_64 {
(
le(bytes, at + 40, 4, "symbol table link")?,
le(bytes, at + 24, 8, "symbol table offset")?,
le(bytes, at + 32, 8, "symbol table size")?,
le(bytes, at + 56, 8, "symbol size")?,
)
} else {
(
le(bytes, at + 24, 4, "symbol table link")?,
le(bytes, at + 16, 4, "symbol table offset")?,
le(bytes, at + 20, 4, "symbol table size")?,
le(bytes, at + 36, 4, "symbol size")?,
)
};
if entsize == 0 {
continue;
}
let str_at = shoff + link as usize * shentsize;
let str_off = if is_64 {
le(bytes, str_at + 24, 8, "string table offset")?
} else {
le(bytes, str_at + 16, 4, "string table offset")?
} as usize;
let count = (size / entsize) as usize;
for s in 0..count {
let sym = offset as usize + s * entsize as usize;
let name_off = le(bytes, sym, 4, "symbol name")? as usize;
let value = if is_64 {
le(bytes, sym + 8, 8, "symbol value")?
} else {
le(bytes, sym + 4, 4, "symbol value")?
};
if name_off == 0 {
continue;
}
symbols.push((cstr(bytes, str_off + name_off), value));
}
}
Ok(Elf {
is_64,
entry,
segments,
symbols,
})
}
#[must_use]
pub fn symbol(&self, name: &str) -> Option<u64> {
self.symbols
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| *v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn tiny_elf() -> Vec<u8> {
let phoff = 64usize;
let phentsize = 56usize;
let shoff = phoff + phentsize;
let shentsize = 64usize;
let shnum = 3usize;
let payload_off = shoff + shnum * shentsize;
let payload = [0x13u8, 0x00, 0x00, 0x00]; let symtab_off = payload_off + payload.len();
let symentsize = 24usize;
let strtab_off = symtab_off + 2 * symentsize;
let names = b"\0tohost\0";
let mut f = vec![0u8; strtab_off + names.len()];
f[..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
f[4] = 2; f[5] = 1; f[6] = 1; f[16..18].copy_from_slice(&2u16.to_le_bytes()); f[18..20].copy_from_slice(&243u16.to_le_bytes()); f[24..32].copy_from_slice(&0x8000_0000u64.to_le_bytes()); f[32..40].copy_from_slice(&(phoff as u64).to_le_bytes());
f[40..48].copy_from_slice(&(shoff as u64).to_le_bytes());
f[54..56].copy_from_slice(&(phentsize as u16).to_le_bytes());
f[56..58].copy_from_slice(&1u16.to_le_bytes()); f[58..60].copy_from_slice(&(shentsize as u16).to_le_bytes());
f[60..62].copy_from_slice(&(shnum as u16).to_le_bytes());
let p = phoff;
f[p..p + 4].copy_from_slice(&1u32.to_le_bytes()); f[p + 8..p + 16].copy_from_slice(&(payload_off as u64).to_le_bytes());
f[p + 16..p + 24].copy_from_slice(&0x8000_0000u64.to_le_bytes()); f[p + 24..p + 32].copy_from_slice(&0x8000_0000u64.to_le_bytes()); f[p + 32..p + 40].copy_from_slice(&(payload.len() as u64).to_le_bytes());
f[p + 40..p + 48].copy_from_slice(&8u64.to_le_bytes());
let s = shoff + shentsize;
f[s + 4..s + 8].copy_from_slice(&2u32.to_le_bytes()); f[s + 24..s + 32].copy_from_slice(&(symtab_off as u64).to_le_bytes());
f[s + 32..s + 40].copy_from_slice(&(2 * symentsize as u64).to_le_bytes());
f[s + 40..s + 44].copy_from_slice(&2u32.to_le_bytes()); f[s + 56..s + 64].copy_from_slice(&(symentsize as u64).to_le_bytes());
let s = shoff + 2 * shentsize;
f[s + 4..s + 8].copy_from_slice(&3u32.to_le_bytes()); f[s + 24..s + 32].copy_from_slice(&(strtab_off as u64).to_le_bytes());
f[s + 32..s + 40].copy_from_slice(&(names.len() as u64).to_le_bytes());
f[payload_off..payload_off + payload.len()].copy_from_slice(&payload);
let y = symtab_off + symentsize;
f[y..y + 4].copy_from_slice(&1u32.to_le_bytes()); f[y + 8..y + 16].copy_from_slice(&0x8000_1000u64.to_le_bytes());
f[strtab_off..strtab_off + names.len()].copy_from_slice(names);
f
}
#[test]
fn a_minimal_image_parses() {
let elf = Elf::parse(&tiny_elf()).unwrap();
assert!(elf.is_64);
assert_eq!(elf.entry, 0x8000_0000);
assert_eq!(elf.segments.len(), 1);
assert_eq!(elf.segments[0].addr, 0x8000_0000);
assert_eq!(elf.segments[0].bytes, [0x13, 0, 0, 0]);
assert_eq!(elf.segments[0].mem_len, 8, "bss must be visible as a gap");
assert_eq!(elf.symbol("tohost"), Some(0x8000_1000));
assert_eq!(elf.symbol("fromhost"), None);
}
#[test]
fn the_wrong_kind_of_file_is_rejected_rather_than_guessed_at() {
assert_eq!(
Elf::parse(b"not an elf at all").unwrap_err(),
ElfError::NotElf
);
let mut f = tiny_elf();
f[4] = 7;
assert_eq!(Elf::parse(&f).unwrap_err(), ElfError::BadClass(7));
let mut f = tiny_elf();
f[5] = 2;
assert_eq!(Elf::parse(&f).unwrap_err(), ElfError::BigEndian);
let mut f = tiny_elf();
f[18] = 62; assert!(matches!(Elf::parse(&f), Err(ElfError::NotRiscv(_))));
}
#[test]
fn a_truncated_file_is_an_error_not_a_panic() {
let full = tiny_elf();
for cut in [20, 40, 64, 80, full.len() - 1] {
let _ = Elf::parse(&full[..cut]);
}
assert!(Elf::parse(&full[..70]).is_err());
}
}