// Copyright (c) 2025 taitep // SPDX-License-Identifier: BSD-2-Clause // // 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::{ 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, } pub struct DebugStream(pub(crate) mpsc::Receiver); #[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(DebugStream(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(()) }