Let emulator tell debugger to stop

This commit is contained in:
2025-01-04 01:04:21 -05:00
parent a5b5f8e80f
commit 11df670ff4
4 changed files with 392 additions and 246 deletions
+177 -177
View File
@@ -1,22 +1,19 @@
use anyhow::{bail, Result};
use registers::REGISTERS;
use request::{Request, RequestKind};
use request::{Request, RequestKind, RequestSource};
use response::Response;
use std::{
sync::{Arc, Mutex},
thread,
};
use tokio::{
io::{AsyncWriteExt as _, BufReader, BufWriter},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpListener, TcpStream,
},
io::{AsyncWriteExt as _, BufReader},
net::{TcpListener, TcpStream},
select,
sync::oneshot,
sync::{mpsc, oneshot},
};
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
use crate::emulator::{DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId};
mod registers;
mod request;
@@ -81,8 +78,8 @@ async fn run_server(
let Some(stream) = try_connect(port, status).await else {
return;
};
let connection = GdbConnection::new(sim_id, client, stream);
match connection.run().await {
let mut connection = GdbConnection::new(sim_id, client);
match connection.run(stream).await {
Ok(()) => {
*status.lock().unwrap() = GdbServerStatus::Stopped;
}
@@ -130,191 +127,191 @@ impl GdbServerStatus {
struct GdbConnection {
sim_id: SimId,
client: EmulatorClient,
stream_in: BufReader<OwnedReadHalf>,
stream_out: BufWriter<OwnedWriteHalf>,
ack_messages: bool,
request_buf: Vec<u8>,
stop_reason: Option<DebugStopReason>,
response_buf: Option<Vec<u8>>,
memory_buf: Option<Vec<u8>>,
}
impl GdbConnection {
fn new(sim_id: SimId, client: EmulatorClient, stream: TcpStream) -> Self {
let (rx, tx) = stream.into_split();
fn new(sim_id: SimId, client: EmulatorClient) -> Self {
Self {
sim_id,
client,
stream_in: BufReader::new(rx),
stream_out: BufWriter::new(tx),
ack_messages: true,
request_buf: vec![],
stop_reason: None,
response_buf: None,
memory_buf: None,
}
}
async fn run(mut self) -> Result<()> {
println!("Connected for {}", self.sim_id);
self.client.send_command(EmulatorCommand::Pause);
async fn run(&mut self, stream: TcpStream) -> Result<()> {
let (debug_sink, mut debug_source) = mpsc::unbounded_channel();
let (rx, mut tx) = stream.into_split();
let mut request_source = RequestSource::new(BufReader::new(rx));
self.client
.send_command(EmulatorCommand::StartDebugging(self.sim_id, debug_sink));
loop {
let mut req = Request::read(&mut self.stream_in, &mut self.request_buf).await?;
println!("received {:02x?}", req);
if req.kind == RequestKind::Signal {
self.client.send_command(EmulatorCommand::Pause);
let res = self
.response()
.write_str("T05;thread:p1.t1;threads:p1.t1;reason:trap;");
self.send(res).await?;
continue;
}
if req.match_str("QStartNoAckMode") {
let res = self.response().write_str("OK");
self.send(res).await?;
self.ack_messages = false;
} else if req.match_str("qSupported:") {
let res = self
.response()
.write_str("multiprocess+;swbreak+;vContSupported+;PacketSize=10000");
self.send(res).await?;
} else if req.match_str("QThreadSuffixSupported")
|| req.match_str("QListThreadsInStopReply")
|| req.match_str("QEnableErrorStrings")
{
let res = self.response().write_str("OK");
self.send(res).await?;
} else if req.match_str("qHostInfo") {
let res = self.response().write_str(&format!(
"triple:{};endian:little;ptrsize:4;",
hex::encode("v810-unknown-vb")
));
self.send(res).await?;
} else if req.match_str("qProcessInfo") {
let res = self.response().write_str(&format!(
"pid:1;triple:{};endian:little;ptrsize:4;",
hex::encode("v810-unknown-vb")
));
self.send(res).await?;
} else if req.match_str("qRegisterInfo") {
let mut get_reg_info = || {
let register = req.match_hex::<usize>()?;
REGISTERS.get(register)
};
let Some(reg_info) = get_reg_info() else {
self.send_empty().await?;
continue;
};
let res = self.response().write_str(&reg_info.to_description());
self.send(res).await?;
} else if req.match_str("vCont?") {
let res = self.response().write_str("vCont;c;");
self.send(res).await?;
} else if req.match_str("qC") {
// The v810 has no threads, so report that the "current thread" is 1.
let res = self.response().write_str("QCp1.t1");
self.send(res).await?;
} else if req.match_str("qfThreadInfo") {
let res = self.response().write_str("mp1.t1");
self.send(res).await?;
} else if req.match_str("qsThreadInfo") {
let res = self.response().write_str("l");
self.send(res).await?;
} else if req.match_str("k") {
bail!("debug process was killed");
} else if req.match_str("?") {
let res = self.response().write_str("T00;thread:p1.t1;threads:p1.t1;");
self.send(res).await?;
} else if req.match_str("c") || req.match_str("vCont;c:") {
self.client.send_command(EmulatorCommand::Resume);
// Don't send a response until we hit a breakpoint or get interrupted
} else if req.match_str("p") {
let mut read_register = || {
let register_index = req.match_hex::<usize>()?;
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()
};
let Some(value) = read_register() else {
self.send_empty().await?;
continue;
};
let res = self.response().write_hex(value);
self.send(res).await?;
} else if req.match_str("m") {
let mut read_memory = || {
let start = req.match_hex::<u32>()?;
if !req.match_str(",") {
return None;
let response = select! {
maybe_event = debug_source.recv() => {
let Some(event) = maybe_event else {
// debugger has stopped running
break;
};
let size = req.match_hex::<u32>()?;
let mut buf = self.memory_buf.take().unwrap_or_default();
buf.clear();
let (tx, rx) = ::oneshot::channel();
self.client.send_command(EmulatorCommand::ReadMemory(
self.sim_id,
start..(start + size),
buf,
tx,
));
rx.recv().ok()
};
let Some(memory) = read_memory() else {
self.send_empty().await?;
continue;
};
let mut res = self.response();
for byte in &memory {
res = res.write_hex(*byte);
self.handle_event(event)
}
self.memory_buf = Some(memory);
self.send(res).await?;
} else if req.match_str("x") {
let mut read_memory = || {
let start = req.match_hex::<u32>()?;
if !req.match_str(",") {
return None;
};
let size = req.match_hex::<u32>()?;
let mut buf = self.memory_buf.take().unwrap_or_default();
buf.clear();
let (tx, rx) = ::oneshot::channel();
self.client.send_command(EmulatorCommand::ReadMemory(
self.sim_id,
start..(start + size),
buf,
tx,
));
rx.recv().ok()
};
let Some(memory) = read_memory() else {
self.send_empty().await?;
continue;
maybe_request = request_source.recv() => {
let req = maybe_request?;
self.handle_request(req)?
}
};
if let Some(res) = response {
let buffer = res.finish();
match std::str::from_utf8(&buffer) {
Ok(text) => println!("response: {text}"),
Err(_) => println!("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<Response> {
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<Option<Response>> {
println!("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")
} 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::<usize>()?;
REGISTERS.get(register)
};
if let Some(reg_info) = get_reg_info() {
self.response().write_str(&reg_info.to_description())
} else {
self.response()
}
} else if req.match_str("vCont?") {
self.response().write_str("vCont;c;")
} 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_str("p") {
let mut read_register = || {
let register_index = req.match_hex::<usize>()?;
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::<u32>()?;
if !req.match_str(",") {
return None;
};
let size = req.match_hex::<u32>()?;
let mut buf = self.memory_buf.take().unwrap_or_default();
buf.clear();
let (tx, rx) = ::oneshot::channel();
self.client.send_command(EmulatorCommand::ReadMemory(
self.sim_id,
start..(start + size),
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");
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);
self.send(res).await?;
res
} else {
// unrecognized command
self.send_empty().await?;
self.response()
}
}
}
async fn send_empty(&mut self) -> std::io::Result<()> {
let res = self.response();
self.send(res).await
} else {
// unrecognized command
self.response()
};
Ok(Some(res))
}
fn response(&mut self) -> Response {
@@ -323,15 +320,18 @@ impl GdbConnection {
self.ack_messages,
)
}
}
async fn send(&mut self, res: Response) -> std::io::Result<()> {
let buffer = res.finish();
match std::str::from_utf8(&buffer) {
Ok(text) => println!("response: {text}"),
Err(_) => println!("response: {buffer:02x?}"),
}
self.stream_out.write_all(&buffer).await?;
self.response_buf = Some(buffer);
self.stream_out.flush().await
impl Drop for GdbConnection {
fn drop(&mut self) {
self.client
.send_command(EmulatorCommand::StopDebugging(self.sim_id));
}
}
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
match reason {
Some(DebugStopReason::Trapped) => "T05;thread:p1.t1;threads:p1.t1;reason:trap;",
None => "T00;thread:p1.t1;threads:p1.t1;",
}
}