start tests

This commit is contained in:
nora 2025-03-09 16:35:54 +01:00
parent ca565ddeb3
commit 0af012d43a
10 changed files with 412 additions and 118 deletions

View file

@ -1,7 +1,7 @@
use eyre::{Result, bail};
pub struct Elf {
pub content: Vec<u8>,
pub struct Elf<'a> {
pub content: &'a [u8],
}
#[derive(Debug)]
@ -30,7 +30,7 @@ pub struct Phdr {
pub p_align: u32,
}
impl Elf {
impl<'a> Elf<'a> {
pub fn header(&self) -> Result<Header> {
let (ident, rest) = self.content.split_bytes(16)?;
if ident[..4] != *b"\x7fELF" {

View file

@ -10,9 +10,9 @@ pub struct Memory {
}
impl Memory {
fn check_align(&self, addr: u32, align: u32) -> Result<(), Error> {
fn check_align(&self, addr: u32, align: u32) -> Result<(), Status> {
if addr % 2 != 0 {
Err(Error::UnaligneMemoryAccess {
Err(Status::UnaligneMemoryAccess {
addr,
required_align: align,
})
@ -20,44 +20,44 @@ impl Memory {
Ok(())
}
}
pub fn slice(&self, addr: u32, len: u32) -> Result<&[u8], Error> {
pub fn slice(&self, addr: u32, len: u32) -> Result<&[u8], Status> {
Ok(self
.mem
.get((addr as usize)..)
.ok_or(Error::InvalidMemoryAccess(addr))?
.ok_or(Status::InvalidMemoryAccess(addr))?
.get(..(len as usize))
.ok_or(Error::InvalidMemoryAccess(addr))?)
.ok_or(Status::InvalidMemoryAccess(addr))?)
}
pub fn slice_mut(&mut self, addr: u32, len: u32) -> Result<&mut [u8], Error> {
pub fn slice_mut(&mut self, addr: u32, len: u32) -> Result<&mut [u8], Status> {
Ok(self
.mem
.get_mut((addr as usize)..)
.ok_or(Error::InvalidMemoryAccess(addr))?
.ok_or(Status::InvalidMemoryAccess(addr))?
.get_mut(..(len as usize))
.ok_or(Error::InvalidMemoryAccess(addr))?)
.ok_or(Status::InvalidMemoryAccess(addr))?)
}
pub fn load_u8(&self, addr: u32) -> Result<u8, Error> {
pub fn load_u8(&self, addr: u32) -> Result<u8, Status> {
Ok(u8::from_le_bytes(self.slice(addr, 1)?.try_into().unwrap()))
}
pub fn load_u16(&self, addr: u32) -> Result<u16, Error> {
pub fn load_u16(&self, addr: u32) -> Result<u16, Status> {
Ok(u16::from_le_bytes(self.slice(addr, 2)?.try_into().unwrap()))
}
pub fn load_u32(&self, addr: u32) -> Result<u32, Error> {
pub fn load_u32(&self, addr: u32) -> Result<u32, Status> {
Ok(u32::from_le_bytes(self.slice(addr, 4)?.try_into().unwrap()))
}
pub fn store_u8(&mut self, addr: u32, value: u8) -> Result<(), Error> {
pub fn store_u8(&mut self, addr: u32, value: u8) -> Result<(), Status> {
Ok(self
.slice_mut(addr, 1)?
.copy_from_slice(&value.to_le_bytes()))
}
pub fn store_u16(&mut self, addr: u32, value: u16) -> Result<(), Error> {
pub fn store_u16(&mut self, addr: u32, value: u16) -> Result<(), Status> {
self.check_align(addr, 2)?;
Ok(self
.slice_mut(addr, 2)?
.copy_from_slice(&value.to_le_bytes()))
}
pub fn store_u32(&mut self, addr: u32, value: u32) -> Result<(), Error> {
pub fn store_u32(&mut self, addr: u32, value: u32) -> Result<(), Status> {
self.check_align(addr, 4)?;
Ok(self
.slice_mut(addr, 4)?
@ -66,7 +66,7 @@ impl Memory {
}
#[derive(Debug)]
pub enum Error {
pub enum Status {
Trap(&'static str),
IllegalInstruction(InstCode, &'static str),
InvalidMemoryAccess(u32),
@ -87,8 +87,7 @@ pub struct Emulator {
pub reservation_set: Option<u32>,
pub debug: bool,
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Error>>,
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Status>>,
}
impl Index<Reg> for Emulator {
@ -141,14 +140,14 @@ impl Display for Reg {
}
impl Emulator {
pub fn start_linux(&mut self) -> Error {
pub fn start_linux(&mut self) -> Status {
// set top of stack. just some yolo address. with no values there. who needs abi?
self[Reg::SP] = 4096;
self.execute()
}
fn execute(&mut self) -> Error {
fn execute(&mut self) -> Status {
loop {
if let Err(err) = self.step() {
return err;
@ -156,15 +155,15 @@ impl Emulator {
}
}
fn set_pc(&mut self, pc: u32) -> Result<(), Error> {
fn set_pc(&mut self, pc: u32) -> Result<(), Status> {
if pc % 4 != 0 {
return Err(Error::UnalignedPc(pc));
return Err(Status::UnalignedPc(pc));
}
self.pc = pc;
Ok(())
}
fn step(&mut self) -> Result<(), Error> {
fn step(&mut self) -> Result<(), Status> {
let code = self.mem.load_u32(self.pc)?;
let inst = Inst::decode(code)?;
@ -314,7 +313,7 @@ impl Emulator {
Inst::Ecall => {
(self.ecall_handler)(&mut self.mem, &mut self.xreg)?;
}
Inst::Ebreak => return Err(Error::Ebreak),
Inst::Ebreak => return Err(Status::Ebreak),
Inst::Mul { dest, src1, src2 } => {
self[dest] = ((self[src1] as i32).wrapping_mul(self[src2] as i32)) as u32;
}

View file

@ -1,4 +1,4 @@
use crate::emu::{Error, Reg};
use crate::emu::{Status, Reg};
use std::fmt::{Debug, Display};
use std::ops::RangeInclusive;
@ -413,7 +413,7 @@ impl Debug for InstCode {
}
impl Inst {
pub fn decode(code: u32) -> Result<Inst, Error> {
pub fn decode(code: u32) -> Result<Inst, Status> {
let code = InstCode(code);
let inst = match code.opcode() {
// LUI
@ -438,7 +438,7 @@ impl Inst {
base: code.rs1(),
dest: code.rd(),
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
},
// BRANCH
0b1100011 => match code.funct3() {
@ -472,7 +472,7 @@ impl Inst {
src1: code.rs1(),
src2: code.rs2(),
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
},
// LOAD
0b0000011 => match code.funct3() {
@ -501,7 +501,7 @@ impl Inst {
dest: code.rd(),
base: code.rs1(),
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
},
// STORE
0b0100011 => match code.funct3() {
@ -520,7 +520,7 @@ impl Inst {
src: code.rs2(),
base: code.rs1(),
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
},
// OP-IMM
0b0010011 => match code.funct3() {
@ -570,9 +570,9 @@ impl Inst {
dest: code.rd(),
src1: code.rs1(),
},
_ => return Err(Error::IllegalInstruction(code, "funct7")),
_ => return Err(Status::IllegalInstruction(code, "funct7")),
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
},
// OP
0b0110011 => {
@ -597,7 +597,7 @@ impl Inst {
(0b101, 0b0000001) => Inst::Divu { dest, src1, src2 },
(0b110, 0b0000001) => Inst::Rem { dest, src1, src2 },
(0b111, 0b0000001) => Inst::Remu { dest, src1, src2 },
_ => return Err(Error::IllegalInstruction(code, "funct3/funct7")),
_ => return Err(Status::IllegalInstruction(code, "funct3/funct7")),
}
}
// MISC-MEM
@ -626,34 +626,34 @@ impl Inst {
src: code.rs1(),
},
},
_ => return Err(Error::IllegalInstruction(code, "funct3")),
_ => return Err(Status::IllegalInstruction(code, "funct3")),
}
}
// SYSTEM
0b1110011 => {
if code.0 == 0b11000000000000000001000001110011 {
return Err(Error::Trap("unimp instruction"));
return Err(Status::Trap("unimp instruction"));
}
if code.rd().0 != 0 {
return Err(Error::IllegalInstruction(code, "rd"));
return Err(Status::IllegalInstruction(code, "rd"));
}
if code.funct3() != 0 {
return Err(Error::IllegalInstruction(code, "funct3"));
return Err(Status::IllegalInstruction(code, "funct3"));
}
if code.rs1().0 != 0 {
return Err(Error::IllegalInstruction(code, "rs1"));
return Err(Status::IllegalInstruction(code, "rs1"));
}
match code.imm_i() {
0b000000000000 => Inst::Ecall,
0b000000000001 => Inst::Ebreak,
_ => return Err(Error::IllegalInstruction(code, "imm")),
_ => return Err(Status::IllegalInstruction(code, "imm")),
}
}
// AMO
00101111 => {
// width must be W
if code.funct3() != 0b010 {
return Err(Error::IllegalInstruction(code, "funct3"));
return Err(Status::IllegalInstruction(code, "funct3"));
}
let kind = code.extract(27..=31);
@ -666,7 +666,7 @@ impl Inst {
// LR
0b00010 => {
if code.rs2().0 != 0 {
return Err(Error::IllegalInstruction(code, "rs2"));
return Err(Status::IllegalInstruction(code, "rs2"));
}
Inst::LrW {
@ -693,7 +693,7 @@ impl Inst {
0b10100 => AmoOp::Max,
0b11000 => AmoOp::Minu,
0b11100 => AmoOp::Maxu,
_ => return Err(Error::IllegalInstruction(code, "funct7")),
_ => return Err(Status::IllegalInstruction(code, "funct7")),
};
Inst::AmoW {
order,
@ -705,7 +705,7 @@ impl Inst {
}
}
}
_ => return Err(Error::IllegalInstruction(code, "opcode")),
_ => return Err(Status::IllegalInstruction(code, "opcode")),
};
Ok(inst)
}

View file

@ -1,3 +1,76 @@
use eyre::{OptionExt, bail};
pub mod elf;
pub mod emu;
pub mod inst;
// 2 MiB
const MEMORY_SIZE: usize = 2 << 21;
pub fn execute_linux_elf(
elf: &[u8],
debug: bool,
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
) -> eyre::Result<emu::Status> {
let elf = elf::Elf { content: elf };
let header = elf.header()?;
let segments = elf.segments()?;
let mut mem = emu::Memory {
mem: vec![0; MEMORY_SIZE],
};
for phdr in segments {
match phdr.p_type {
// PT_NULL
0 => {}
// PT_LOAD
1 => {
if phdr.p_filesz > 0 {
let contents = &elf
.content
.get((phdr.p_offset as usize)..)
.ok_or_eyre("invalid offset")?
.get(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?;
mem.mem
.get_mut((phdr.p_vaddr as usize)..)
.ok_or_eyre("invalid offset")?
.get_mut(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?
.copy_from_slice(contents);
}
}
// PT_DYNAMIC
2 => {}
// PT_PHDR
6 => {}
// PT_GNU_EH_FRAME
1685382480 => {}
// PT_GNU_STACK
1685382481 => {}
// PT_GNU_RELRO
1685382482 => {}
// PT_RISCV_ATTRIBUTES
0x70000003 => {}
_ => bail!("unknown program header type: {}", phdr.p_type),
}
}
let start = header.e_entry;
let mut emu = emu::Emulator {
mem,
xreg: [0; 32],
xreg0_value: 0,
pc: start,
reservation_set: None,
debug,
ecall_handler,
};
Ok(emu.start_linux())
}

View file

@ -1,88 +1,27 @@
use eyre::{OptionExt, bail, eyre};
use rustv32i::{
elf,
emu::{self, Memory, Reg},
};
// 2 MiB
const MEMORY_SIZE: usize = 2 << 21;
use eyre::eyre;
use rustv32i::emu::{self, Memory, Reg};
fn main() -> eyre::Result<()> {
let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap();
let elf = elf::Elf { content };
let header = elf.header()?;
let status = rustv32i::execute_linux_elf(
&content,
std::env::args().any(|arg| arg == "--debug"),
Box::new(ecall_handler),
)?;
let segments = elf.segments()?;
let mut mem = emu::Memory {
mem: vec![0; MEMORY_SIZE],
};
for phdr in segments {
match phdr.p_type {
// PT_NULL
0 => {}
// PT_LOAD
1 => {
if phdr.p_filesz > 0 {
let contents = &elf
.content
.get((phdr.p_offset as usize)..)
.ok_or_eyre("invalid offset")?
.get(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?;
mem.mem
.get_mut((phdr.p_vaddr as usize)..)
.ok_or_eyre("invalid offset")?
.get_mut(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?
.copy_from_slice(contents);
}
}
// PT_DYNAMIC
2 => {}
// PT_PHDR
6 => {}
// PT_GNU_EH_FRAME
1685382480 => {}
// PT_GNU_STACK
1685382481 => {}
// PT_GNU_RELRO
1685382482 => {}
// PT_RISCV_ATTRIBUTES
0x70000003 => {}
_ => bail!("unknown program header type: {}", phdr.p_type),
}
}
let start = header.e_entry;
let mut emu = emu::Emulator {
mem,
xreg: [0; 32],
xreg0_value: 0,
pc: start,
reservation_set: None,
debug: std::env::args().any(|arg| arg == "--debug"),
ecall_handler: Box::new(ecall_handler),
};
match emu.start_linux() {
emu::Error::Exit { code } => {
match status {
emu::Status::Exit { code } => {
eprintln!("exited with code {code}");
}
emu::Error::Trap(cause) => eprintln!("program trapped: {cause}"),
emu::Status::Trap(cause) => eprintln!("program trapped: {cause}"),
e => return Err(eyre!("error: {e:?}")),
}
Ok(())
}
fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Error> {
fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Status> {
let nr = xreg[Reg::A7.0 as usize];
match nr {
@ -122,7 +61,7 @@ fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Erro
}
// exit
93 => {
return Err(emu::Error::Exit {
return Err(emu::Status::Exit {
code: xreg[Reg::A0.0 as usize] as i32,
});
}