mirror of
https://github.com/Noratrieb/rustv32i.git
synced 2026-01-14 13:25:01 +01:00
start tests
This commit is contained in:
parent
ca565ddeb3
commit
0af012d43a
10 changed files with 412 additions and 118 deletions
|
|
@ -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" {
|
||||
|
|
|
|||
45
src/emu.rs
45
src/emu.rs
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
38
src/inst.rs
38
src/inst.rs
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
73
src/lib.rs
73
src/lib.rs
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
85
src/main.rs
85
src/main.rs
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue