use anyhow::{bail, Result}; use registers::REGISTERS; use request::{Request, RequestKind, RequestSource}; use response::Response; use std::{ sync::{Arc, Mutex}, thread, }; use tokio::{ io::{AsyncWriteExt as _, BufReader}, net::{TcpListener, TcpStream}, pin, select, sync::{mpsc, oneshot}, }; use tracing::{debug, enabled, error, info, Level}; use crate::emulator::{ DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId, VBWatchpointType, }; mod registers; mod request; mod response; pub struct GdbServer { sim_id: SimId, client: EmulatorClient, status: Arc>, killer: Option>, } impl GdbServer { pub fn new(sim_id: SimId, client: EmulatorClient) -> Self { Self { sim_id, client, status: Arc::new(Mutex::new(GdbServerStatus::Stopped)), killer: None, } } pub fn status(&self) -> GdbServerStatus { self.status.lock().unwrap().clone() } pub fn start(&mut self, port: u16) { *self.status.lock().unwrap() = GdbServerStatus::Connecting; let sim_id = self.sim_id; let client = self.client.clone(); let status = self.status.clone(); let (tx, rx) = oneshot::channel(); self.killer = Some(tx); thread::spawn(move || { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async move { select! { _ = run_server(sim_id, client.clone(), port, &status) => {} _ = rx => { client.send_command(EmulatorCommand::StopDebugging(sim_id)); *status.lock().unwrap() = GdbServerStatus::Stopped; } } }) }); } pub fn stop(&mut self) { if let Some(killer) = self.killer.take() { let _ = killer.send(()); } } } impl Drop for GdbServer { fn drop(&mut self) { self.stop(); } } async fn run_server( sim_id: SimId, client: EmulatorClient, port: u16, status: &Mutex, ) { let (debug_sink, mut debug_source) = mpsc::unbounded_channel(); client.send_command(EmulatorCommand::StartDebugging(sim_id, debug_sink)); info!("Connecting to debugger on port {port}..."); let connect_future = try_connect(port, status); pin!(connect_future); let stream = loop { select! { stream = &mut connect_future => { if let Some(stream) = stream { break stream; } else { return; } } event = debug_source.recv() => { if event.is_none() { // The sim has stopped (or was never started) *status.lock().unwrap() = GdbServerStatus::Stopped; return; } } } }; info!("Connected!"); let mut connection = GdbConnection::new(sim_id, client); match connection.run(stream, debug_source).await { Ok(()) => { info!("Finished debugging."); *status.lock().unwrap() = GdbServerStatus::Stopped; } Err(error) => { error!(%error, "Error from debugger."); *status.lock().unwrap() = GdbServerStatus::Error(error.to_string()); } } } async fn try_connect(port: u16, status: &Mutex) -> Option { *status.lock().unwrap() = GdbServerStatus::Connecting; let listener = match TcpListener::bind(("127.0.0.1", port)).await { Ok(l) => l, Err(err) => { error!(%err, "Could not open port."); *status.lock().unwrap() = GdbServerStatus::Error(err.to_string()); return None; } }; match listener.accept().await { Ok((stream, _)) => { *status.lock().unwrap() = GdbServerStatus::Running; Some(stream) } Err(err) => { error!(%err, "Could not connect to debugger."); *status.lock().unwrap() = GdbServerStatus::Error(err.to_string()); None } } } #[derive(Clone)] pub enum GdbServerStatus { Stopped, Connecting, Running, Error(String), } impl GdbServerStatus { pub fn running(&self) -> bool { matches!(self, Self::Connecting | Self::Running) } } struct GdbConnection { sim_id: SimId, client: EmulatorClient, ack_messages: bool, stop_reason: Option, response_buf: Option>, memory_buf: Option>, } impl GdbConnection { fn new(sim_id: SimId, client: EmulatorClient) -> Self { Self { sim_id, client, ack_messages: true, stop_reason: None, response_buf: None, memory_buf: None, } } async fn run( &mut self, stream: TcpStream, mut debug_source: mpsc::UnboundedReceiver, ) -> Result<()> { let (rx, mut tx) = stream.into_split(); let mut request_source = RequestSource::new(BufReader::new(rx)); loop { let response = select! { maybe_event = debug_source.recv() => { let Some(event) = maybe_event else { // debugger has stopped running break; }; self.handle_event(event) } maybe_request = request_source.recv() => { let req = maybe_request?; self.handle_request(req)? } }; if let Some(res) = response { let buffer = res.finish(); if enabled!(Level::DEBUG) { match std::str::from_utf8(&buffer) { Ok(text) => debug!("response: {text}"), Err(_) => debug!("response: {buffer:02x?}"), } } tx.write_all(&buffer).await?; self.response_buf = Some(buffer); tx.flush().await?; } } Ok(()) } fn handle_event(&mut self, event: DebugEvent) -> Option { let res = match event { DebugEvent::Stopped(reason) => { if self.stop_reason.is_some_and(|r| r == reason) { return None; } self.stop_reason = Some(reason); self.response() .write_str(&debug_stop_reason_string(self.stop_reason)) } }; Some(res) } fn handle_request(&mut self, mut req: Request<'_>) -> Result> { debug!("received {:02x?}", req); if req.kind == RequestKind::Signal { self.client .send_command(EmulatorCommand::DebugInterrupt(self.sim_id)); return Ok(None); // we'll send a message when the emulator reports it has stopped } let res = if req.match_str("QStartNoAckMode") { let res = self.response().write_str("OK"); self.ack_messages = false; res } else if req.match_str("qSupported:") { self.response() .write_str("multiprocess+;swbreak+;vContSupported+;PacketSize=10000;SupportedWatchpointTypes=x86_64,aarch64-bas,aarch64-mask") } else if req .match_some_str([ "QThreadSuffixSupported", "QListThreadsInStopReply", "QEnableErrorStrings", ]) .is_some() { self.response().write_str("OK") } else if req.match_str("qHostInfo") { self.response().write_str(&format!( "triple:{};endian:little;ptrsize:4;", hex::encode("v810-unknown-vb") )) } else if req.match_str("qProcessInfo") { self.response().write_str(&format!( "pid:1;triple:{};endian:little;ptrsize:4;", hex::encode("v810-unknown-vb") )) } else if req.match_str("qRegisterInfo") { let mut get_reg_info = || { let register = req.match_hex::()?; REGISTERS.get(register) }; if let Some(reg_info) = get_reg_info() { self.response().write_str(®_info.to_description()) } else { self.response() } } else if req.match_str("vCont?") { self.response().write_str("vCont;c;s;") } else if req.match_str("qC") { // The v810 has no threads, so report that the "current thread" is 1. self.response().write_str("QCp1.t1") } else if req.match_str("qfThreadInfo") { self.response().write_str("mp1.t1") } else if req.match_str("qsThreadInfo") { self.response().write_str("l") } else if req.match_str("k") { bail!("debug process was killed"); } else if req.match_str("?") { self.response() .write_str(&debug_stop_reason_string(self.stop_reason)) } else if req.match_some_str(["c", "vCont;c:"]).is_some() { self.client .send_command(EmulatorCommand::DebugContinue(self.sim_id)); self.stop_reason = None; // Don't send a response until we hit a breakpoint or get interrupted return Ok(None); } else if req.match_some_str(["s", "vCont;s:"]).is_some() { self.client .send_command(EmulatorCommand::DebugStep(self.sim_id)); self.stop_reason = None; // Don't send a response until we hit a breakpoint or get interrupted return Ok(None); } else if req.match_str("p") { let mut read_register = || { let register_index = req.match_hex::()?; let register = REGISTERS.get(register_index)?.to_vb_register(); let (tx, rx) = ::oneshot::channel(); self.client .send_command(EmulatorCommand::ReadRegister(self.sim_id, register, tx)); rx.recv().ok() }; if let Some(value) = read_register() { self.response().write_hex(value) } else { self.response() } } else if let Some(op) = req.match_some_str(["m", "x"]) { let mut read_memory = || { let start = req.match_hex::()?; if !req.match_str(",") { return None; }; let length = req.match_hex::()?; let mut buf = self.memory_buf.take().unwrap_or_default(); buf.clear(); // The v810 has a 32-bit address space. // Addresses wrap within that space, but we don't need to implement that for 64-bit addresses. // Just refuse to return any info for addresses above 0xffffffff. let Ok(start) = u32::try_from(start) else { return Some(buf); }; let length = length.min((u32::MAX - start) as usize + 1); if length == 0 { return Some(buf); } let (tx, rx) = ::oneshot::channel(); self.client.send_command(EmulatorCommand::ReadMemory( self.sim_id, start, length, buf, tx, )); rx.recv().ok() }; if let Some(memory) = read_memory() { let mut res = self.response(); if memory.is_empty() { res = res.write_str("OK"); } else if op == "m" { // send the hex-encoded byte stream for byte in &memory { res = res.write_hex(*byte); } } else { // send the raw byte stream for byte in &memory { res = res.write_byte(*byte); } } self.memory_buf = Some(memory); res } else { self.response() } } else if req.match_str("Z") { let mut parse_request = || { let type_ = req.match_hex::()?; if !req.match_str(",") { return None; } let address = req.match_hex()?; if type_ == 0 || type_ == 1 { return Some(EmulatorCommand::AddBreakpoint(self.sim_id, address)); } if !req.match_str(",") { return None; } let length = req.match_hex()?; let watch = match type_ { 2 => VBWatchpointType::Write, 3 => VBWatchpointType::Read, 4 => VBWatchpointType::Access, _ => return None, }; Some(EmulatorCommand::AddWatchpoint( self.sim_id, address, length, watch, )) }; if let Some(command) = parse_request() { self.client.send_command(command); self.response().write_str("OK") } else { self.response() } } else if req.match_str("z") { let mut parse_request = || { let type_ = req.match_hex::()?; if !req.match_str(",") { return None; } let address = req.match_hex()?; if type_ == 0 || type_ == 1 { return Some(EmulatorCommand::RemoveBreakpoint(self.sim_id, address)); } if !req.match_str(",") { return None; } let length = req.match_hex()?; let watch = match type_ { 2 => VBWatchpointType::Write, 3 => VBWatchpointType::Read, 4 => VBWatchpointType::Access, _ => return None, }; Some(EmulatorCommand::RemoveWatchpoint( self.sim_id, address, length, watch, )) }; if let Some(command) = parse_request() { self.client.send_command(command); self.response().write_str("OK") } else { self.response() } } else { // unrecognized command self.response() }; Ok(Some(res)) } fn response(&mut self) -> Response { Response::new( self.response_buf.take().unwrap_or_default(), self.ack_messages, ) } } impl Drop for GdbConnection { fn drop(&mut self) { self.client .send_command(EmulatorCommand::StopDebugging(self.sim_id)); } } fn debug_stop_reason_string(reason: Option) -> String { let mut result = String::new(); result += if reason.is_some() { "T05;" } else { "T00;" }; if let Some(DebugStopReason::Breakpoint) = reason { result += "swbreak;"; } if let Some(DebugStopReason::Watchpoint(watch, address)) = reason { result += match watch { VBWatchpointType::Write => "watch:", VBWatchpointType::Read => "rwatch:", VBWatchpointType::Access => "awatch:", }; result += &format!("{address:08x};"); } result += "thread:p1.t1;threads:p1.t1;"; if let Some(reason) = reason { result += "reason:"; result += match reason { DebugStopReason::Trace => "trace;", DebugStopReason::Breakpoint => "breakpoint;", DebugStopReason::Watchpoint(_, _) => "watchpoint;", DebugStopReason::Trapped => "trap;", }; } if let Some(DebugStopReason::Watchpoint(_, address)) = reason { result += "description:"; result += &hex::encode(address.to_string()); result += ";"; } result }