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

165
Cargo.lock generated
View file

@ -2,6 +2,28 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.12" version = "0.6.12"
@ -12,6 +34,24 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]] [[package]]
name = "indenter" name = "indenter"
version = "0.3.3" version = "0.3.3"
@ -24,16 +64,141 @@ version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.3" version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "rustix"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]] [[package]]
name = "rustv32i" name = "rustv32i"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"libc", "libc",
"tempfile",
]
[[package]]
name = "tempfile"
version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
] ]

View file

@ -9,3 +9,6 @@ libc = "0.2.170"
[profile.release] [profile.release]
debug = 1 debug = 1
[dev-dependencies]
tempfile = "3.18.0"

View file

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

View file

@ -10,9 +10,9 @@ pub struct Memory {
} }
impl 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 { if addr % 2 != 0 {
Err(Error::UnaligneMemoryAccess { Err(Status::UnaligneMemoryAccess {
addr, addr,
required_align: align, required_align: align,
}) })
@ -20,44 +20,44 @@ impl Memory {
Ok(()) 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 Ok(self
.mem .mem
.get((addr as usize)..) .get((addr as usize)..)
.ok_or(Error::InvalidMemoryAccess(addr))? .ok_or(Status::InvalidMemoryAccess(addr))?
.get(..(len as usize)) .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 Ok(self
.mem .mem
.get_mut((addr as usize)..) .get_mut((addr as usize)..)
.ok_or(Error::InvalidMemoryAccess(addr))? .ok_or(Status::InvalidMemoryAccess(addr))?
.get_mut(..(len as usize)) .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())) 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())) 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())) 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 Ok(self
.slice_mut(addr, 1)? .slice_mut(addr, 1)?
.copy_from_slice(&value.to_le_bytes())) .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)?; self.check_align(addr, 2)?;
Ok(self Ok(self
.slice_mut(addr, 2)? .slice_mut(addr, 2)?
.copy_from_slice(&value.to_le_bytes())) .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)?; self.check_align(addr, 4)?;
Ok(self Ok(self
.slice_mut(addr, 4)? .slice_mut(addr, 4)?
@ -66,7 +66,7 @@ impl Memory {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Status {
Trap(&'static str), Trap(&'static str),
IllegalInstruction(InstCode, &'static str), IllegalInstruction(InstCode, &'static str),
InvalidMemoryAccess(u32), InvalidMemoryAccess(u32),
@ -87,8 +87,7 @@ pub struct Emulator {
pub reservation_set: Option<u32>, pub reservation_set: Option<u32>,
pub debug: bool, pub debug: bool,
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Status>>,
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Error>>,
} }
impl Index<Reg> for Emulator { impl Index<Reg> for Emulator {
@ -141,14 +140,14 @@ impl Display for Reg {
} }
impl Emulator { 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? // set top of stack. just some yolo address. with no values there. who needs abi?
self[Reg::SP] = 4096; self[Reg::SP] = 4096;
self.execute() self.execute()
} }
fn execute(&mut self) -> Error { fn execute(&mut self) -> Status {
loop { loop {
if let Err(err) = self.step() { if let Err(err) = self.step() {
return err; 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 { if pc % 4 != 0 {
return Err(Error::UnalignedPc(pc)); return Err(Status::UnalignedPc(pc));
} }
self.pc = pc; self.pc = pc;
Ok(()) Ok(())
} }
fn step(&mut self) -> Result<(), Error> { fn step(&mut self) -> Result<(), Status> {
let code = self.mem.load_u32(self.pc)?; let code = self.mem.load_u32(self.pc)?;
let inst = Inst::decode(code)?; let inst = Inst::decode(code)?;
@ -314,7 +313,7 @@ impl Emulator {
Inst::Ecall => { Inst::Ecall => {
(self.ecall_handler)(&mut self.mem, &mut self.xreg)?; (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 } => { Inst::Mul { dest, src1, src2 } => {
self[dest] = ((self[src1] as i32).wrapping_mul(self[src2] as i32)) as u32; 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::fmt::{Debug, Display};
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
@ -413,7 +413,7 @@ impl Debug for InstCode {
} }
impl Inst { impl Inst {
pub fn decode(code: u32) -> Result<Inst, Error> { pub fn decode(code: u32) -> Result<Inst, Status> {
let code = InstCode(code); let code = InstCode(code);
let inst = match code.opcode() { let inst = match code.opcode() {
// LUI // LUI
@ -438,7 +438,7 @@ impl Inst {
base: code.rs1(), base: code.rs1(),
dest: code.rd(), dest: code.rd(),
}, },
_ => return Err(Error::IllegalInstruction(code, "funct3")), _ => return Err(Status::IllegalInstruction(code, "funct3")),
}, },
// BRANCH // BRANCH
0b1100011 => match code.funct3() { 0b1100011 => match code.funct3() {
@ -472,7 +472,7 @@ impl Inst {
src1: code.rs1(), src1: code.rs1(),
src2: code.rs2(), src2: code.rs2(),
}, },
_ => return Err(Error::IllegalInstruction(code, "funct3")), _ => return Err(Status::IllegalInstruction(code, "funct3")),
}, },
// LOAD // LOAD
0b0000011 => match code.funct3() { 0b0000011 => match code.funct3() {
@ -501,7 +501,7 @@ impl Inst {
dest: code.rd(), dest: code.rd(),
base: code.rs1(), base: code.rs1(),
}, },
_ => return Err(Error::IllegalInstruction(code, "funct3")), _ => return Err(Status::IllegalInstruction(code, "funct3")),
}, },
// STORE // STORE
0b0100011 => match code.funct3() { 0b0100011 => match code.funct3() {
@ -520,7 +520,7 @@ impl Inst {
src: code.rs2(), src: code.rs2(),
base: code.rs1(), base: code.rs1(),
}, },
_ => return Err(Error::IllegalInstruction(code, "funct3")), _ => return Err(Status::IllegalInstruction(code, "funct3")),
}, },
// OP-IMM // OP-IMM
0b0010011 => match code.funct3() { 0b0010011 => match code.funct3() {
@ -570,9 +570,9 @@ impl Inst {
dest: code.rd(), dest: code.rd(),
src1: code.rs1(), 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 // OP
0b0110011 => { 0b0110011 => {
@ -597,7 +597,7 @@ impl Inst {
(0b101, 0b0000001) => Inst::Divu { dest, src1, src2 }, (0b101, 0b0000001) => Inst::Divu { dest, src1, src2 },
(0b110, 0b0000001) => Inst::Rem { dest, src1, src2 }, (0b110, 0b0000001) => Inst::Rem { dest, src1, src2 },
(0b111, 0b0000001) => Inst::Remu { 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 // MISC-MEM
@ -626,34 +626,34 @@ impl Inst {
src: code.rs1(), src: code.rs1(),
}, },
}, },
_ => return Err(Error::IllegalInstruction(code, "funct3")), _ => return Err(Status::IllegalInstruction(code, "funct3")),
} }
} }
// SYSTEM // SYSTEM
0b1110011 => { 0b1110011 => {
if code.0 == 0b11000000000000000001000001110011 { if code.0 == 0b11000000000000000001000001110011 {
return Err(Error::Trap("unimp instruction")); return Err(Status::Trap("unimp instruction"));
} }
if code.rd().0 != 0 { if code.rd().0 != 0 {
return Err(Error::IllegalInstruction(code, "rd")); return Err(Status::IllegalInstruction(code, "rd"));
} }
if code.funct3() != 0 { if code.funct3() != 0 {
return Err(Error::IllegalInstruction(code, "funct3")); return Err(Status::IllegalInstruction(code, "funct3"));
} }
if code.rs1().0 != 0 { if code.rs1().0 != 0 {
return Err(Error::IllegalInstruction(code, "rs1")); return Err(Status::IllegalInstruction(code, "rs1"));
} }
match code.imm_i() { match code.imm_i() {
0b000000000000 => Inst::Ecall, 0b000000000000 => Inst::Ecall,
0b000000000001 => Inst::Ebreak, 0b000000000001 => Inst::Ebreak,
_ => return Err(Error::IllegalInstruction(code, "imm")), _ => return Err(Status::IllegalInstruction(code, "imm")),
} }
} }
// AMO // AMO
00101111 => { 00101111 => {
// width must be W // width must be W
if code.funct3() != 0b010 { if code.funct3() != 0b010 {
return Err(Error::IllegalInstruction(code, "funct3")); return Err(Status::IllegalInstruction(code, "funct3"));
} }
let kind = code.extract(27..=31); let kind = code.extract(27..=31);
@ -666,7 +666,7 @@ impl Inst {
// LR // LR
0b00010 => { 0b00010 => {
if code.rs2().0 != 0 { if code.rs2().0 != 0 {
return Err(Error::IllegalInstruction(code, "rs2")); return Err(Status::IllegalInstruction(code, "rs2"));
} }
Inst::LrW { Inst::LrW {
@ -693,7 +693,7 @@ impl Inst {
0b10100 => AmoOp::Max, 0b10100 => AmoOp::Max,
0b11000 => AmoOp::Minu, 0b11000 => AmoOp::Minu,
0b11100 => AmoOp::Maxu, 0b11100 => AmoOp::Maxu,
_ => return Err(Error::IllegalInstruction(code, "funct7")), _ => return Err(Status::IllegalInstruction(code, "funct7")),
}; };
Inst::AmoW { Inst::AmoW {
order, order,
@ -705,7 +705,7 @@ impl Inst {
} }
} }
} }
_ => return Err(Error::IllegalInstruction(code, "opcode")), _ => return Err(Status::IllegalInstruction(code, "opcode")),
}; };
Ok(inst) Ok(inst)
} }

View file

@ -1,3 +1,76 @@
use eyre::{OptionExt, bail};
pub mod elf; pub mod elf;
pub mod emu; pub mod emu;
pub mod inst; 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 eyre::eyre;
use rustv32i::{ use rustv32i::emu::{self, Memory, Reg};
elf,
emu::{self, Memory, Reg},
};
// 2 MiB
const MEMORY_SIZE: usize = 2 << 21;
fn main() -> eyre::Result<()> { fn main() -> eyre::Result<()> {
let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap(); let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap();
let elf = elf::Elf { content }; let status = rustv32i::execute_linux_elf(
let header = elf.header()?; &content,
std::env::args().any(|arg| arg == "--debug"),
Box::new(ecall_handler),
)?;
let segments = elf.segments()?; match status {
emu::Status::Exit { code } => {
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 } => {
eprintln!("exited with code {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:?}")), e => return Err(eyre!("error: {e:?}")),
} }
Ok(()) 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]; let nr = xreg[Reg::A7.0 as usize];
match nr { match nr {
@ -122,7 +61,7 @@ fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Erro
} }
// exit // exit
93 => { 93 => {
return Err(emu::Error::Exit { return Err(emu::Status::Exit {
code: xreg[Reg::A0.0 as usize] as i32, code: xreg[Reg::A0.0 as usize] as i32,
}); });
} }

86
tests/check.rs Normal file
View file

@ -0,0 +1,86 @@
use std::{
io::Write,
path::{Path, PathBuf},
};
use eyre::{Context, bail};
use rustv32i::emu::{Reg, Status};
#[test]
fn check() -> eyre::Result<()> {
let tmpdir = tempfile::tempdir().wrap_err("failed to create tempdir")?;
let dir =
Path::new(&std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set"))
.join("tests")
.join("check");
let files = std::fs::read_dir(&dir).wrap_err(format!("reading {}", dir.display()))?;
for file in files {
let file = file.wrap_err(format!("reading file in {}", dir.display()))?;
let name = file.file_name();
let name = name.to_str().unwrap();
if !name.ends_with(".s") {
continue;
}
write!(std::io::stdout(), "test {name} ...")?;
std::io::stdout().flush()?;
let output = build(&tmpdir.path(), &file.path()).wrap_err(format!("building {name}"))?;
let content =
std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?;
let status = rustv32i::execute_linux_elf(
&content,
matches!(std::env::var("EMULATOR_DEBUG").as_deref(), Ok(v) if v != "0"),
Box::new(|_, xreg| {
if xreg[Reg::A7.0 as usize] == u32::MAX {
if xreg[Reg::A0.0 as usize] == 1 {
Err(rustv32i::emu::Status::Exit { code: 0 })
} else {
Err(rustv32i::emu::Status::Trap("wrong exit code"))
}
} else {
Err(rustv32i::emu::Status::Trap("wrong syscall"))
}
}),
)
.wrap_err(format!("{name} failed"))?;
if let Status::Exit { code: 0 } = status {
writeln!(std::io::stdout(), "")?;
} else {
bail!("{name} returned an error: {status:?}");
}
}
Ok(())
}
fn build(tmpdir: &Path, src: &Path) -> eyre::Result<PathBuf> {
let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension(""));
let mut cmd = std::process::Command::new("clang");
cmd.args([
"-target",
"riscv32-unknown-none-elf",
"-nostdlib",
"-march=rv32ima",
]);
cmd.arg(src);
cmd.arg("-o");
cmd.arg(&out_path);
let output = cmd.output().wrap_err("failed to spawn clang")?;
if !output.status.success() {
bail!(
"failed to compile {}:\n{}",
src.display(),
String::from_utf8_lossy(&output.stderr)
);
}
Ok(out_path)
}

23
tests/check/arith.s Normal file
View file

@ -0,0 +1,23 @@
.section .text
.globl _start
_start:
li t0, 10
li t1, 20
add t2, t0, t1
li t3, 30
bne t2, t3, fail
li t0, 10
li t1, -2
add t2, t0, t1
li t3, 8
bne t2, t3, fail
li a7, -1
li a0, 1
ecall
fail:
li a7, -1
li a0, 0
ecall

6
tests/check/smoke.s Normal file
View file

@ -0,0 +1,6 @@
.section .text
.globl _start
_start:
li a7, -1
li a0, 1
ecall