From 9f8e9ec380ad679fe714222345a46ebf77d063d6 Mon Sep 17 00:00:00 2001 From: taitep Date: Sat, 27 Dec 2025 11:48:36 +0100 Subject: Implement a GDB stub and fix another huge issue in S-type immediate decoding --- src/gdb.rs | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/gdb.rs (limited to 'src/gdb.rs') 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(()) +} -- cgit v1.2.3