use std::cmp::min;
use std::collections::BTreeMap;
use elf::abi::{EM_RISCV, ET_EXEC, PF_X, PT_LOAD};
use elf::endian::LittleEndian;
use elf::file::Class;
use elf::ElfBytes;
pub const MAXIMUM_MEMORY_SIZE: u32 = u32::MAX;
pub const WORD_SIZE: usize = 4;
#[derive(Debug, Clone)]
pub struct Elf {
pub instructions: Vec<u32>,
pub pc_start: u32,
pub pc_base: u32,
pub memory_image: BTreeMap<u32, u32>,
}
impl Elf {
pub const fn new(
instructions: Vec<u32>,
pc_start: u32,
pc_base: u32,
memory_image: BTreeMap<u32, u32>,
) -> Self {
Self {
instructions,
pc_start,
pc_base,
memory_image,
}
}
pub fn decode(input: &[u8]) -> Self {
let mut image: BTreeMap<u32, u32> = BTreeMap::new();
let elf = ElfBytes::<LittleEndian>::minimal_parse(input).expect("failed to parse elf");
if elf.ehdr.class != Class::ELF32 {
panic!("must be a 32-bit elf");
} else if elf.ehdr.e_machine != EM_RISCV {
panic!("must be a riscv machine");
} else if elf.ehdr.e_type != ET_EXEC {
panic!("must be executable");
}
let entry: u32 = elf
.ehdr
.e_entry
.try_into()
.expect("e_entry was larger than 32 bits");
if entry == MAXIMUM_MEMORY_SIZE || entry % WORD_SIZE as u32 != 0 {
panic!("invalid entrypoint");
}
let segments = elf.segments().expect("failed to get segments");
if segments.len() > 256 {
panic!("too many program headers");
}
let mut instructions: Vec<u32> = Vec::new();
let mut base_address = u32::MAX;
for segment in segments.iter().filter(|x| x.p_type == PT_LOAD) {
let file_size: u32 = segment
.p_filesz
.try_into()
.expect("filesize was larger than 32 bits");
if file_size == MAXIMUM_MEMORY_SIZE {
panic!("invalid segment file_size");
}
let mem_size: u32 = segment
.p_memsz
.try_into()
.expect("mem_size was larger than 32 bits");
if mem_size == MAXIMUM_MEMORY_SIZE {
panic!("Invalid segment mem_size");
}
let vaddr: u32 = segment
.p_vaddr
.try_into()
.expect("vaddr was larger than 32 bits");
if vaddr % WORD_SIZE as u32 != 0 {
panic!("vaddr {vaddr:08x} is unaligned");
}
if (segment.p_flags & PF_X) != 0 && base_address > vaddr {
base_address = vaddr;
}
let offset: u32 = segment
.p_offset
.try_into()
.expect("offset was larger than 32 bits");
for i in (0..mem_size).step_by(WORD_SIZE) {
let addr = vaddr.checked_add(i).expect("invalid segment vaddr");
if addr == MAXIMUM_MEMORY_SIZE {
panic!("address [0x{addr:08x}] exceeds maximum address for guest programs [0x{MAXIMUM_MEMORY_SIZE:08x}]");
}
if i >= file_size {
image.insert(addr, 0);
continue;
}
let mut word = 0;
let len = min(file_size - i, WORD_SIZE as u32);
for j in 0..len {
let offset = (offset + i + j) as usize;
let byte = input.get(offset).expect("invalid segment offset");
word |= (*byte as u32) << (j * 8);
}
image.insert(addr, word);
if (segment.p_flags & PF_X) != 0 {
instructions.push(word);
}
}
}
Elf::new(instructions, entry, base_address, image)
}
}