mirror of
https://github.com/Noratrieb/rustv32i.git
synced 2026-01-14 13:25:01 +01:00
This commit is contained in:
parent
0144740228
commit
e9a689aa1a
6 changed files with 449 additions and 193 deletions
143
rvdc/src/lib.rs
143
rvdc/src/lib.rs
|
|
@ -5,6 +5,27 @@
|
|||
use core::fmt::{self, Debug, Display};
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
/// The register size of the ISA, RV32 or RV64.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Xlen {
|
||||
/// 32 bit
|
||||
Rv32,
|
||||
/// 64 bit
|
||||
Rv64,
|
||||
}
|
||||
|
||||
impl Xlen {
|
||||
/// Whether this is [`Xlen::Rv32`].
|
||||
pub fn is_32(self) -> bool {
|
||||
matches!(self, Self::Rv32)
|
||||
}
|
||||
|
||||
/// Whether this is [`Xlen::Rv64`].
|
||||
pub fn is_64(self) -> bool {
|
||||
matches!(self, Self::Rv64)
|
||||
}
|
||||
}
|
||||
|
||||
/// A decoded RISC-V integer register.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Reg(pub u8);
|
||||
|
|
@ -109,7 +130,7 @@ impl Display for Reg {
|
|||
///
|
||||
/// This type is XLEN-agnostic, use the XLEN-specific accessors to get the correct value.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Imm(u32);
|
||||
pub struct Imm(u64);
|
||||
|
||||
impl Imm {
|
||||
/// The immediate `0`.
|
||||
|
|
@ -118,23 +139,33 @@ impl Imm {
|
|||
|
||||
/// Create a new immediate from the (if necessary) sign-extended value.
|
||||
pub const fn new_i32(value: i32) -> Self {
|
||||
Self(value as u32)
|
||||
Self(value as i64 as u64)
|
||||
}
|
||||
|
||||
/// Create a new immediate from the (if necessary) zero-extended value.
|
||||
pub const fn new_u32(value: u32) -> Self {
|
||||
Self(value)
|
||||
Self(value as u64)
|
||||
}
|
||||
|
||||
/// Get the `u32` (RV32) value of the immediate.
|
||||
pub const fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
self.0 as u32
|
||||
}
|
||||
|
||||
/// Get the `i32` (RV32) value of the immediate.
|
||||
pub const fn as_i32(self) -> i32 {
|
||||
self.0 as i32
|
||||
}
|
||||
|
||||
/// Get the `u64` (RV64) value of the immediate.
|
||||
pub const fn as_u64(self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
|
||||
/// Get the `i64` (RV64) value of the immediate.
|
||||
pub const fn as_i64(self) -> i64 {
|
||||
self.0 as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Imm {
|
||||
|
|
@ -216,6 +247,8 @@ pub enum Inst {
|
|||
|
||||
/// Add Immediate
|
||||
Addi { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Add Immediate 32-bit (**RV64 only**)
|
||||
AddiW { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Set Less Than Immediate (signed)
|
||||
Slti { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Set Less Than Immediate Unsigned
|
||||
|
|
@ -228,10 +261,16 @@ pub enum Inst {
|
|||
Andi { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Left Logical Immediate
|
||||
Slli { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Left Logical Immediate 32-bit (**RV64 only**)
|
||||
SlliW { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Right Logical Immediate (unsigned)
|
||||
Srli { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Right Logical Immediate (unsigned) 32-bit (**RV64 only**)
|
||||
SrliW { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Right Arithmetic Immediate (signed)
|
||||
Srai { imm: Imm, dest: Reg, src1: Reg },
|
||||
/// Shift Right Arithmetic Immediate (signed) 32-bit (**RV64 only**)
|
||||
SraiW { imm: Imm, dest: Reg, src1: Reg },
|
||||
|
||||
/// Add
|
||||
Add { dest: Reg, src1: Reg, src2: Reg },
|
||||
|
|
@ -502,6 +541,13 @@ impl Display for Inst {
|
|||
write!(f, "addi {dest}, {src1}, {}", imm.as_i32())
|
||||
}
|
||||
}
|
||||
Inst::AddiW { imm, dest, src1 } => {
|
||||
if imm.as_u32() == 0 {
|
||||
write!(f, "sext.w {dest}, {src1}")
|
||||
} else {
|
||||
write!(f, "addi.w {dest}, {src1}, {}", imm.as_i32())
|
||||
}
|
||||
}
|
||||
Inst::Slti {
|
||||
imm,
|
||||
dest,
|
||||
|
|
@ -532,16 +578,31 @@ impl Display for Inst {
|
|||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "slli {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::SlliW {
|
||||
imm,
|
||||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "slliw {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::Srli {
|
||||
imm,
|
||||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "srli {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::SrliW {
|
||||
imm,
|
||||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "srliw {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::Srai {
|
||||
imm,
|
||||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "srai {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::SraiW {
|
||||
imm,
|
||||
dest,
|
||||
src1: rs1,
|
||||
} => write!(f, "sraiw {dest}, {rs1}, {}", imm.as_i32()),
|
||||
Inst::Add { dest, src1, src2 } => {
|
||||
write!(f, "add {dest}, {src1}, {src2}")
|
||||
}
|
||||
|
|
@ -847,12 +908,15 @@ impl Inst {
|
|||
/// If the caller wants to avoid reading more bytes than necessary, [`Self::first_byte_is_compressed`]
|
||||
/// can be used to check, read the required bytes, and then call [`Self::decode_compressed`] or
|
||||
/// [`Self::decode_normal`] directly.
|
||||
pub fn decode(code: u32) -> Result<(Inst, IsCompressed), DecodeError> {
|
||||
pub fn decode(code: u32, xlen: Xlen) -> Result<(Inst, IsCompressed), DecodeError> {
|
||||
let is_compressed = (code & 0b11) != 0b11;
|
||||
if is_compressed {
|
||||
Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes))
|
||||
Ok((
|
||||
Self::decode_compressed(code as u16, xlen)?,
|
||||
IsCompressed::Yes,
|
||||
))
|
||||
} else {
|
||||
Ok((Self::decode_normal(code)?, IsCompressed::No))
|
||||
Ok((Self::decode_normal(code, xlen)?, IsCompressed::No))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -867,7 +931,7 @@ impl Inst {
|
|||
/// let inst = rvdc::Inst::decode_compressed(x).unwrap();
|
||||
/// assert_eq!(inst, expected);
|
||||
/// ```
|
||||
pub fn decode_compressed(code: u16) -> Result<Inst, DecodeError> {
|
||||
pub fn decode_compressed(code: u16, xlen: Xlen) -> Result<Inst, DecodeError> {
|
||||
let code = InstCodeC(code);
|
||||
if code.0 == 0 {
|
||||
return Err(decode_error(code, "null instruction"));
|
||||
|
|
@ -1146,7 +1210,7 @@ impl Inst {
|
|||
}
|
||||
|
||||
/// Decode a normal (not compressed) instruction.
|
||||
pub fn decode_normal(code: u32) -> Result<Inst, DecodeError> {
|
||||
pub fn decode_normal(code: u32, xlen: Xlen) -> Result<Inst, DecodeError> {
|
||||
let code = InstCode(code);
|
||||
let inst = match code.opcode() {
|
||||
// LUI
|
||||
|
|
@ -1312,6 +1376,47 @@ impl Inst {
|
|||
},
|
||||
_ => return Err(decode_error(code, "OP-IMM funct3")),
|
||||
},
|
||||
// OP-IMM-32
|
||||
0b0011011 => {
|
||||
if !xlen.is_64() {
|
||||
return Err(decode_error(code, "OP-IMM-32 only on RV64"));
|
||||
}
|
||||
|
||||
match code.funct3() {
|
||||
0b000 => Inst::AddiW {
|
||||
imm: code.imm_i(),
|
||||
dest: code.rd(),
|
||||
src1: code.rs1(),
|
||||
},
|
||||
// SLLIW
|
||||
0b001 => {
|
||||
if code.funct7() != 0 {
|
||||
return Err(decode_error(code, "SLLIW funct7"));
|
||||
}
|
||||
|
||||
Inst::SlliW {
|
||||
imm: Imm::new_u32(code.rs2_imm()),
|
||||
dest: code.rd(),
|
||||
src1: code.rs1(),
|
||||
}
|
||||
}
|
||||
|
||||
0b101 => match code.funct7() {
|
||||
0b0000000 => Inst::SrliW {
|
||||
imm: Imm::new_u32(code.rs2_imm()),
|
||||
dest: code.rd(),
|
||||
src1: code.rs1(),
|
||||
},
|
||||
0b0100000 => Inst::SraiW {
|
||||
imm: Imm::new_u32(code.rs2_imm()),
|
||||
dest: code.rd(),
|
||||
src1: code.rs1(),
|
||||
},
|
||||
_ => return Err(decode_error(code, "OP-IMM-32 funct7")),
|
||||
},
|
||||
_ => return Err(decode_error(code, "OP-IMM-32 funct3")),
|
||||
}
|
||||
}
|
||||
// OP
|
||||
0b0110011 => {
|
||||
let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2());
|
||||
|
|
@ -1465,6 +1570,7 @@ mod tests {
|
|||
use crate::Imm;
|
||||
use crate::Inst;
|
||||
use crate::Reg;
|
||||
use crate::Xlen;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(slow_tests), ignore = "cfg(slow_tests) not enabled")]
|
||||
|
|
@ -1476,9 +1582,20 @@ mod tests {
|
|||
std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done));
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
let _ = Inst::decode(i);
|
||||
let _ = Inst::decode(i, Xlen::Rv32);
|
||||
}
|
||||
let _ = Inst::decode(u32::MAX);
|
||||
let _ = Inst::decode(u32::MAX, Xlen::Rv32);
|
||||
|
||||
for i in 0..u32::MAX {
|
||||
if (i % (2 << 25)) == 0 {
|
||||
let percent = i as f32 / (u32::MAX as f32);
|
||||
let done = (100.0 * percent) as usize;
|
||||
std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done));
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
let _ = Inst::decode(i, Xlen::Rv64);
|
||||
}
|
||||
let _ = Inst::decode(u32::MAX, Xlen::Rv64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1591,7 +1708,7 @@ mod tests {
|
|||
let start_time = std::time::Instant::now();
|
||||
|
||||
let insts = (start..=start.saturating_add(CHUNK_SIZE))
|
||||
.filter_map(|code| Some((code, Inst::decode_normal(code).ok()?)))
|
||||
.filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?)))
|
||||
.filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -1631,7 +1748,7 @@ mod tests {
|
|||
#[ignore = "this doesn't quite work yet because there is often a non-canonical encoding"]
|
||||
fn compressed_clang_roundtrip() {
|
||||
let insts = (0..=u16::MAX)
|
||||
.filter_map(|code| Some((code, Inst::decode_compressed(code).ok()?)))
|
||||
.filter_map(|code| Some((code, Inst::decode_compressed(code, Xlen::Rv32).ok()?)))
|
||||
.filter(|(_, inst)| is_compressed_inst_supposed_to_roundtrip(inst))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
|
|||
65
src/elf.rs
65
src/elf.rs
|
|
@ -32,18 +32,30 @@ pub struct Header {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Phdr32 {
|
||||
pub struct Phdr {
|
||||
pub p_type: u32,
|
||||
pub p_offset: u32,
|
||||
pub p_vaddr: u32,
|
||||
pub p_paddr: u32,
|
||||
pub p_filesz: u32,
|
||||
pub p_memsz: u32,
|
||||
pub p_offset: Offset,
|
||||
pub p_vaddr: Addr,
|
||||
pub p_paddr: Addr,
|
||||
pub p_filesz: u64,
|
||||
pub p_memsz: u64,
|
||||
pub p_flags: u32,
|
||||
pub p_align: u32,
|
||||
pub p_align: u64,
|
||||
}
|
||||
|
||||
impl Elf<'_> {
|
||||
fn class(&self) -> Result<ElfClass> {
|
||||
let (_, class) = self.content.split_u32()?;
|
||||
let (class, _) = class.split_bytes(1)?;
|
||||
Ok(match class[0] {
|
||||
// ELFCLASS32
|
||||
1 => ElfClass::Elf32,
|
||||
// ELFCLASS64
|
||||
2 => ElfClass::Elf64,
|
||||
_ => bail!("not a ELF32 or ELF64 file (EI_CLASS={})", class[0]),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header(&self) -> Result<Header> {
|
||||
let (ident, rest) = self.content.split_bytes(16)?;
|
||||
if ident[..4] != *b"\x7fELF" {
|
||||
|
|
@ -140,8 +152,9 @@ impl Elf<'_> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn segments_32(&self) -> Result<Vec<Phdr32>> {
|
||||
pub fn segments(&self) -> Result<Vec<Phdr>> {
|
||||
let header = self.header()?;
|
||||
let class = self.class()?;
|
||||
|
||||
let (_, phdrs) = self.content.split_bytes(header.e_phoff.0 as usize)?;
|
||||
let (mut phdrs, _) = phdrs.split_bytes((header.e_phentsize * header.e_phnum) as usize)?;
|
||||
|
|
@ -152,6 +165,8 @@ impl Elf<'_> {
|
|||
let phdr;
|
||||
(phdr, phdrs) = phdrs.split_bytes(header.e_phentsize as usize)?;
|
||||
|
||||
let phdr = match class {
|
||||
ElfClass::Elf32 => {
|
||||
let (p_type, phdr) = phdr.split_u32()?;
|
||||
let (p_offset, phdr) = phdr.split_u32()?;
|
||||
let (p_vaddr, phdr) = phdr.split_u32()?;
|
||||
|
|
@ -161,16 +176,40 @@ impl Elf<'_> {
|
|||
let (p_flags, phdr) = phdr.split_u32()?;
|
||||
let (p_align, _) = phdr.split_u32()?;
|
||||
|
||||
parsed_phdrs.push(Phdr32 {
|
||||
Phdr {
|
||||
p_type,
|
||||
p_offset,
|
||||
p_vaddr,
|
||||
p_paddr,
|
||||
p_offset: Offset(p_offset as u64),
|
||||
p_vaddr: Addr(p_vaddr as u64),
|
||||
p_paddr: Addr(p_paddr as u64),
|
||||
p_filesz: p_filesz as u64,
|
||||
p_memsz: p_memsz as u64,
|
||||
p_flags,
|
||||
p_align: p_align as u64,
|
||||
}
|
||||
}
|
||||
ElfClass::Elf64 => {
|
||||
let (p_type, phdr) = phdr.split_u32()?;
|
||||
let (p_flags, phdr) = phdr.split_u32()?;
|
||||
let (p_offset, phdr) = phdr.split_u64()?;
|
||||
let (p_vaddr, phdr) = phdr.split_u64()?;
|
||||
let (p_paddr, phdr) = phdr.split_u64()?;
|
||||
let (p_filesz, phdr) = phdr.split_u64()?;
|
||||
let (p_memsz, phdr) = phdr.split_u64()?;
|
||||
let (p_align, _) = phdr.split_u64()?;
|
||||
|
||||
Phdr {
|
||||
p_type,
|
||||
p_offset: Offset(p_offset),
|
||||
p_vaddr: Addr(p_vaddr),
|
||||
p_paddr: Addr(p_paddr),
|
||||
p_filesz,
|
||||
p_memsz,
|
||||
p_flags,
|
||||
p_align,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
parsed_phdrs.push(phdr);
|
||||
}
|
||||
|
||||
Ok(parsed_phdrs)
|
||||
|
|
|
|||
161
src/emu.rs
161
src/emu.rs
|
|
@ -20,44 +20,50 @@ impl Memory {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
pub fn slice<XLEN: XLen>(&self, addr: XLEN, len: u32) -> Result<&[u8], Status> {
|
||||
pub fn slice<XLEN: XLen>(&self, addr: XLEN, len: XLEN) -> Result<&[u8], Status> {
|
||||
self.mem
|
||||
.get((addr.as_usize())..)
|
||||
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))?
|
||||
.get(..(len as usize))
|
||||
.get(..(len.as_usize()))
|
||||
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))
|
||||
}
|
||||
pub fn slice_mut<XLEN: XLen>(&mut self, addr: XLEN, len: u32) -> Result<&mut [u8], Status> {
|
||||
pub fn slice_mut<XLEN: XLen>(&mut self, addr: XLEN, len: XLEN) -> Result<&mut [u8], Status> {
|
||||
self.mem
|
||||
.get_mut((addr.as_usize())..)
|
||||
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))?
|
||||
.get_mut(..(len as usize))
|
||||
.get_mut(..(len.as_usize()))
|
||||
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))
|
||||
}
|
||||
|
||||
pub fn load_u8<XLEN: XLen>(&self, addr: XLEN) -> Result<u8, Status> {
|
||||
Ok(u8::from_le_bytes(self.slice(addr, 1)?.try_into().unwrap()))
|
||||
Ok(u8::from_le_bytes(
|
||||
self.slice(addr, XLEN::from_32_z(1))?.try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
pub fn load_u16<XLEN: XLen>(&self, addr: XLEN) -> Result<u16, Status> {
|
||||
Ok(u16::from_le_bytes(self.slice(addr, 2)?.try_into().unwrap()))
|
||||
Ok(u16::from_le_bytes(
|
||||
self.slice(addr, XLEN::from_32_z(2))?.try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
pub fn load_u32<XLEN: XLen>(&self, addr: XLEN) -> Result<u32, Status> {
|
||||
Ok(u32::from_le_bytes(self.slice(addr, 4)?.try_into().unwrap()))
|
||||
Ok(u32::from_le_bytes(
|
||||
self.slice(addr, XLEN::from_32_z(4))?.try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
pub fn store_u8<XLEN: XLen>(&mut self, addr: XLEN, value: u8) -> Result<(), Status> {
|
||||
self.slice_mut(addr, 1)?
|
||||
self.slice_mut(addr, XLEN::from_32_z(1))?
|
||||
.copy_from_slice(&value.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
pub fn store_u16<XLEN: XLen>(&mut self, addr: XLEN, value: u16) -> Result<(), Status> {
|
||||
self.check_align(addr, 2)?;
|
||||
self.slice_mut(addr, 2)?
|
||||
self.slice_mut(addr, XLEN::from_32_z(2))?
|
||||
.copy_from_slice(&value.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
pub fn store_u32<XLEN: XLen>(&mut self, addr: XLEN, value: u32) -> Result<(), Status> {
|
||||
self.check_align(addr, 4)?;
|
||||
self.slice_mut(addr, 4)?
|
||||
self.slice_mut(addr, XLEN::from_32_z(4))?
|
||||
.copy_from_slice(&value.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -139,6 +145,28 @@ fn hash_color(value: usize) -> impl Display {
|
|||
}
|
||||
|
||||
impl<XLEN: XLen> Emulator<XLEN> {
|
||||
pub fn new(
|
||||
mem: Memory,
|
||||
start: XLEN,
|
||||
break_addr: XLEN,
|
||||
debug: bool,
|
||||
ecall_handler: Box<dyn FnMut(&mut Memory, &mut [XLEN; 32]) -> Result<(), Status>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mem,
|
||||
xreg: [XLEN::ZERO; 32],
|
||||
xreg0_value: XLEN::ZERO,
|
||||
pc: start,
|
||||
reservation_set: None,
|
||||
|
||||
is_breaking: false,
|
||||
|
||||
break_pc: break_addr,
|
||||
debug,
|
||||
ecall_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_linux(&mut self) -> Status {
|
||||
self.setup_linux_stack().unwrap();
|
||||
|
||||
|
|
@ -210,7 +238,7 @@ impl<XLEN: XLen> Emulator<XLEN> {
|
|||
print!("0x{:x} ", self.pc.as_usize());
|
||||
}
|
||||
|
||||
let (inst, was_compressed) = Inst::decode(code)?;
|
||||
let (inst, was_compressed) = Inst::decode(code, XLEN::XLEN)?;
|
||||
|
||||
if self.debug {
|
||||
println!(
|
||||
|
|
@ -649,11 +677,21 @@ impl<XLEN: XLen> Emulator<XLEN> {
|
|||
}
|
||||
|
||||
pub trait XLen: Copy + PartialEq + Eq {
|
||||
type Signed;
|
||||
type NextUnsigned;
|
||||
type NextSigned;
|
||||
|
||||
const XLEN: rvdc::Xlen;
|
||||
|
||||
const ZERO: Self;
|
||||
const SIGNED_MIN: Self;
|
||||
const MAX: Self;
|
||||
|
||||
fn from_bool(v: bool) -> Self;
|
||||
fn switch<R>(self, on_32: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R;
|
||||
|
||||
fn from_bool(v: bool) -> Self {
|
||||
Self::from_32_z(v as u32)
|
||||
}
|
||||
fn from_8_z(v: u8) -> Self {
|
||||
Self::from_32_z(v as u32)
|
||||
}
|
||||
|
|
@ -704,29 +742,17 @@ pub trait XLen: Copy + PartialEq + Eq {
|
|||
fn unsigned_rem(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
impl XLen for u32 {
|
||||
macro_rules! xlen_impl {
|
||||
() => {
|
||||
const ZERO: Self = 0;
|
||||
const SIGNED_MIN: Self = i32::MIN as u32;
|
||||
const MAX: Self = u32::MAX;
|
||||
const SIGNED_MIN: Self = Self::Signed::MIN as Self;
|
||||
const MAX: Self = Self::MAX;
|
||||
|
||||
fn from_bool(v: bool) -> Self {
|
||||
v as u32
|
||||
}
|
||||
fn from_32_s(v: u32) -> Self {
|
||||
v
|
||||
}
|
||||
fn from_32_z(v: u32) -> Self {
|
||||
v
|
||||
}
|
||||
fn from_imm(v: Imm) -> Self {
|
||||
v.as_u32()
|
||||
}
|
||||
fn as_usize(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
|
||||
fn truncate32(self) -> u32 {
|
||||
self
|
||||
self as u32
|
||||
}
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
|
|
@ -745,13 +771,13 @@ impl XLen for u32 {
|
|||
self ^ other
|
||||
}
|
||||
fn signed_lt(self, other: Self) -> bool {
|
||||
(self as i32) < (other as i32)
|
||||
(self as Self::Signed) < (other as Self::Signed)
|
||||
}
|
||||
fn unsigned_lt(self, other: Self) -> bool {
|
||||
self < other
|
||||
}
|
||||
fn signed_ge(self, other: Self) -> bool {
|
||||
(self as i32) >= (other as i32)
|
||||
(self as Self::Signed) >= (other as Self::Signed)
|
||||
}
|
||||
fn unsigned_ge(self, other: Self) -> bool {
|
||||
self >= other
|
||||
|
|
@ -763,42 +789,93 @@ impl XLen for u32 {
|
|||
self.wrapping_shr(other)
|
||||
}
|
||||
fn signed_shr(self, other: u32) -> Self {
|
||||
((self as i32).wrapping_shr(other)) as u32
|
||||
((self as Self::Signed).wrapping_shr(other)) as Self
|
||||
}
|
||||
fn signed_min(self, other: Self) -> Self {
|
||||
(self as i32).min(other as i32) as u32
|
||||
(self as Self::Signed).min(other as Self::Signed) as Self
|
||||
}
|
||||
fn unsigned_min(self, other: Self) -> Self {
|
||||
self.min(other)
|
||||
}
|
||||
fn signed_max(self, other: Self) -> Self {
|
||||
(self as i32).max(other as i32) as u32
|
||||
(self as Self::Signed).max(other as Self::Signed) as Self
|
||||
}
|
||||
fn unsigned_max(self, other: Self) -> Self {
|
||||
self.max(other)
|
||||
}
|
||||
fn mul_lower(self, other: Self) -> Self {
|
||||
(self as i32).wrapping_mul(other as i32) as u32
|
||||
(self as Self::Signed).wrapping_mul(other as Self::Signed) as Self
|
||||
}
|
||||
fn signed_mul_upper(self, other: Self) -> Self {
|
||||
let mul_result = (self as i32 as i64).wrapping_mul(other as i32 as i64);
|
||||
let shifted = (mul_result as u64) >> 32;
|
||||
shifted as u32
|
||||
let mul_result = (self as Self::Signed as Self::NextSigned)
|
||||
.wrapping_mul(other as Self::Signed as Self::NextSigned);
|
||||
let shifted = (mul_result as Self::NextUnsigned) >> Self::BITS;
|
||||
shifted as Self
|
||||
}
|
||||
fn unsigned_mul_upper(self, other: Self) -> Self {
|
||||
let shifted = ((self as u64).wrapping_mul(other as u64)) >> 32;
|
||||
shifted as u32
|
||||
let shifted = ((self as Self::NextUnsigned).wrapping_mul(other as Self::NextUnsigned))
|
||||
>> Self::BITS;
|
||||
shifted as Self
|
||||
}
|
||||
fn signed_div(self, other: Self) -> Self {
|
||||
((self as i32) / (other as i32)) as u32
|
||||
((self as Self::Signed) / (other as Self::Signed)) as Self
|
||||
}
|
||||
fn unsigned_div(self, other: Self) -> Self {
|
||||
self / other
|
||||
}
|
||||
fn signed_rem(self, other: Self) -> Self {
|
||||
((self as i32) % (other as i32)) as u32
|
||||
((self as Self::Signed) % (other as Self::Signed)) as Self
|
||||
}
|
||||
fn unsigned_rem(self, other: Self) -> Self {
|
||||
self % other
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl XLen for u32 {
|
||||
type Signed = i32;
|
||||
type NextUnsigned = u64;
|
||||
type NextSigned = i64;
|
||||
|
||||
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32;
|
||||
|
||||
fn switch<R>(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R {
|
||||
on_32(self)
|
||||
}
|
||||
|
||||
fn from_32_s(v: u32) -> Self {
|
||||
v
|
||||
}
|
||||
fn from_32_z(v: u32) -> Self {
|
||||
v
|
||||
}
|
||||
fn from_imm(v: Imm) -> Self {
|
||||
v.as_u32()
|
||||
}
|
||||
|
||||
xlen_impl!();
|
||||
}
|
||||
|
||||
impl XLen for u64 {
|
||||
type Signed = i64;
|
||||
type NextUnsigned = u128;
|
||||
type NextSigned = i128;
|
||||
|
||||
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64;
|
||||
|
||||
fn switch<R>(self, _: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R {
|
||||
on_64(self)
|
||||
}
|
||||
|
||||
fn from_32_s(v: u32) -> Self {
|
||||
v as i32 as i64 as u64
|
||||
}
|
||||
fn from_32_z(v: u32) -> Self {
|
||||
v as u64
|
||||
}
|
||||
fn from_imm(v: Imm) -> Self {
|
||||
v.as_u64()
|
||||
}
|
||||
|
||||
xlen_impl!();
|
||||
}
|
||||
|
|
|
|||
36
src/lib.rs
36
src/lib.rs
|
|
@ -9,13 +9,14 @@ const MEMORY_SIZE: usize = 2 << 21;
|
|||
pub fn execute_linux_elf(
|
||||
elf: &[u8],
|
||||
debug: bool,
|
||||
break_addr: u32,
|
||||
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
|
||||
break_addr: u64,
|
||||
ecall_handler32: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
|
||||
ecall_handler64: Box<dyn FnMut(&mut emu::Memory, &mut [u64; 32]) -> Result<(), emu::Status>>,
|
||||
) -> eyre::Result<emu::Status> {
|
||||
let elf = elf::Elf { content: elf };
|
||||
let header = elf.header()?;
|
||||
|
||||
let segments = elf.segments_32()?;
|
||||
let segments = elf.segments()?;
|
||||
|
||||
let mut mem = emu::Memory {
|
||||
mem: vec![0; MEMORY_SIZE],
|
||||
|
|
@ -30,13 +31,13 @@ pub fn execute_linux_elf(
|
|||
if phdr.p_filesz > 0 {
|
||||
let contents = &elf
|
||||
.content
|
||||
.get((phdr.p_offset as usize)..)
|
||||
.get((phdr.p_offset.0 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)..)
|
||||
.get_mut((phdr.p_vaddr.0 as usize)..)
|
||||
.ok_or_eyre("invalid offset")?
|
||||
.get_mut(..(phdr.p_filesz as usize))
|
||||
.ok_or_eyre("invalid offset")?
|
||||
|
|
@ -61,19 +62,16 @@ pub fn execute_linux_elf(
|
|||
|
||||
let start = header.e_entry;
|
||||
|
||||
let mut emu = emu::Emulator {
|
||||
mem,
|
||||
xreg: [0; 32],
|
||||
xreg0_value: 0,
|
||||
pc: start.0 as u32,
|
||||
reservation_set: None,
|
||||
|
||||
is_breaking: false,
|
||||
|
||||
break_pc: break_addr,
|
||||
debug,
|
||||
ecall_handler,
|
||||
};
|
||||
|
||||
match header.class {
|
||||
elf::ElfClass::Elf32 => {
|
||||
let mut emu =
|
||||
emu::Emulator::<u32>::new(mem, start.0 as u32, break_addr as u32, debug, ecall_handler32);
|
||||
Ok(emu.start_linux())
|
||||
}
|
||||
elf::ElfClass::Elf64 => {
|
||||
let mut emu =
|
||||
emu::Emulator::<u64>::new(mem, start.0, break_addr, debug, ecall_handler64);
|
||||
Ok(emu.start_linux())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
src/main.rs
64
src/main.rs
|
|
@ -1,7 +1,7 @@
|
|||
use std::io::Write;
|
||||
use std::{cell::RefCell, io::Write, sync::Arc};
|
||||
|
||||
use eyre::eyre;
|
||||
use rustv32i::emu::{self, Memory};
|
||||
use rustv32i::emu::{self, Memory, XLen};
|
||||
use rvdc::Reg;
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
|
|
@ -12,22 +12,26 @@ fn main() -> eyre::Result<()> {
|
|||
.nth(1)
|
||||
.map(|addr| {
|
||||
if let Some(addr) = addr.strip_prefix("0x") {
|
||||
u32::from_str_radix(addr, 16)
|
||||
u64::from_str_radix(addr, 16)
|
||||
} else {
|
||||
u32::from_str_radix(&addr, 10)
|
||||
u64::from_str_radix(&addr, 10)
|
||||
}
|
||||
})
|
||||
.unwrap_or(Ok(0))?;
|
||||
|
||||
let debug = std::env::args().any(|arg| arg == "--debug");
|
||||
|
||||
let mut syscall_state = SyscallState { set_child_tid: 0 };
|
||||
let syscall_state = Arc::new(RefCell::new(SyscallState { set_child_tid: 0 }));
|
||||
let syscall_state64 = syscall_state.clone();
|
||||
|
||||
let status = rustv32i::execute_linux_elf(
|
||||
&content,
|
||||
debug,
|
||||
break_addr,
|
||||
Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)),
|
||||
Box::new(move |mem, xreg| ecall_handler::<u32>(mem, xreg, &mut syscall_state.borrow_mut())),
|
||||
Box::new(move |mem, xreg| {
|
||||
ecall_handler::<u64>(mem, xreg, &mut syscall_state64.borrow_mut())
|
||||
}),
|
||||
)?;
|
||||
|
||||
std::io::stdout().flush()?;
|
||||
|
|
@ -44,12 +48,12 @@ fn main() -> eyre::Result<()> {
|
|||
}
|
||||
|
||||
struct SyscallState {
|
||||
set_child_tid: u32,
|
||||
set_child_tid: u64,
|
||||
}
|
||||
|
||||
fn ecall_handler(
|
||||
fn ecall_handler<XLEN: Into<u64> + From<u32> + XLen>(
|
||||
mem: &mut Memory,
|
||||
xreg: &mut [u32; 32],
|
||||
xreg: &mut [XLEN; 32],
|
||||
syscall_state: &mut SyscallState,
|
||||
) -> Result<(), emu::Status> {
|
||||
let nr = xreg[Reg::A7.0 as usize];
|
||||
|
|
@ -60,22 +64,22 @@ fn ecall_handler(
|
|||
|
||||
// https://jborza.com/post/2021-05-11-riscv-linux-syscalls/
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
|
||||
match nr {
|
||||
match nr.into() {
|
||||
// ioctl
|
||||
29 => {
|
||||
let fd = arg0;
|
||||
let request = arg1;
|
||||
|
||||
match request {
|
||||
match request.into() {
|
||||
// TIOCGWINSZ
|
||||
0x5413 => {
|
||||
let wsz_ptr = xreg[Reg::A2.0 as usize];
|
||||
let wsz_ptr = xreg[Reg::A2.0 as usize].into();
|
||||
|
||||
let mut wsz: libc::winsize = unsafe { std::mem::zeroed() };
|
||||
|
||||
let r = unsafe { libc::ioctl(fd as i32, libc::TIOCGWINSZ, &mut wsz) };
|
||||
let r = unsafe { libc::ioctl(fd.into() as i32, libc::TIOCGWINSZ, &mut wsz) };
|
||||
|
||||
xreg[Reg::A0.0 as usize] = r as u32;
|
||||
xreg[Reg::A0.0 as usize] = (r as u32).into();
|
||||
if r >= 0 {
|
||||
mem.store_u16(wsz_ptr, wsz.ws_row)?;
|
||||
mem.store_u16(wsz_ptr + 2, wsz.ws_col)?;
|
||||
|
|
@ -83,16 +87,16 @@ fn ecall_handler(
|
|||
mem.store_u16(wsz_ptr + 6, wsz.ws_ypixel)?;
|
||||
}
|
||||
}
|
||||
_ => todo!("unknown ioctl: {request}"),
|
||||
_ => todo!("unknown ioctl: {}", request.into()),
|
||||
}
|
||||
}
|
||||
// read
|
||||
63 => {
|
||||
let fd = arg0;
|
||||
let fd = arg0.into();
|
||||
let ptr = xreg[Reg::A1.0 as usize];
|
||||
let len = xreg[Reg::A2.0 as usize];
|
||||
|
||||
let buf = mem.slice_mut(ptr, len)?;
|
||||
let buf = mem.slice_mut::<XLEN>(ptr, len)?;
|
||||
|
||||
let len = unsafe { libc::read(fd as i32, buf.as_mut_ptr().cast(), buf.len()) };
|
||||
let ret = if len < 0 {
|
||||
|
|
@ -101,11 +105,11 @@ fn ecall_handler(
|
|||
len as u32
|
||||
};
|
||||
|
||||
xreg[Reg::A0.0 as usize] = ret;
|
||||
xreg[Reg::A0.0 as usize] = ret.into();
|
||||
}
|
||||
// write
|
||||
64 => {
|
||||
let fd = arg0;
|
||||
let fd = arg0.into();
|
||||
let ptr = xreg[Reg::A1.0 as usize];
|
||||
let len = xreg[Reg::A2.0 as usize];
|
||||
|
||||
|
|
@ -118,13 +122,13 @@ fn ecall_handler(
|
|||
len as u32
|
||||
};
|
||||
|
||||
xreg[Reg::A0.0 as usize] = ret;
|
||||
xreg[Reg::A0.0 as usize] = ret.into();
|
||||
}
|
||||
// https://man7.org/linux/man-pages/man3/writev.3p.html
|
||||
66 => {
|
||||
let fd = arg0;
|
||||
let iovec = arg1;
|
||||
let iovcnt = arg2;
|
||||
let fd = arg0.into();
|
||||
let iovec = arg1.into();
|
||||
let iovcnt = arg2.into();
|
||||
|
||||
let mut written = 0;
|
||||
|
||||
|
|
@ -142,27 +146,27 @@ fn ecall_handler(
|
|||
};
|
||||
|
||||
if (ret as i32) < 0 {
|
||||
xreg[Reg::A0.0 as usize] = ret;
|
||||
xreg[Reg::A0.0 as usize] = ret.into();
|
||||
return Ok(());
|
||||
} else {
|
||||
written += ret;
|
||||
}
|
||||
}
|
||||
|
||||
xreg[Reg::A0.0 as usize] = written;
|
||||
xreg[Reg::A0.0 as usize] = written.into();
|
||||
}
|
||||
// exit | exit_group
|
||||
93 | 94 => {
|
||||
return Err(emu::Status::Exit {
|
||||
code: xreg[Reg::A0.0 as usize] as i32,
|
||||
code: xreg[Reg::A0.0 as usize].into() as i32,
|
||||
});
|
||||
}
|
||||
// <https://man7.org/linux/man-pages/man2/set_tid_address.2.html>
|
||||
96 => {
|
||||
let tidptr = arg0;
|
||||
syscall_state.set_child_tid = tidptr;
|
||||
syscall_state.set_child_tid = tidptr.into();
|
||||
|
||||
xreg[Reg::A0.0 as usize] = 1; // thread ID
|
||||
xreg[Reg::A0.0 as usize] = 1.into(); // thread ID
|
||||
}
|
||||
// ppoll - called for some stdin/stdout/stderr check.
|
||||
414 => {
|
||||
|
|
@ -170,10 +174,10 @@ fn ecall_handler(
|
|||
// and opens /dev/null for them if they are.
|
||||
// They're always valid here, so just get out.
|
||||
|
||||
xreg[Reg::A0.0 as usize] = 0;
|
||||
xreg[Reg::A0.0 as usize] = 0.into();
|
||||
}
|
||||
_ => {
|
||||
todo!("unkonwn syscall: {nr}");
|
||||
todo!("unkonwn syscall: {}", nr.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,21 +27,30 @@ fn check() -> eyre::Result<()> {
|
|||
continue;
|
||||
}
|
||||
|
||||
test_case(tmpdir.path(), &file, name, "rv32ima")?;
|
||||
test_case(tmpdir.path(), &file, name, "rv32imac")?;
|
||||
test_case(tmpdir.path(), &file, name, 32, "rv32ima")?;
|
||||
test_case(tmpdir.path(), &file, name, 32, "rv32imac")?;
|
||||
|
||||
// test_case(tmpdir.path(), &file, name, 64, "rv64ima")?;
|
||||
// test_case(tmpdir.path(), &file, name, 64, "rv64imac")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::Result<()> {
|
||||
fn test_case(
|
||||
tmpdir: &Path,
|
||||
file: &DirEntry,
|
||||
name: &str,
|
||||
size: u8,
|
||||
march: &str,
|
||||
) -> eyre::Result<()> {
|
||||
let name = format!("{name} ({march})");
|
||||
write!(std::io::stdout(), "test {name} ...")?;
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
eprintln!("---- START TEST {name} -----");
|
||||
|
||||
let output = build(&tmpdir, &file.path(), march).wrap_err(format!("building {name}"))?;
|
||||
let output = build(&tmpdir, &file.path(), size, march).wrap_err(format!("building {name}"))?;
|
||||
let content =
|
||||
std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?;
|
||||
|
||||
|
|
@ -60,6 +69,17 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R
|
|||
Err(rustv32i::emu::Status::Trap("wrong syscall"))
|
||||
}
|
||||
}),
|
||||
Box::new(|_, xreg| {
|
||||
if xreg[Reg::A7.0 as usize] == u32::MAX as u64 {
|
||||
if xreg[Reg::A0.0 as usize] == 1 {
|
||||
Err(rustv32i::emu::Status::Exit { code: 0 })
|
||||
} else {
|
||||
Err(rustv32i::emu::Status::Trap("fail"))
|
||||
}
|
||||
} else {
|
||||
Err(rustv32i::emu::Status::Trap("wrong syscall"))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.wrap_err(format!("{name} failed"))?;
|
||||
|
||||
|
|
@ -71,11 +91,12 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R
|
|||
}
|
||||
}
|
||||
|
||||
fn build(tmpdir: &Path, src: &Path, march: &str) -> eyre::Result<PathBuf> {
|
||||
fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> 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"]);
|
||||
cmd.args(["-target", &format!("riscv{size}-unknown-none-elf")]);
|
||||
cmd.arg("-nostdlib");
|
||||
cmd.arg(format!("-march={march}"));
|
||||
cmd.arg(src);
|
||||
cmd.arg("-o");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue