Let emulator tell debugger to stop
This commit is contained in:
+177
-177
@@ -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(®_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(®_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;",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user