diff --git a/Cargo.lock b/Cargo.lock index c50db93..1dfb25c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml index f76ab77..35ad73b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/core.rs b/src/core.rs index acc52f2..dde8fd7 100644 --- a/src/core.rs +++ b/src/core.rs @@ -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,53 +20,154 @@ pub struct Core { pub(crate) x_regs: [RegValue; 32], pub(crate) pc: Addr, pub(crate) mem: MemConfig, + command_stream: mpsc::Receiver, } +pub mod commands; + impl Core { - pub fn new(mem: MemConfig) -> Self { + pub fn new(mem: MemConfig, command_stream: mpsc::Receiver) -> Self { Self { x_regs: [0; 32], pc: 0, mem, + command_stream, } } pub fn run(&mut self) { loop { - if !self.pc.is_multiple_of(4) { - self.throw_exception(ExceptionType::InstructionAddressMisaligned); - break; + if let Ok(cmd) = self.command_stream.try_recv() { + match cmd { + CoreCmd::EnterDbgMode(dbg_stream) => { + let _ = self.debug_loop(dbg_stream); + } + }; } - let instr = match self.mem.read_word(self.pc) { - Ok(i) => i, - Err(e) => { - self.throw_exception(e.to_exception_instr()); - break; - } - }; - - if instr == 0 { - self.throw_exception(ExceptionType::IllegalInstruction); - break; - } - - if instr & 3 != 3 { - // Compressed instruction - (currently) unsupported - self.throw_exception(ExceptionType::IllegalInstruction); - break; - } - - let instr = Instruction(instr); - - if let Err(e) = find_and_exec(instr, self) { + if let Err(e) = self.step() { self.throw_exception(e); - eprintln!("instr: {:08x}", instr.0); 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) -> 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, + 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); + } + + let instr = match self.mem.read_word(self.pc) { + Ok(i) => i, + Err(e) => { + return Err(e.to_exception_instr()); + } + }; + + if instr == 0 { + return Err(ExceptionType::IllegalInstruction); + } + + if instr & 3 != 3 { + // Compressed instruction - (currently) unsupported + return Err(ExceptionType::IllegalInstruction); + } + + let instr = Instruction(instr); + + if let Err(e) = find_and_exec(instr, self) { + eprintln!("instr: {:08x}", instr.0); + return Err(e); + } + + Ok(()) + } + fn throw_exception(&mut self, exception_type: ExceptionType) { eprintln!("Exception: {exception_type:?}"); dbg!(self.pc, self.x_regs); diff --git a/src/core/commands.rs b/src/core/commands.rs new file mode 100644 index 0000000..93bdd1e --- /dev/null +++ b/src/core/commands.rs @@ -0,0 +1,7 @@ +use std::sync::mpsc; + +use crate::gdb; + +pub enum CoreCmd { + EnterDbgMode(mpsc::Receiver), +} diff --git a/src/decode.rs b/src/decode.rs index 4258a6f..d2e8a80 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -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] diff --git a/src/exceptions.rs b/src/exceptions.rs index 7f00e5c..3c1d4bd 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -29,6 +29,7 @@ pub enum ExceptionType { HardwareError = 19, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MemoryExceptionType { AddressMisaligned, AccessFault, diff --git a/src/gdb.rs b/src/gdb.rs new file mode 100644 index 0000000..17e2f71 --- /dev/null +++ b/src/gdb.rs @@ -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), + ReadMem { + addr: Addr, + len: u64, + responder: oneshot::Sender, MemoryExceptionType>>, + }, + Step(oneshot::Sender), + Continue(oneshot::Sender, 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) { + 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, +) -> 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(reader: &mut R) -> io::Result { + 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( + packet: &str, + writer: &mut W, + dbg_tx: &mpsc::Sender, + 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 ® in ®s.x_regs { + hex.push_str( + ®.to_le_bytes() + .iter() + .map(|b| format!("{b:02x}")) + .collect::(), + ); + } + hex.push_str( + ®s + .pc + .to_le_bytes() + .iter() + .map(|b| format!("{b:02x}")) + .collect::(), + ); + 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(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(()) +} diff --git a/src/lib.rs b/src/lib.rs index 7a9ac37..6eda620 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,5 +2,6 @@ pub mod consts; pub mod core; mod decode; pub mod exceptions; +pub mod gdb; mod instructions; pub mod mem; diff --git a/src/main.rs b/src/main.rs index 03072f6..274ef02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 = env::args().collect(); - - if args.len() != 2 { - eprintln!("USAGE: trve "); - bail!("Wrong number of arguments"); - } - - let entry_point = execload::load(&args[1], buf)?; + 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); - core.run(); + + if args.wait { + core.run_waiting_for_cmd(); + } else { + core.run(); + } Ok(()) }