diff --git a/src/emu.rs b/src/emu.rs index 39dcae9..c20f5bd 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -1,4 +1,4 @@ -use crate::inst::{AmoOp, Inst, InstCode, IsCompressed}; +use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed}; use std::{ fmt::{Debug, Display}, io::Write, @@ -66,7 +66,7 @@ impl Memory { #[derive(Debug)] pub enum Status { Trap(&'static str), - IllegalInstruction(InstCode, &'static str), + IllegalInstruction(DecodeError), InvalidMemoryAccess(u32), UnalignedPc(u32), UnaligneMemoryAccess { addr: u32, required_align: u32 }, @@ -74,6 +74,12 @@ pub enum Status { Exit { code: i32 }, } +impl From for Status { + fn from(value: DecodeError) -> Self { + Status::IllegalInstruction(value) + } +} + pub struct Emulator { pub mem: Memory, pub xreg: [u32; 32], @@ -87,6 +93,7 @@ pub struct Emulator { pub is_breaking: bool, pub debug: bool, + pub break_pc: u32, pub ecall_handler: Box Result<(), Status>>, } @@ -219,7 +226,6 @@ impl Emulator { // TODO: add some auxv for the poor process. self.mem.store_u32(next_word(), 0)?; - Ok(()) } @@ -238,16 +244,16 @@ impl Emulator { fn step(&mut self) -> Result<(), Status> { let code = self.mem.load_u32(self.pc)?; - let (inst, was_compressed) = Inst::decode(code)?; - if self.is_breaking { - self.debug_interactive(); + if self.debug { + print!("0x{:x} ", self.pc); } + let (inst, was_compressed) = Inst::decode(code)?; + if self.debug { println!( - "0x{:x} {} (sp: {}) {inst:?}", - self.pc, + "{} (sp: {}) {inst:?}", match was_compressed { IsCompressed::Yes => "C", IsCompressed::No => " ", @@ -256,6 +262,14 @@ impl Emulator { ); } + if self.pc == self.break_pc { + self.is_breaking = true; + } + + if self.is_breaking { + self.debug_interactive(); + } + let next_pc = self.pc.wrapping_add(match was_compressed { IsCompressed::Yes => 2, IsCompressed::No => 4, @@ -515,8 +529,8 @@ impl Emulator { std::io::stdout().flush().unwrap(); let mut input = String::new(); std::io::stdin().read_line(&mut input).unwrap(); - let input = input.trim(); - match input { + let mut input = input.trim().split_whitespace(); + match input.next().unwrap_or_default() { "c" => { self.is_breaking = false; return; @@ -525,7 +539,43 @@ impl Emulator { return; } "q" => std::process::exit(0), - "p" => { + "m" => { + let Some(size) = input.next() else { + eprintln!("require b/h/w argument for m command"); + continue; + }; + let Some(addr) = input.next() else { + eprintln!("require address argument for m command"); + continue; + }; + let Ok(addr) = (if let Some(addr) = addr.strip_prefix("0x") { + u32::from_str_radix(addr, 16) + } else { + u32::from_str_radix(&addr, 10) + }) else { + eprintln!("invalid address"); + continue; + }; + let value = match size { + "w" => self.mem.load_u32(addr), + "h" => self.mem.load_u16(addr).map(Into::into), + "b" => self.mem.load_u8(addr).map(Into::into), + _ => { + eprintln!("require b/h/w argument for m command"); + continue; + } + }; + let value = match value { + Ok(v) => v, + Err(e) => { + eprintln!("failed to load value: {e:?}"); + continue; + } + }; + + println!("{value} (0x{value:x})"); + } + "r" => { let format_value = |v: u32| { if v == 0 { format!("{:0>8x}", v.black()) @@ -626,7 +676,8 @@ impl Emulator { _ => println!( "commands: - ?: help -- p: print registers +- r: print registers +- m : read a byte/half/word of memory at an address - c: continue until next breakpoint - s: step one instruction - q: quit" diff --git a/src/inst.rs b/src/inst.rs index f57322b..23ab9f9 100644 --- a/src/inst.rs +++ b/src/inst.rs @@ -1,4 +1,4 @@ -use crate::emu::{Reg, Status}; +use crate::emu::Reg; use std::fmt::{Debug, Display}; use std::ops::RangeInclusive; @@ -173,6 +173,11 @@ pub enum AmoOp { Maxu, } +pub struct DecodeError { + pub instruction: u32, + pub unexpected_field: &'static str, +} + impl Fence { pub fn is_pause(&self) -> bool { self.pred @@ -393,6 +398,27 @@ impl Display for AmoOp { } } +impl Debug for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DecodeError") + .field("instruction", &format!("{:0>32b}", self.instruction)) + .field("unexpected_field", &self.unexpected_field) + .finish() + } +} + +impl Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "failed to decode instruction '{:0>32b}' because of field '{}'", + self.instruction, self.unexpected_field + ) + } +} + +impl std::error::Error for DecodeError {} + fn sign_extend(value: u32, size: u32) -> u32 { let right = u32::BITS - size; (((value << right) as i32) >> right) as u32 @@ -461,12 +487,6 @@ impl InstCode { } } -impl Debug for InstCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:0>32b}", self.0) - } -} - #[derive(Clone, Copy)] pub struct InstCodeC(u16); @@ -537,8 +557,15 @@ pub enum IsCompressed { No, } +fn decode_error(instruction: impl Into, unexpected_field: &'static str) -> DecodeError { + DecodeError { + instruction: instruction.into().0, + unexpected_field, + } +} + impl Inst { - pub fn decode(code: u32) -> Result<(Inst, IsCompressed), Status> { + pub fn decode(code: u32) -> Result<(Inst, IsCompressed), DecodeError> { let is_compressed = (code & 0b11) != 0b11; if is_compressed { Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes)) @@ -547,10 +574,10 @@ impl Inst { } } - fn decode_compressed(code: u16) -> Result { + fn decode_compressed(code: u16) -> Result { let code = InstCodeC(code); if code.0 == 0 { - return Err(Status::IllegalInstruction(code.into(), "null instruction")); + return Err(decode_error(code, "null instruction")); } let inst = match code.quadrant() { // C0 @@ -573,7 +600,7 @@ impl Inst { src: code.rs2_short(), base: code.rs1_short(), }, - _ => return Err(Status::IllegalInstruction(code.into(), "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // C1 0b01 => match code.funct3() { @@ -610,7 +637,7 @@ impl Inst { // C.SRLI -> srli \rd', \rd', \imm 0b00 => { if bit12 != 0 { - return Err(Status::IllegalInstruction(code.into(), "imm")); + return Err(decode_error(code, "imm")); } Inst::Srli { @@ -622,7 +649,7 @@ impl Inst { // C.SRAI -> srai \rd', \rd', \imm 0b01 => { if bit12 != 0 { - return Err(Status::IllegalInstruction(code.into(), "imm")); + return Err(decode_error(code, "imm")); } Inst::Srai { @@ -639,7 +666,7 @@ impl Inst { }, 0b11 => { if bit12 != 0 { - return Err(Status::IllegalInstruction(code.into(), "bit 12")); + return Err(decode_error(code, "bit 12")); } let funct2 = code.extract(5..=6); match funct2 { @@ -696,6 +723,7 @@ impl Inst { (3..=4, 7), (5..=5, 6), (6..=6, 4), + (12..=12, 9), ]), dest: Reg::SP, src1: Reg::SP, @@ -704,7 +732,7 @@ impl Inst { _ => { let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]); if uimm == 0 { - return Err(Status::IllegalInstruction(code.into(), "imm")); + return Err(decode_error(code, "imm")); } Inst::Lui { uimm, @@ -737,14 +765,14 @@ impl Inst { src1: code.rs1_short(), src2: Reg::ZERO, }, - _ => return Err(Status::IllegalInstruction(code.into(), "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // C2 0b10 => match code.funct3() { // C.SLLI -> slli \rd, \rd, \imm 0b000 => { if code.extract(12..=12) != 0 { - return Err(Status::IllegalInstruction(code.into(), "imm")); + return Err(decode_error(code, "imm")); } Inst::Slli { imm: code.immediate_u(&[(2..=6, 0), (12..=12, 5)]), @@ -756,7 +784,7 @@ impl Inst { 0b010 => { let dest = code.rd(); if dest.0 == 0 { - return Err(Status::IllegalInstruction(code.into(), "rd")); + return Err(decode_error(code, "rd")); } Inst::Lw { @@ -773,7 +801,7 @@ impl Inst { // C.JR -> jalr zero, 0(\rs1) (0, _, 0) => { if rd_rs1.0 == 0 { - return Err(Status::IllegalInstruction(code.into(), "rs1")); + return Err(decode_error(code, "rs1")); } Inst::Jalr { offset: 0, @@ -801,7 +829,7 @@ impl Inst { src1: rd_rs1, src2: rs2, }, - _ => return Err(Status::IllegalInstruction(code.into(), "inst")), + _ => return Err(decode_error(code, "inst")), } } // C.SWSP -> sw \reg \offset(sp) @@ -810,14 +838,14 @@ impl Inst { src: code.rs2(), base: Reg::SP, }, - _ => return Err(Status::IllegalInstruction(code.into(), "funct3")), + _ => return Err(decode_error(code, "funct3")), }, - _ => return Err(Status::IllegalInstruction(code.into(), "op")), + _ => return Err(decode_error(code, "op")), }; Ok(inst) } - fn decode_normal(code: u32) -> Result { + fn decode_normal(code: u32) -> Result { let code = InstCode(code); let inst = match code.opcode() { // LUI @@ -842,7 +870,7 @@ impl Inst { base: code.rs1(), dest: code.rd(), }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // BRANCH 0b1100011 => match code.funct3() { @@ -876,7 +904,7 @@ impl Inst { src1: code.rs1(), src2: code.rs2(), }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // LOAD 0b0000011 => match code.funct3() { @@ -905,7 +933,7 @@ impl Inst { dest: code.rd(), base: code.rs1(), }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // STORE 0b0100011 => match code.funct3() { @@ -924,7 +952,7 @@ impl Inst { src: code.rs2(), base: code.rs1(), }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // OP-IMM 0b0010011 => match code.funct3() { @@ -974,9 +1002,9 @@ impl Inst { dest: code.rd(), src1: code.rs1(), }, - _ => return Err(Status::IllegalInstruction(code, "funct7")), + _ => return Err(decode_error(code, "funct7")), }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), }, // OP 0b0110011 => { @@ -1001,7 +1029,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(Status::IllegalInstruction(code, "funct3/funct7")), + _ => return Err(decode_error(code, "funct3/funct7")), } } // MISC-MEM @@ -1030,34 +1058,34 @@ impl Inst { src: code.rs1(), }, }, - _ => return Err(Status::IllegalInstruction(code, "funct3")), + _ => return Err(decode_error(code, "funct3")), } } // SYSTEM 0b1110011 => { if code.0 == 0b11000000000000000001000001110011 { - return Err(Status::Trap("unimp instruction")); + return Err(decode_error(code, "unimp instruction")); } if code.rd().0 != 0 { - return Err(Status::IllegalInstruction(code, "rd")); + return Err(decode_error(code, "rd")); } if code.funct3() != 0 { - return Err(Status::IllegalInstruction(code, "funct3")); + return Err(decode_error(code, "funct3")); } if code.rs1().0 != 0 { - return Err(Status::IllegalInstruction(code, "rs1")); + return Err(decode_error(code, "rs1")); } match code.imm_i() { 0b000000000000 => Inst::Ecall, 0b000000000001 => Inst::Ebreak, - _ => return Err(Status::IllegalInstruction(code, "imm")), + _ => return Err(decode_error(code, "imm")), } } // AMO 0b00101111 => { // width must be W if code.funct3() != 0b010 { - return Err(Status::IllegalInstruction(code, "funct3")); + return Err(decode_error(code, "funct3")); } let kind = code.extract(27..=31); @@ -1070,7 +1098,7 @@ impl Inst { // LR 0b00010 => { if code.rs2().0 != 0 { - return Err(Status::IllegalInstruction(code, "rs2")); + return Err(decode_error(code, "rs2")); } Inst::LrW { @@ -1097,7 +1125,7 @@ impl Inst { 0b10100 => AmoOp::Max, 0b11000 => AmoOp::Minu, 0b11100 => AmoOp::Maxu, - _ => return Err(Status::IllegalInstruction(code, "funct7")), + _ => return Err(decode_error(code, "funct7")), }; Inst::AmoW { order, @@ -1109,7 +1137,7 @@ impl Inst { } } } - _ => return Err(Status::IllegalInstruction(code, "opcode")), + _ => return Err(decode_error(code, "opcode")), }; Ok(inst) } diff --git a/src/lib.rs b/src/lib.rs index 5151fb3..b05365a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ const MEMORY_SIZE: usize = 2 << 21; pub fn execute_linux_elf( elf: &[u8], debug: bool, + break_addr: u32, ecall_handler: Box Result<(), emu::Status>>, ) -> eyre::Result { let elf = elf::Elf { content: elf }; @@ -67,9 +68,10 @@ pub fn execute_linux_elf( xreg0_value: 0, pc: start, reservation_set: None, - + is_breaking: false, + break_pc: break_addr, debug, ecall_handler, }; diff --git a/src/main.rs b/src/main.rs index f4c8e81..ecf03be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,36 @@ +use std::io::Write; + 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 break_addr = std::env::args() + .skip_while(|arg| arg != "--break") + .nth(1) + .map(|addr| { + if let Some(addr) = addr.strip_prefix("0x") { + u32::from_str_radix(addr, 16) + } else { + u32::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 status = rustv32i::execute_linux_elf( &content, - std::env::args().any(|arg| arg == "--debug"), + debug, + break_addr, Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)), )?; + std::io::stdout().flush()?; + match status { emu::Status::Exit { code } => { eprintln!("exited with code {code}"); diff --git a/tests/check.rs b/tests/check.rs index 7388bec..823ab24 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -47,6 +47,7 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R let status = rustv32i::execute_linux_elf( &content, true, + 0, Box::new(|_, xreg| { if xreg[Reg::A7.0 as usize] == u32::MAX { if xreg[Reg::A0.0 as usize] == 1 { diff --git a/tests/check/int_comp.S b/tests/check/int_comp.S index 317535b..71753fa 100644 --- a/tests/check/int_comp.S +++ b/tests/check/int_comp.S @@ -2,36 +2,75 @@ #include "../helper.S" -.macro CASER inst a b expected +.macro CASER inst:req a:req b:req expected:req li t0, \a li t1, \b \inst t2, t0, t1 ASSERT_EQ t2, \expected .endm -.macro CASE_IMM inst a b expected +.macro CASE_IMM inst:req a:req b:req expected:req li t0, \a \inst t2, t0, \b ASSERT_EQ t2, \expected .endm -.macro CASE_BOTH inst insti a b expected +.macro CASE_BOTH inst:req insti:req a:req b:req expected:req CASER \inst, \a, \b, \expected CASE_IMM \insti, \a, \b, \expected .endm - -.macro CASE inst a b expected +.macro CASE inst:req a:req b:req expected:req CASE_BOTH \inst, \inst\()i, \a, \b, \expected .endm +.macro WITH_SINGLE_TEST_NUMBERS macro + \macro a, 0 + \macro c, 1 + \macro d, 2 + \macro u, 3 + \macro e, 4 + \macro v, 5 + \macro f, 8 + \macro t, 10 + \macro g, 16 + \macro h, 32 + \macro i, 64 + \macro s, 100 + \macro j, 128 + \macro k, 256 + \macro l, 512 + \macro w, 1000 + \macro m, 1024 + \macro n, 2047 + \macro b, -1 + \macro o, -2 + \macro p, -16 + \macro q, -1024 + \macro r, -1000 + +.endm + +.macro WITH_TWO_TEST_NUMBERS macro + .macro \macro\()_TMP namea:req a:req + .macro \macro\()_TMP_\namea nameb:req b:req + \macro \a, \b + .endm + + WITH_SINGLE_TEST_NUMBERS \macro\()_TMP_\namea + .endm + + WITH_SINGLE_TEST_NUMBERS \macro\()_TMP +.endm + START_TEST # Base instructions - CASE add 10, 20, 30 - CASE add 10, -2, 8 - CASE add 10, 0, 10 - CASE add 0, 0, 0 + .macro CASE_ADD a:req, b:req + CASE add, \a, \b, \a + \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_ADD CASE slt 10 20 1 CASE slt 20 10 0 @@ -52,15 +91,33 @@ START_TEST CASE and, -1, 40, 40 CASE and, 0b101, 0b100, 0b100 + .macro CASE_AND a:req, b:req + CASE and, \a, \b, \a & \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_AND + CASE or, -1, 0, -1 CASE or, -1, 40, -1 CASE or, 0, 0, 0 CASE or, 0b101, 0b110, 0b111 + .macro CASE_OR a:req, b:req + CASE or, \a, \b, \a | \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_OR + CASE xor, -1, 0, -1 CASE xor, -1, -1, 0 CASE xor 0b101, 0b100, 0b001 + .macro CASE_XOR a:req, b:req + CASE xor, \a, \b, \a ^ \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_XOR + CASE sll, 2, 1, 4 CASE sll, 2, 20, 2097152 CASE sll, 2, 30, 2147483648 @@ -84,6 +141,12 @@ START_TEST CASER sub, -1, -2, 1 CASER sub, 0, 4294967295, 1 + .macro CASE_SUB a:req, b:req + CASER sub, \a, \b, \a - \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_SUB + CASE sra, 4, 1, 2 CASE sra, 0, 10, 0 CASE sra, 10, 0, 10 @@ -100,6 +163,12 @@ START_TEST CASER mul, -1, -1, 1 CASER mul, 25252566, 5225225, 353909638 + .macro CASE_MUL a:req, b:req + CASER mul, \a, \b, \a * \b + .endm + + WITH_TWO_TEST_NUMBERS CASE_MUL + CASER mulh 4, 4, 0 CASER mulh, -1, -1, 0 CASER mulh, 25252566, 5225225, 30722 diff --git a/tests/check/stack.S b/tests/check/stack.S new file mode 100644 index 0000000..decd416 --- /dev/null +++ b/tests/check/stack.S @@ -0,0 +1,57 @@ +# Stack Pointer Addition (special-cased by compressed instructions) +# I've had two bugs in this area before... + +#include "../helper.S" + +.macro CASE_FOR_REGISTER reg:req number:req + li \reg, 0 + addi \reg, \reg, \number + ASSERT_EQ \reg, \number +.endm + +# The goal is sp addition, but why not test some other registers as well while we're at it :) + +.macro CASE number + CASE_FOR_REGISTER sp, \number + CASE_FOR_REGISTER ra, \number + CASE_FOR_REGISTER a0, \number + CASE_FOR_REGISTER s0, \number + CASE_FOR_REGISTER t0, \number +.endm + +START_TEST + CASE 0 + CASE 1 + CASE 2 + CASE 4 + CASE 8 + CASE 10 + CASE 16 + CASE 32 + CASE 64 + CASE 100 + CASE 128 + CASE 200 + CASE 256 + CASE 512 + CASE 1024 + CASE 2047 + + CASE -1 + CASE -2 + CASE -4 + CASE -8 + CASE -10 + CASE -16 + CASE -32 + CASE -64 + CASE -100 + CASE -128 + CASE -200 + CASE -256 + CASE -512 + CASE -1024 + CASE -2047 + CASE -2048 + + PASS diff --git a/tests/helper.S b/tests/helper.S index 286a001..de1e7ca 100644 --- a/tests/helper.S +++ b/tests/helper.S @@ -20,7 +20,6 @@ .endm fail: - ebreak li a7, -1 li a0, 0 ecall