Separate GDB request and response into structs
This commit is contained in:
parent
24487b21b7
commit
9519897711
245
src/gdbserver.rs
245
src/gdbserver.rs
|
@ -1,10 +1,12 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use request::{Request, RequestKind};
|
||||||
|
use response::Response;
|
||||||
use std::{
|
use std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt as _, BufReader, BufWriter},
|
io::{AsyncWriteExt as _, BufReader, BufWriter},
|
||||||
net::{
|
net::{
|
||||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
TcpListener, TcpStream,
|
TcpListener, TcpStream,
|
||||||
|
@ -15,6 +17,9 @@ use tokio::{
|
||||||
|
|
||||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
|
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
|
||||||
|
|
||||||
|
mod request;
|
||||||
|
mod response;
|
||||||
|
|
||||||
pub struct GdbServer {
|
pub struct GdbServer {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
|
@ -126,6 +131,8 @@ struct GdbConnection {
|
||||||
stream_in: BufReader<OwnedReadHalf>,
|
stream_in: BufReader<OwnedReadHalf>,
|
||||||
stream_out: BufWriter<OwnedWriteHalf>,
|
stream_out: BufWriter<OwnedWriteHalf>,
|
||||||
ack_messages: bool,
|
ack_messages: bool,
|
||||||
|
request_buf: Vec<u8>,
|
||||||
|
response_buf: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GdbConnection {
|
impl GdbConnection {
|
||||||
|
@ -137,208 +144,94 @@ impl GdbConnection {
|
||||||
stream_in: BufReader::new(rx),
|
stream_in: BufReader::new(rx),
|
||||||
stream_out: BufWriter::new(tx),
|
stream_out: BufWriter::new(tx),
|
||||||
ack_messages: true,
|
ack_messages: true,
|
||||||
|
request_buf: vec![],
|
||||||
|
response_buf: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn run(mut self) -> Result<()> {
|
async fn run(mut self) -> Result<()> {
|
||||||
println!("Connected for {}", self.sim_id);
|
println!("Connected for {}", self.sim_id);
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
self.client.send_command(EmulatorCommand::Pause);
|
||||||
loop {
|
loop {
|
||||||
let message = self.read_message().await?;
|
let mut req = Request::read(&mut self.stream_in, &mut self.request_buf).await?;
|
||||||
println!("received {:?}", message);
|
println!("received {:?}", req);
|
||||||
|
|
||||||
//let mut res = ResponseWriter::new(&mut self.stream_out);
|
if req.kind == RequestKind::Signal {
|
||||||
//res.init(self.ack_messages).await?;
|
|
||||||
|
|
||||||
let body = match &message {
|
|
||||||
Message::String(str) => str.as_str(),
|
|
||||||
Message::Signal => {
|
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
self.client.send_command(EmulatorCommand::Pause);
|
||||||
let mut res = self.respond().await?;
|
let res = self
|
||||||
res.write_str("T05;thread:p1.t1;threads:p1.t1;reason:trap;")
|
.response()
|
||||||
.await?;
|
.write_str("T05;thread:p1.t1;threads:p1.t1;reason:trap;");
|
||||||
res.send().await?;
|
self.send(res).await?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if body == "QStartNoAckMode" {
|
if req.match_str("QStartNoAckMode") {
|
||||||
let mut res = ResponseWriter::new(&mut self.stream_out);
|
let res = self.response().write_str("OK");
|
||||||
res.init(self.ack_messages).await?;
|
self.send(res).await?;
|
||||||
self.ack_messages = false;
|
self.ack_messages = false;
|
||||||
res.send_ok().await?;
|
} else if req.match_str("qSupported:") {
|
||||||
} else if body.starts_with("qSupported:") {
|
let res = self
|
||||||
let mut res = self.respond().await?;
|
.response()
|
||||||
res.write_str("multiprocess+;swbreak+;vContSupported+")
|
.write_str("multiprocess+;swbreak+;vContSupported+");
|
||||||
.await?;
|
self.send(res).await?;
|
||||||
res.send().await?;
|
} else if req.match_str("QThreadSuffixSupported")
|
||||||
} else if body == "QThreadSuffixSupported"
|
|| req.match_str("QListThreadsInStopReply")
|
||||||
|| body == "QListThreadsInStopReply"
|
|| req.match_str("QEnableErrorStrings")
|
||||||
|| body == "QEnableErrorStrings"
|
|
||||||
{
|
{
|
||||||
let res = self.respond().await?;
|
let res = self.response().write_str("OK");
|
||||||
res.send_ok().await?;
|
self.send(res).await?;
|
||||||
} else if body == "qHostInfo" {
|
} else if req.match_str("qHostInfo") {
|
||||||
let mut res = self.respond().await?;
|
let res = self.response().write_str(&format!(
|
||||||
res.write_str(&format!(
|
|
||||||
"triple:{};endian:little;ptrsize:4;",
|
"triple:{};endian:little;ptrsize:4;",
|
||||||
hex::encode("v810-unknown-vb")
|
hex::encode("v810-unknown-vb")
|
||||||
))
|
));
|
||||||
.await?;
|
self.send(res).await?;
|
||||||
res.send().await?;
|
} else if req.match_str("qProcessInfo") {
|
||||||
} else if body == "qProcessInfo" {
|
let res = self.response().write_str(&format!(
|
||||||
let mut res = self.respond().await?;
|
|
||||||
res.write_str(&format!(
|
|
||||||
"pid:1;triple:{};endian:little;ptrsize:4;",
|
"pid:1;triple:{};endian:little;ptrsize:4;",
|
||||||
hex::encode("v810-unknown-vb")
|
hex::encode("v810-unknown-vb")
|
||||||
))
|
));
|
||||||
.await?;
|
self.send(res).await?;
|
||||||
res.send().await?;
|
} else if req.match_str("vCont?") {
|
||||||
} else if body == "vCont?" {
|
let res = self.response().write_str("vCont;c;");
|
||||||
let mut res = self.respond().await?;
|
self.send(res).await?;
|
||||||
res.write_str("vCont;c;").await?;
|
} else if req.match_str("qC") {
|
||||||
res.send().await?;
|
|
||||||
} else if body == "qC" {
|
|
||||||
// The v810 has no threads, so report that the "current thread" is 1.
|
// The v810 has no threads, so report that the "current thread" is 1.
|
||||||
let mut res = self.respond().await?;
|
let res = self.response().write_str("QCp1.t1");
|
||||||
res.write_str("QCp1.t1").await?;
|
self.send(res).await?;
|
||||||
res.send().await?;
|
} else if req.match_str("qfThreadInfo") {
|
||||||
} else if body == "qfThreadInfo" {
|
let res = self.response().write_str("mp1.t1");
|
||||||
let mut res = self.respond().await?;
|
self.send(res).await?;
|
||||||
res.write_str("mp1.t1").await?;
|
} else if req.match_str("qsThreadInfo") {
|
||||||
res.send().await?;
|
let res = self.response().write_str("l");
|
||||||
} else if body == "qsThreadInfo" {
|
self.send(res).await?;
|
||||||
let mut res = self.respond().await?;
|
} else if req.match_str("k") {
|
||||||
res.write_str("l").await?;
|
|
||||||
res.send().await?;
|
|
||||||
} else if body == "k" {
|
|
||||||
bail!("debug process was killed");
|
bail!("debug process was killed");
|
||||||
} else if body == "?" {
|
} else if req.match_str("?") {
|
||||||
let mut res = self.respond().await?;
|
let res = self.response().write_str("T00;thread:p1.t1;threads:p1.t1;");
|
||||||
res.write_str("T00;thread:p1.t1;threads:p1.t1;").await?;
|
self.send(res).await?;
|
||||||
res.send().await?;
|
} else if req.match_str("c") || req.match_str("vCont;c:") {
|
||||||
} else if body == "c" || body.starts_with("vCont;c:") {
|
|
||||||
// Continue running the game until we're interrupted again
|
|
||||||
self.client.send_command(EmulatorCommand::Resume);
|
self.client.send_command(EmulatorCommand::Resume);
|
||||||
continue;
|
// Don't send a response until we hit a breakpoint or get interrupted
|
||||||
} else {
|
} else {
|
||||||
// unrecognized command
|
// unrecognized command
|
||||||
let res = self.respond().await?;
|
let res = self.response();
|
||||||
res.send().await?;
|
self.send(res).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_message(&mut self) -> Result<Message> {
|
fn response(&mut self) -> Response {
|
||||||
let mut char = self.read_byte().await?;
|
Response::new(
|
||||||
while char == b'+' {
|
self.response_buf.take().unwrap_or_default(),
|
||||||
// just ignore positive acks
|
self.ack_messages,
|
||||||
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
|
async fn send(&mut self, res: Response) -> std::io::Result<()> {
|
||||||
let mut checksum = 0u8;
|
let buffer = res.finish();
|
||||||
let mut body = vec![];
|
println!("{:?}", std::str::from_utf8(&buffer));
|
||||||
char = self.read_byte().await?;
|
self.stream_out.write_all(&buffer).await?;
|
||||||
while char != b'#' {
|
self.response_buf = Some(buffer);
|
||||||
if char == b'}' {
|
self.stream_out.flush().await
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_byte(&mut self) -> std::io::Result<u8> {
|
|
||||||
self.stream_in.read_u8().await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn respond(&mut self) -> std::io::Result<ResponseWriter<'_>> {
|
|
||||||
let mut res = ResponseWriter::new(&mut self.stream_out);
|
|
||||||
res.init(self.ack_messages).await?;
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResponseWriter<'a> {
|
|
||||||
inner: &'a mut BufWriter<OwnedWriteHalf>,
|
|
||||||
checksum: u8,
|
|
||||||
}
|
|
||||||
impl<'a> ResponseWriter<'a> {
|
|
||||||
fn new(inner: &'a mut BufWriter<OwnedWriteHalf>) -> 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,
|
|
||||||
}
|
|
||||||
|
|
|
@ -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<R: AsyncRead + Unpin>(
|
||||||
|
reader: &mut R,
|
||||||
|
buffer: &'a mut Vec<u8>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
pub struct Response {
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
checksum: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn new(mut buffer: Vec<u8>, 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<u8> {
|
||||||
|
let checksum = self.checksum;
|
||||||
|
self.buffer.push(b'#');
|
||||||
|
self.write_hex_u8(checksum).buffer
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue