use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use super::isa::Endian;
pub const EM_MIPS: u16 = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ElfError {
NotElf,
Not32Bit(u8),
BadEndian(u8),
NotMips(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::Not32Bit(c) => write!(f, "ELF class {c} is not 32-bit"),
ElfError::BadEndian(d) => write!(f, "unknown ELF data encoding {d}"),
ElfError::NotMips(m) => write!(f, "ELF machine {m} is not MIPS ({EM_MIPS})"),
ElfError::Truncated(what) => write!(f, "truncated ELF: {what} runs off the end"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub addr: u32,
pub vaddr: u32,
pub bytes: Vec<u8>,
pub mem_len: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Elf {
pub endian: Endian,
pub entry: u32,
pub segments: Vec<Segment>,
pub symbols: Vec<(String, u32)>,
}
fn num(
bytes: &[u8],
at: usize,
len: usize,
endian: Endian,
what: &'static str,
) -> Result<u32, ElfError> {
let slice = bytes
.get(at..at.checked_add(len).ok_or(ElfError::Truncated(what))?)
.ok_or(ElfError::Truncated(what))?;
let mut v = 0u32;
if endian.is_big() {
for b in slice {
v = (v << 8) | u32::from(*b);
}
} else {
for (i, b) in slice.iter().enumerate() {
v |= u32::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() < 52 || bytes[..4] != [0x7f, b'E', b'L', b'F'] {
return Err(ElfError::NotElf);
}
if bytes[4] != 1 {
return Err(ElfError::Not32Bit(bytes[4]));
}
let endian = match bytes[5] {
1 => Endian::Little,
2 => Endian::Big,
other => return Err(ElfError::BadEndian(other)),
};
let machine = num(bytes, 18, 2, endian, "e_machine")? as u16;
if machine != EM_MIPS {
return Err(ElfError::NotMips(machine));
}
let entry = num(bytes, 24, 4, endian, "e_entry")?;
let phoff = num(bytes, 28, 4, endian, "e_phoff")? as usize;
let shoff = num(bytes, 32, 4, endian, "e_shoff")? as usize;
let phentsize = num(bytes, 42, 2, endian, "e_phentsize")? as usize;
let phnum = num(bytes, 44, 2, endian, "e_phnum")? as usize;
let shentsize = num(bytes, 46, 2, endian, "e_shentsize")? as usize;
let shnum = num(bytes, 48, 2, endian, "e_shnum")? as usize;
let mut segments = Vec::new();
for i in 0..phnum {
let at = phoff + i * phentsize;
if num(bytes, at, 4, endian, "p_type")? != 1 {
continue;
}
let offset = num(bytes, at + 4, 4, endian, "p_offset")? as usize;
let vaddr = num(bytes, at + 8, 4, endian, "p_vaddr")?;
let paddr = num(bytes, at + 12, 4, endian, "p_paddr")?;
let filesz = num(bytes, at + 16, 4, endian, "p_filesz")? as usize;
let memsz = num(bytes, at + 20, 4, endian, "p_memsz")?;
let data = bytes
.get(
offset
..offset
.checked_add(filesz)
.ok_or(ElfError::Truncated("a segment"))?,
)
.ok_or(ElfError::Truncated("a segment"))?;
segments.push(Segment {
addr: paddr,
vaddr,
bytes: data.to_vec(),
mem_len: memsz,
});
}
let mut symbols = Vec::new();
for i in 0..shnum {
let at = shoff + i * shentsize;
if num(bytes, at, 4, endian, "sh_type").unwrap_or(0) != 2 {
continue;
}
let offset = num(bytes, at + 16, 4, endian, "sh_offset")? as usize;
let size = num(bytes, at + 20, 4, endian, "sh_size")? as usize;
let link = num(bytes, at + 24, 4, endian, "sh_link")? as usize;
let entsize = num(bytes, at + 36, 4, endian, "sh_entsize")? as usize;
if entsize == 0 || link >= shnum {
continue;
}
let strtab = shoff + link * shentsize;
let stroff = num(bytes, strtab + 16, 4, endian, "sh_offset")? as usize;
let strsize = num(bytes, strtab + 20, 4, endian, "sh_size")? as usize;
let strings = bytes.get(stroff..stroff + strsize).unwrap_or(&[]);
for k in 0..(size / entsize) {
let sym = offset + k * entsize;
let name = num(bytes, sym, 4, endian, "st_name")? as usize;
let value = num(bytes, sym + 4, 4, endian, "st_value")?;
if name == 0 {
continue;
}
symbols.push((cstr(strings, name), value));
}
}
Ok(Elf {
endian,
entry,
segments,
symbols,
})
}
#[must_use]
pub fn symbol(&self, name: &str) -> Option<u32> {
self.symbols
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| *v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn image(endian: Endian, machine: u16, class: u8) -> Vec<u8> {
let put = |out: &mut Vec<u8>, at: usize, len: usize, v: u32| {
for i in 0..len {
let byte = if endian.is_big() {
(v >> (8 * (len - 1 - i))) as u8
} else {
(v >> (8 * i)) as u8
};
out[at + i] = byte;
}
};
let mut out = vec![0u8; 52 + 32 + 8];
out[..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
out[4] = class;
out[5] = if endian.is_big() { 2 } else { 1 };
out[6] = 1;
put(&mut out, 16, 2, 2); put(&mut out, 18, 2, u32::from(machine));
put(&mut out, 24, 4, 0x8000_0400); put(&mut out, 28, 4, 52); put(&mut out, 42, 2, 32); put(&mut out, 44, 2, 1); put(&mut out, 46, 2, 40); put(&mut out, 52, 4, 1); put(&mut out, 56, 4, 84); put(&mut out, 60, 4, 0x8000_0400); put(&mut out, 64, 4, 0x0000_0400); put(&mut out, 68, 4, 8); put(&mut out, 72, 4, 16); out[84..92].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
out
}
#[test]
fn a_little_endian_image_parses() {
let elf = Elf::parse(&image(Endian::Little, EM_MIPS, 1)).expect("it parses");
assert_eq!(elf.endian, Endian::Little);
assert_eq!(elf.entry, 0x8000_0400);
assert_eq!(elf.segments.len(), 1);
let seg = &elf.segments[0];
assert_eq!(seg.addr, 0x0000_0400);
assert_eq!(seg.vaddr, 0x8000_0400);
assert_eq!(seg.bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(seg.mem_len, 16, "eight bytes of .bss beyond the file");
}
#[test]
fn a_big_endian_image_parses_the_same_way() {
let elf = Elf::parse(&image(Endian::Big, EM_MIPS, 1)).expect("it parses");
assert_eq!(elf.endian, Endian::Big);
assert_eq!(elf.entry, 0x8000_0400);
assert_eq!(elf.segments[0].addr, 0x0000_0400);
}
#[test]
fn the_wrong_architecture_is_refused_rather_than_loaded() {
assert_eq!(
Elf::parse(&image(Endian::Little, 243, 1)),
Err(ElfError::NotMips(243))
);
assert_eq!(
Elf::parse(&image(Endian::Little, EM_MIPS, 2)),
Err(ElfError::Not32Bit(2))
);
assert_eq!(Elf::parse(b"not an elf at all!!!"), Err(ElfError::NotElf));
}
#[test]
fn a_truncated_file_is_an_error_rather_than_a_panic() {
let mut bytes = image(Endian::Little, EM_MIPS, 1);
bytes.truncate(60);
assert!(matches!(Elf::parse(&bytes), Err(ElfError::Truncated(_))));
}
}