diff --git a/src/gdbserver.rs b/src/gdbserver.rs index 4f0a78d..97171e0 100644 --- a/src/gdbserver.rs +++ b/src/gdbserver.rs @@ -1,10 +1,12 @@ use anyhow::{bail, Result}; +use request::{Request, RequestKind}; +use response::Response; use std::{ sync::{Arc, Mutex}, thread, }; use tokio::{ - io::{AsyncReadExt, AsyncWriteExt as _, BufReader, BufWriter}, + io::{AsyncWriteExt as _, BufReader, BufWriter}, net::{ tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpListener, TcpStream, @@ -15,6 +17,9 @@ use tokio::{ use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; +mod request; +mod response; + pub struct GdbServer { sim_id: SimId, client: EmulatorClient, @@ -126,6 +131,8 @@ struct GdbConnection { stream_in: BufReader, stream_out: BufWriter, ack_messages: bool, + request_buf: Vec, + response_buf: Option>, } impl GdbConnection { @@ -137,208 +144,94 @@ impl GdbConnection { stream_in: BufReader::new(rx), stream_out: BufWriter::new(tx), ack_messages: true, + request_buf: vec![], + response_buf: None, } } async fn run(mut self) -> Result<()> { println!("Connected for {}", self.sim_id); self.client.send_command(EmulatorCommand::Pause); loop { - let message = self.read_message().await?; - println!("received {:?}", message); + let mut req = Request::read(&mut self.stream_in, &mut self.request_buf).await?; + println!("received {:?}", req); - //let mut res = ResponseWriter::new(&mut self.stream_out); - //res.init(self.ack_messages).await?; + 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; + } - let body = match &message { - Message::String(str) => str.as_str(), - Message::Signal => { - self.client.send_command(EmulatorCommand::Pause); - let mut res = self.respond().await?; - res.write_str("T05;thread:p1.t1;threads:p1.t1;reason:trap;") - .await?; - res.send().await?; - continue; - } - }; - - if body == "QStartNoAckMode" { - let mut res = ResponseWriter::new(&mut self.stream_out); - res.init(self.ack_messages).await?; + if req.match_str("QStartNoAckMode") { + let res = self.response().write_str("OK"); + self.send(res).await?; self.ack_messages = false; - res.send_ok().await?; - } else if body.starts_with("qSupported:") { - let mut res = self.respond().await?; - res.write_str("multiprocess+;swbreak+;vContSupported+") - .await?; - res.send().await?; - } else if body == "QThreadSuffixSupported" - || body == "QListThreadsInStopReply" - || body == "QEnableErrorStrings" + } else if req.match_str("qSupported:") { + let res = self + .response() + .write_str("multiprocess+;swbreak+;vContSupported+"); + self.send(res).await?; + } else if req.match_str("QThreadSuffixSupported") + || req.match_str("QListThreadsInStopReply") + || req.match_str("QEnableErrorStrings") { - let res = self.respond().await?; - res.send_ok().await?; - } else if body == "qHostInfo" { - let mut res = self.respond().await?; - res.write_str(&format!( + 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") - )) - .await?; - res.send().await?; - } else if body == "qProcessInfo" { - let mut res = self.respond().await?; - res.write_str(&format!( + )); + 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") - )) - .await?; - res.send().await?; - } else if body == "vCont?" { - let mut res = self.respond().await?; - res.write_str("vCont;c;").await?; - res.send().await?; - } else if body == "qC" { + )); + 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 mut res = self.respond().await?; - res.write_str("QCp1.t1").await?; - res.send().await?; - } else if body == "qfThreadInfo" { - let mut res = self.respond().await?; - res.write_str("mp1.t1").await?; - res.send().await?; - } else if body == "qsThreadInfo" { - let mut res = self.respond().await?; - res.write_str("l").await?; - res.send().await?; - } else if body == "k" { + 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 body == "?" { - let mut res = self.respond().await?; - res.write_str("T00;thread:p1.t1;threads:p1.t1;").await?; - res.send().await?; - } else if body == "c" || body.starts_with("vCont;c:") { - // Continue running the game until we're interrupted again + } 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); - continue; + // Don't send a response until we hit a breakpoint or get interrupted } else { // unrecognized command - let res = self.respond().await?; - res.send().await?; + let res = self.response(); + self.send(res).await?; } } } - async fn read_message(&mut self) -> Result { - let mut char = self.read_byte().await?; - while char == b'+' { - // just ignore positive acks - char = self.read_byte().await?; - } - if char == b'-' { - bail!("no support for negative acks"); - } - if char == 0x03 { - // This is how the client "cancels an in-flight request" - return Ok(Message::Signal); - } - if char != b'$' { - // Messages are supposed to start with a dollar sign - bail!("malformed message"); - } - - // now read the body - let mut checksum = 0u8; - let mut body = vec![]; - char = self.read_byte().await?; - while char != b'#' { - if char == b'}' { - // escape character - checksum = checksum.wrapping_add(char); - char = self.read_byte().await?; - checksum = checksum.wrapping_add(char); - body.push(char ^ 0x20); - } else { - checksum = checksum.wrapping_add(char); - body.push(char); - } - char = self.read_byte().await?; - } - - let mut checksum_bytes = [b'0'; 2]; - self.stream_in.read_exact(&mut checksum_bytes).await?; - let checksum_str = std::str::from_utf8(&checksum_bytes)?; - let real_checksum = u8::from_str_radix(checksum_str, 16)?; - if checksum != real_checksum { - bail!("invalid checksum"); - } - - let string = String::from_utf8(body)?; - Ok(Message::String(string)) + fn response(&mut self) -> Response { + Response::new( + self.response_buf.take().unwrap_or_default(), + self.ack_messages, + ) } - async fn read_byte(&mut self) -> std::io::Result { - self.stream_in.read_u8().await - } - - async fn respond(&mut self) -> std::io::Result> { - let mut res = ResponseWriter::new(&mut self.stream_out); - res.init(self.ack_messages).await?; - Ok(res) + async fn send(&mut self, res: Response) -> std::io::Result<()> { + let buffer = res.finish(); + println!("{:?}", std::str::from_utf8(&buffer)); + self.stream_out.write_all(&buffer).await?; + self.response_buf = Some(buffer); + self.stream_out.flush().await } } - -struct ResponseWriter<'a> { - inner: &'a mut BufWriter, - checksum: u8, -} -impl<'a> ResponseWriter<'a> { - fn new(inner: &'a mut BufWriter) -> Self { - Self { inner, checksum: 0 } - } - - async fn init(&mut self, ack: bool) -> std::io::Result<()> { - if ack { - self.inner.write_u8(b'+').await?; - } - self.inner.write_u8(b'$').await - } - - async fn write_str(&mut self, str: &str) -> std::io::Result<()> { - for byte in str.bytes() { - self.checksum = self.checksum.wrapping_add(byte); - } - self.inner.write_all(str.as_bytes()).await - } - - async fn write_hex_u8(&mut self, value: u8) -> std::io::Result<()> { - for digit in [(value >> 4), (value & 0xf)] { - let char = if digit > 9 { - b'a' + digit - 10 - } else { - b'0' + digit - }; - self.checksum = self.checksum.wrapping_add(char); - self.inner.write_u8(char).await?; - } - Ok(()) - } - - async fn send_ok(mut self) -> std::io::Result<()> { - self.write_str("OK").await?; - self.send().await - } - - async fn send(mut self) -> std::io::Result<()> { - let final_checksum = self.checksum; - self.inner.write_u8(b'#').await?; - self.write_hex_u8(final_checksum).await?; - println!("{:?}", std::str::from_utf8(self.inner.buffer())); - self.inner.flush().await - } -} - -#[derive(Debug)] -enum Message { - String(String), - Signal, -} diff --git a/src/gdbserver/request.rs b/src/gdbserver/request.rs new file mode 100644 index 0000000..63f66e8 --- /dev/null +++ b/src/gdbserver/request.rs @@ -0,0 +1,84 @@ +use anyhow::{bail, Result}; +use tokio::io::{AsyncRead, AsyncReadExt as _}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RequestKind { + Signal, + Command, +} + +#[derive(Debug)] +pub struct Request<'a> { + pub kind: RequestKind, + body: &'a str, +} + +impl<'a> Request<'a> { + pub async fn read( + reader: &mut R, + buffer: &'a mut Vec, + ) -> Result { + buffer.clear(); + + let mut char = reader.read_u8().await?; + while char == b'+' { + // just ignore positive acks + char = reader.read_u8().await?; + } + if char == b'-' { + bail!("no support for negative acks"); + } + if char == 0x03 { + // This is how the client "cancels an in-flight request" + buffer.push(char); + let body = std::str::from_utf8(buffer)?; + return Ok(Self { + kind: RequestKind::Signal, + body, + }); + } + if char != b'$' { + // Messages are supposed to start with a dollar sign + bail!("malformed message"); + } + + // now read the body + let mut checksum = 0u8; + char = reader.read_u8().await?; + while char != b'#' { + if char == b'}' { + // escape character + checksum = checksum.wrapping_add(char); + char = reader.read_u8().await?; + checksum = checksum.wrapping_add(char); + buffer.push(char ^ 0x20); + } else { + checksum = checksum.wrapping_add(char); + buffer.push(char); + } + char = reader.read_u8().await?; + } + + let mut checksum_bytes = [b'0'; 2]; + reader.read_exact(&mut checksum_bytes).await?; + let checksum_str = std::str::from_utf8(&checksum_bytes)?; + let real_checksum = u8::from_str_radix(checksum_str, 16)?; + if checksum != real_checksum { + bail!("invalid checksum"); + } + + let body = std::str::from_utf8(buffer)?; + Ok(Self { + kind: RequestKind::Command, + body, + }) + } + + pub fn match_str(&mut self, prefix: &str) -> bool { + if let Some(new_body) = self.body.strip_prefix(prefix) { + self.body = new_body; + return true; + } + false + } +} diff --git a/src/gdbserver/response.rs b/src/gdbserver/response.rs new file mode 100644 index 0000000..fdd9220 --- /dev/null +++ b/src/gdbserver/response.rs @@ -0,0 +1,45 @@ +pub struct Response { + buffer: Vec, + checksum: u8, +} + +impl Response { + pub fn new(mut buffer: Vec, ack: bool) -> Self { + buffer.clear(); + if ack { + buffer.push(b'+'); + } + buffer.push(b'$'); + Self { + buffer, + checksum: 0, + } + } + + pub fn write_str(mut self, str: &str) -> Self { + for char in str.as_bytes() { + self.buffer.push(*char); + self.checksum = self.checksum.wrapping_add(*char); + } + self + } + + pub fn write_hex_u8(mut self, value: u8) -> Self { + for digit in [(value >> 4), (value & 0xf)] { + let char = if digit > 9 { + b'a' + digit - 10 + } else { + b'0' + digit + }; + self.buffer.push(char); + self.checksum = self.checksum.wrapping_add(char); + } + self + } + + pub fn finish(mut self) -> Vec { + let checksum = self.checksum; + self.buffer.push(b'#'); + self.write_hex_u8(checksum).buffer + } +}