debugger and fixes

This commit is contained in:
nora 2025-03-10 18:08:00 +01:00
parent de03390dab
commit 7ae26ddc85
9 changed files with 212 additions and 50 deletions

7
Cargo.lock generated
View file

@ -76,6 +76,12 @@ 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 = "owo-colors"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.1" version = "1.0.1"
@ -95,6 +101,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"libc", "libc",
"owo-colors",
"tempfile", "tempfile",
] ]

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
eyre = "0.6.12" eyre = "0.6.12"
libc = "0.2.170" libc = "0.2.170"
owo-colors = "4.2.0"
[profile.release] [profile.release]
debug = 1 debug = 1

View file

@ -1,6 +1,7 @@
use crate::inst::{AmoOp, Inst, InstCode, IsCompressed}; use crate::inst::{AmoOp, Inst, InstCode, IsCompressed};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
io::Write,
ops::{Index, IndexMut}, ops::{Index, IndexMut},
u32, u32,
}; };
@ -86,6 +87,8 @@ pub struct Emulator {
/// to make SC fail if it's not to this address. /// to make SC fail if it's not to this address.
pub reservation_set: Option<u32>, pub reservation_set: Option<u32>,
pub is_breaking: bool,
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<(), Status>>,
} }
@ -129,12 +132,20 @@ impl Reg {
impl Display for Reg { impl Display for Reg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0 == Self::SP.0 { let n = self.0;
write!(f, "sp") match n {
} else if self.0 == Self::RA.0 { 0 => write!(f, "zero"),
write!(f, "ra") 1 => write!(f, "ra"),
} else { 2 => write!(f, "sp"),
write!(f, "x{}", self.0) 3 => write!(f, "gp"),
4 => write!(f, "tp"),
5..=7 => write!(f, "t{}", n - 5),
8 => write!(f, "s0"),
9 => write!(f, "s1"),
10..=17 => write!(f, "a{}", n - 10),
18..=27 => write!(f, "s{}", n - 18 + 2),
28..=31 => write!(f, "t{}", n - 28 + 3),
_ => unreachable!("invalid register"),
} }
} }
} }
@ -164,6 +175,10 @@ impl Emulator {
let code = self.mem.load_u32(self.pc)?; let code = self.mem.load_u32(self.pc)?;
let (inst, was_compressed) = Inst::decode(code)?; let (inst, was_compressed) = Inst::decode(code)?;
if self.is_breaking {
self.debug_interactive();
}
if self.debug { if self.debug {
println!("executing 0x{:x} {inst:?}", self.pc); println!("executing 0x{:x} {inst:?}", self.pc);
} }
@ -314,7 +329,13 @@ 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(Status::Ebreak), Inst::Ebreak => {
if self.debug {
self.is_breaking = true;
} else {
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;
} }
@ -357,8 +378,7 @@ impl Emulator {
if self[src2] == 0 { if self[src2] == 0 {
self[dest] = self[src1]; self[dest] = self[src1];
} else { } else {
dbg!(self[src1], self[src2]); self[dest] = self[src1] % self[src2];
self[dest] = dbg!(self[src1] % self[src2]);
} }
} }
Inst::AmoW { Inst::AmoW {
@ -414,4 +434,129 @@ impl Emulator {
} }
Ok(()) Ok(())
} }
fn debug_interactive(&mut self) {
use owo_colors::OwoColorize;
loop {
print!("> ");
std::io::stdout().flush().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
match input {
"c" => {
self.is_breaking = false;
return;
}
"s" => {
return;
}
"p" => {
let format_value = |v: u32| {
if v == 0 {
format!("{:0>8x}", v.black())
} else {
format!("{:0>8x}", v)
}
};
let r = |i| format_value(self.xreg[i]);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"ra".red(),
r(1),
"sp".red(),
r(2),
"gp".red(),
r(3),
"tp".red(),
r(4)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"a0".green(),
r(10),
"a1".green(),
r(11),
"a2".green(),
r(12),
"a3".green(),
r(13)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"a4".green(),
r(14),
"a5".green(),
r(15),
"a6".green(),
r(16),
"a7".green(),
r(17)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"s0".cyan(),
r(8),
"s1".cyan(),
r(9),
"s2".cyan(),
r(18),
"s3".cyan(),
r(19)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"s4".cyan(),
r(20),
"s5".cyan(),
r(21),
"s6".cyan(),
r(22),
"s7".cyan(),
r(23)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"s8".cyan(),
r(24),
"s9".cyan(),
r(25),
"s10".cyan(),
r(26),
"s11".cyan(),
r(27)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"t0".yellow(),
r(5),
"t1".yellow(),
r(6),
"t2".yellow(),
r(7),
"t3".yellow(),
r(28)
);
println!(
"{}: {} | {}: {} | {}: {} | {}: {}",
"t4".yellow(),
r(29),
"t5".yellow(),
r(30),
"t6".yellow(),
r(31),
"pc".red(),
format_value(self.pc)
);
}
_ => println!(
"commands:
- ?: help
- p: print registers
- c: continue until next breakpoint
- s: step one instruction"
),
}
}
}
} }

View file

@ -602,7 +602,7 @@ impl Inst {
}, },
// C.LI -> addi \rd, zero, \imm // C.LI -> addi \rd, zero, \imm
0b010 => Inst::Addi { 0b010 => Inst::Addi {
imm: code.immediate_s(&[(2..=4, 0), (12..=12, 5)]), imm: code.immediate_s(&[(2..=6, 0), (12..=12, 5)]),
dest: code.rd(), dest: code.rd(),
src1: Reg::ZERO, src1: Reg::ZERO,
}, },

View file

@ -68,6 +68,8 @@ pub fn execute_linux_elf(
pc: start, pc: start,
reservation_set: None, reservation_set: None,
is_breaking: false,
debug, debug,
ecall_handler, ecall_handler,
}; };

View file

@ -11,5 +11,5 @@ init: init.c
init1: init1.rs init1: init1.rs
$(RUSTC) init1.rs $(RUSTC) init1.rs
x: x.s x: x.S
$(CC_STATIC) x.s -o x $(CC_STATIC) x.S -o x

View file

@ -1,4 +1,5 @@
use std::{ use std::{
fs::DirEntry,
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -25,50 +26,55 @@ fn check() -> eyre::Result<()> {
continue; continue;
} }
write!(std::io::stdout(), "test {name} ...")?; test_case(tmpdir.path(), &file, name, "rv32ima")?;
std::io::stdout().flush()?; test_case(tmpdir.path(), &file, name, "rv32imac")?;
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,
true,
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(()) Ok(())
} }
fn build(tmpdir: &Path, src: &Path) -> eyre::Result<PathBuf> { fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, 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 content =
std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?;
let status = rustv32i::execute_linux_elf(
&content,
true,
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("fail"))
}
} else {
Err(rustv32i::emu::Status::Trap("wrong syscall"))
}
}),
)
.wrap_err(format!("{name} failed"))?;
if let Status::Exit { code: 0 } = status {
writeln!(std::io::stdout(), "")?;
Ok(())
} else {
bail!("{name} returned an error: {status:?}");
}
}
fn build(tmpdir: &Path, src: &Path, march: &str) -> eyre::Result<PathBuf> {
let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension("")); let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension(""));
let mut cmd = std::process::Command::new("clang"); let mut cmd = std::process::Command::new("clang");
cmd.args([ cmd.args(["-target", "riscv32-unknown-none-elf", "-nostdlib"]);
"-target", cmd.arg(format!("-march={march}"));
"riscv32-unknown-none-elf",
"-nostdlib",
"-march=rv32ima",
]);
cmd.arg(src); cmd.arg(src);
cmd.arg("-o"); cmd.arg("-o");
cmd.arg(&out_path); cmd.arg(&out_path);

View file

@ -43,14 +43,14 @@ branch7:
# Test link registers being set correctly: # Test link registers being set correctly:
auipc t1, 0 auipc t1, 0
jal t0, link2 jal t0, link2
j fail jal t5, fail # force uncompressed
link2: link2:
addi t1, t1, 8 # the instruction following the jump addi t1, t1, 8 # the instruction following the jump
bne t1, t0, fail bne t1, t0, fail
auipc t1, 0 auipc t1, 0
jalr t0, 12(t1) # 12 is the three instructions, so to the addi jalr t0, 12(t1) # 12 is the three instructions, so to the addi
j fail jal t5, fail # force uncompressed
addi t1, t1, 8 # the instruction following the jump addi t1, t1, 8 # the instruction following the jump
bne t0, t1, fail bne t0, t1, fail

View file

@ -20,6 +20,7 @@
.endm .endm
fail: fail:
ebreak
li a7, -1 li a7, -1
li a0, 0 li a0, 0
ecall ecall