Compare commits

..

2 Commits

10 changed files with 641 additions and 45 deletions

149
Cargo.lock generated
View File

@@ -2,6 +2,56 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.100"
@@ -26,6 +76,52 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "goblin"
version = "0.10.4"
@@ -37,6 +133,12 @@ dependencies = [
"scroll",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "int-enum"
version = "1.2.0"
@@ -49,6 +151,12 @@ dependencies = [
"syn",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "libc"
version = "0.2.176"
@@ -82,6 +190,18 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "oneshot"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea"
[[package]]
name = "plain"
version = "0.2.3"
@@ -138,6 +258,12 @@ dependencies = [
"syn",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.111"
@@ -154,10 +280,12 @@ name = "trve"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"goblin",
"int-enum",
"memmap2",
"nix",
"oneshot",
]
[[package]]
@@ -166,8 +294,29 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

View File

@@ -5,7 +5,9 @@ edition = "2024"
[dependencies]
anyhow = "1.0.100"
clap = { version = "4.5.53", features = ["derive"] }
goblin = "0.10.4"
int-enum = "1.2.0"
memmap2 = "0.9.8"
nix = { version = "0.30.1", features = ["fs"] }
oneshot = "0.1.11"

View File

@@ -4,10 +4,14 @@
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text.
use std::{collections::HashSet, sync::mpsc};
use crate::{
consts::{Addr, RegId, RegValue},
core::commands::CoreCmd,
decode::Instruction,
exceptions::ExceptionType,
gdb::{self, DebugCommand, StopReason},
instructions::find_and_exec,
mem::MemConfig,
};
@@ -16,51 +20,152 @@ pub struct Core {
pub(crate) x_regs: [RegValue; 32],
pub(crate) pc: Addr,
pub(crate) mem: MemConfig,
command_stream: mpsc::Receiver<CoreCmd>,
}
pub mod commands;
impl Core {
pub fn new(mem: MemConfig) -> Self {
pub fn new(mem: MemConfig, command_stream: mpsc::Receiver<CoreCmd>) -> Self {
Self {
x_regs: [0; 32],
pc: 0,
mem,
command_stream,
}
}
pub fn run(&mut self) {
loop {
if let Ok(cmd) = self.command_stream.try_recv() {
match cmd {
CoreCmd::EnterDbgMode(dbg_stream) => {
let _ = self.debug_loop(dbg_stream);
}
};
}
if let Err(e) = self.step() {
self.throw_exception(e);
break;
}
}
}
pub fn run_waiting_for_cmd(&mut self) {
eprintln!("Waiting for any core command...");
if let Ok(cmd) = self.command_stream.recv() {
eprintln!("Recieved a command");
match cmd {
CoreCmd::EnterDbgMode(dbg_stream) => {
let _ = self.debug_loop(dbg_stream);
}
};
} else {
eprintln!("Error recieving command, starting anyway");
}
eprintln!("Command processed");
self.run();
}
fn debug_loop(&mut self, dbg_stream: mpsc::Receiver<gdb::DebugCommand>) -> anyhow::Result<()> {
let mut breakpoints = HashSet::new();
loop {
match dbg_stream.recv()? {
DebugCommand::GetRegs(sender) => sender.send(gdb::RegsResponse {
x_regs: self.x_regs.clone(),
pc: self.pc,
})?,
DebugCommand::ReadMem {
addr,
len,
responder,
} => {
let data = (0..len)
.map(|offset| self.mem.read_byte(addr + offset))
.collect();
responder.send(data)?;
}
DebugCommand::SetBreakpoint(addr) => {
breakpoints.insert(addr);
}
DebugCommand::RemoveBreakpoint(addr) => {
breakpoints.remove(&addr);
}
DebugCommand::Step(responder) => {
responder.send(match self.step() {
Ok(_) => gdb::StopReason::Step,
Err(e) => {
self.throw_exception(e);
gdb::StopReason::Exception(e)
}
})?;
}
DebugCommand::Continue(responder, stopper) => {
responder.send(self.continue_loop(&breakpoints, stopper))?;
}
DebugCommand::ExitDebugMode => {
eprintln!("exitdbgmode");
break Ok(());
}
};
}
}
fn continue_loop(
&mut self,
breakpoints: &HashSet<Addr>,
stopper: oneshot::Receiver<()>,
) -> StopReason {
loop {
if breakpoints.contains(&self.pc) {
return StopReason::Exception(ExceptionType::Breakpoint);
}
if let Ok(_) = stopper.try_recv() {
return StopReason::Interrupted;
}
if let Err(e) = self.step() {
self.throw_exception(e);
return StopReason::Exception(e);
}
}
}
pub(crate) fn step(&mut self) -> Result<(), ExceptionType> {
if !self.pc.is_multiple_of(4) {
self.throw_exception(ExceptionType::InstructionAddressMisaligned);
break;
}
let instr = match self.mem.read_word(self.pc) {
Ok(i) => i,
Err(e) => {
self.throw_exception(e.to_exception_instr());
break;
return Err(e.to_exception_instr());
}
};
if instr == 0 {
self.throw_exception(ExceptionType::IllegalInstruction);
break;
return Err(ExceptionType::IllegalInstruction);
}
if instr & 3 != 3 {
// Compressed instruction - (currently) unsupported
self.throw_exception(ExceptionType::IllegalInstruction);
break;
return Err(ExceptionType::IllegalInstruction);
}
let instr = Instruction(instr);
if let Err(e) = find_and_exec(instr, self) {
self.throw_exception(e);
eprintln!("instr: {:08x}", instr.0);
break;
}
return Err(e);
}
Ok(())
}
fn throw_exception(&mut self, exception_type: ExceptionType) {

7
src/core/commands.rs Normal file
View File

@@ -0,0 +1,7 @@
use std::sync::mpsc;
use crate::gdb;
pub enum CoreCmd {
EnterDbgMode(mpsc::Receiver<gdb::DebugCommand>),
}

View File

@@ -57,7 +57,9 @@ impl Instruction {
#[inline]
pub fn imm_s(self) -> DWord {
(self.0 as i32 as i64 >> (25 - 5) & (0x7f << 5)) as DWord | (self.0 >> 7 & 0b11111) as DWord
let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as DWord;
let imm_4_0 = (self.0 >> 7 & 0x1f) as DWord;
imm_11_5 | imm_4_0
}
#[inline]

View File

@@ -29,6 +29,7 @@ pub enum ExceptionType {
HardwareError = 19,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryExceptionType {
AddressMisaligned,
AccessFault,

View File

@@ -14,9 +14,9 @@ use goblin::{
program_header::PT_LOAD,
},
};
use trve::consts::Addr;
use trve::{consts::Addr, mem::RAM_START};
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<Addr> {
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8]) -> Result<Addr> {
let buf = fs::read(path)?;
match Object::parse(&buf)? {
@@ -36,11 +36,11 @@ pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<
for ph in elf.program_headers {
if ph.p_type == PT_LOAD {
let start = (ph.p_vaddr - ram_start) as usize;
let start = (ph.p_vaddr - RAM_START) as usize;
let end = start + ph.p_memsz as usize;
let file_end = start + ph.p_filesz as usize;
if end > ram_start as usize + ram.len() {
if end > RAM_START as usize + ram.len() {
bail!("Segment at 0x{:x} does not fit in RAM", ph.p_vaddr);
}
@@ -60,7 +60,7 @@ pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<
bail!("Program too large for RAM");
}
ram[..buf.len()].copy_from_slice(&buf);
Ok(ram_start)
Ok(RAM_START)
}
_ => bail!("Unsupported executable format"),
}

315
src/gdb.rs Normal file
View File

@@ -0,0 +1,315 @@
use std::{
io::{self, BufRead, ErrorKind, Write},
net::{TcpListener, TcpStream},
sync::mpsc,
};
use crate::{
consts::{Addr, RegValue},
core::commands::CoreCmd,
exceptions::{ExceptionType, MemoryExceptionType},
};
pub(crate) enum DebugCommand {
GetRegs(oneshot::Sender<RegsResponse>),
ReadMem {
addr: Addr,
len: u64,
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
},
Step(oneshot::Sender<StopReason>),
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
SetBreakpoint(Addr),
RemoveBreakpoint(Addr),
ExitDebugMode,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum StopReason {
Exception(ExceptionType),
Step,
Interrupted,
}
impl StopReason {
fn to_rsp(self) -> &'static str {
match self {
StopReason::Step => "S05",
StopReason::Exception(e) => {
use ExceptionType::*;
match e {
IllegalInstruction => "S04",
InstructionAddressMisaligned
| InstructionAccessFault
| InstructionPageFault
| LoadAddressMisaligned
| LoadAccessFault
| LoadPageFault
| StoreAmoAddressMisaligned
| StoreAmoAccessFault
| StoreAmoPageFault => "S0b",
_ => "S05",
}
}
StopReason::Interrupted => "S02",
}
}
}
pub(crate) struct RegsResponse {
pub x_regs: [RegValue; 32],
pub pc: Addr,
}
pub fn run_stub(cmd_sender: mpsc::Sender<CoreCmd>) {
std::thread::spawn(move || {
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
for stream_res in listener.incoming() {
if let Ok(stream) = stream_res {
let (dbg_tx, dbg_rx) = mpsc::channel();
stream
.set_nonblocking(true)
.expect("Couldnt set TCP stream to nonblocking");
cmd_sender
.send(CoreCmd::EnterDbgMode(dbg_rx))
.expect("couldnt ask core to enter debug mode");
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
}
}
});
}
fn handle_gdb_connection(
gdb_stream: TcpStream,
dbg_tx: mpsc::Sender<DebugCommand>,
) -> io::Result<()> {
eprintln!("gdb connected");
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
let mut writer = gdb_stream;
loop {
match read_rsp_packet(&mut reader) {
Ok(packet) => match handle_packet(
&packet[..packet.len() - 1],
&mut writer,
&dbg_tx,
&mut reader,
) {
Err(_) => {
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
break;
}
_ => {}
},
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
std::thread::yield_now();
}
Err(_) => {
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
break;
}
}
}
Ok(())
}
fn read_rsp_packet<R: BufRead>(reader: &mut R) -> io::Result<String> {
let mut buf = Vec::new();
// Wait for leading '$'
loop {
let mut byte = [0u8];
let n = reader.read(&mut byte)?;
if n == 0 {
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Disconnected"));
}
if byte[0] == b'$' {
break;
}
}
// Read until '#'
reader.read_until(b'#', &mut buf)?;
let mut checksum = [0u8; 2];
reader.read_exact(&mut checksum)?;
String::from_utf8(buf).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
}
fn handle_packet<W: Write, R: BufRead>(
packet: &str,
writer: &mut W,
dbg_tx: &mpsc::Sender<DebugCommand>,
reader: &mut R,
) -> io::Result<()> {
writer.write_all(b"+")?;
if packet.is_empty() {
send_packet("", writer)?;
return Ok(());
}
match &packet[0..1] {
"?" => {
send_packet("S05", writer)?;
}
"g" => {
let (regs_tx, regs_rx) = oneshot::channel();
dbg_tx.send(DebugCommand::GetRegs(regs_tx)).unwrap();
let regs = regs_rx.recv().unwrap();
let mut hex = String::with_capacity(32 * 8 * 2 + 8 * 2);
for &reg in &regs.x_regs {
hex.push_str(
&reg.to_le_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>(),
);
}
hex.push_str(
&regs
.pc
.to_le_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>(),
);
send_packet(&hex, writer)?;
}
"m" => {
if let Some((addr_str, len_str)) = packet[1..].split_once(',') {
if let (Ok(addr), Ok(len)) = (
u64::from_str_radix(addr_str, 16),
u64::from_str_radix(len_str, 16),
) {
let (responder, response) = oneshot::channel();
dbg_tx
.send(DebugCommand::ReadMem {
addr,
len,
responder,
})
.unwrap();
let response = response.recv().unwrap();
match response {
Ok(data) => {
let hex: String = data.iter().map(|b| format!("{b:02x}")).collect();
send_packet(&hex, writer)?;
}
Err(e) => send_packet(&format!("E.{e:?}"), writer)?,
};
} else {
send_packet("", writer)?;
}
} else {
send_packet("", writer)?;
}
}
"s" => {
let (responder, stop_reason_rx) = oneshot::channel();
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
let stop_reason = stop_reason_rx.recv().unwrap();
send_packet(&stop_reason.to_rsp(), writer)?;
}
"c" => {
let (responder, stop_reason_rx) = oneshot::channel();
let (stopper, stop_listener) = oneshot::channel();
dbg_tx
.send(DebugCommand::Continue(responder, stop_listener))
.unwrap();
loop {
let mut byte = [0u8];
match reader.read(&mut byte) {
Ok(0) => {
stopper.send(()).unwrap();
break;
}
Ok(1) if byte[0] == 0x03 => {
stopper.send(()).unwrap();
break;
}
Ok(_) => {}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(e) => {
return Err(e);
}
}
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
send_packet(&stop_reason.to_rsp(), writer)?;
return Ok(());
}
std::thread::yield_now();
}
let stop_reason = stop_reason_rx.recv().unwrap();
send_packet(stop_reason.to_rsp(), writer)?;
}
"Z" if packet.chars().nth(1) == Some('0') => {
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
if let (Ok(addr), Ok(size)) = (
u64::from_str_radix(addr_str, 16),
u64::from_str_radix(size_str, 16),
) {
if size != 4 {
send_packet("", writer)?;
return Ok(());
}
dbg_tx.send(DebugCommand::SetBreakpoint(addr)).unwrap();
send_packet("OK", writer)?;
return Ok(());
}
}
send_packet("", writer)?;
}
"z" if packet.chars().nth(1) == Some('0') => {
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
if let (Ok(addr), Ok(size)) = (
u64::from_str_radix(addr_str, 16),
u64::from_str_radix(size_str, 16),
) {
if size != 4 {
send_packet("", writer)?;
return Ok(());
}
dbg_tx.send(DebugCommand::RemoveBreakpoint(addr)).unwrap();
send_packet("OK", writer)?;
return Ok(());
}
}
send_packet("", writer)?;
}
_ => {
send_packet("", writer)?;
}
}
Ok(())
}
fn send_packet<W: Write>(packet: &str, writer: &mut W) -> io::Result<()> {
writer.write_all(b"$")?;
writer.write_all(packet.as_bytes())?;
writer.write_all(b"#")?;
let checksum = packet.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
write!(writer, "{checksum:02x}")?;
// eprintln!("successfully sent packet {packet:?}");
Ok(())
}

View File

@@ -2,5 +2,6 @@ pub mod consts;
pub mod core;
mod decode;
pub mod exceptions;
pub mod gdb;
mod instructions;
pub mod mem;

View File

@@ -4,33 +4,38 @@
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text.
use std::{env, sync::Arc, time::Duration};
use std::{env, path::PathBuf, sync::Arc, time::Duration};
use clap::Parser;
use trve::{
consts::{Addr, Byte, DWord, HWord, Word},
core::Core,
exceptions::MemoryExceptionType,
gdb,
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
};
use anyhow::{Result, bail};
use anyhow::Result;
use crate::basic_uart::BasicUart;
mod execload;
#[derive(Parser)]
struct Args {
executable: PathBuf,
#[arg(long)]
wait: bool,
}
fn main() -> Result<()> {
let args = Args::parse();
let mut ram = Ram::try_new(16 * 1024 * 1024)?;
let buf = ram.buf_mut();
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("USAGE: trve <ram_image>");
bail!("Wrong number of arguments");
}
let entry_point = execload::load(&args[1], buf, 0x8000_0000)?;
let entry_point = execload::load(args.executable, buf)?;
let mut mmio_root = MmioRoot::default();
mmio_root.insert(0, Arc::new(DbgOut));
@@ -44,9 +49,18 @@ fn main() -> Result<()> {
mmio_root,
};
let mut core = Core::new(mem_cfg);
let (cmd_sender, cmd_reciever) = std::sync::mpsc::channel();
gdb::run_stub(cmd_sender.clone());
let mut core = Core::new(mem_cfg, cmd_reciever);
core.reset(entry_point);
if args.wait {
core.run_waiting_for_cmd();
} else {
core.run();
}
Ok(())
}