Implement GDB/LLDB compatible server #3

Merged
SonicSwordcane merged 33 commits from debugger into main 2025-01-19 00:13:43 +00:00
5 changed files with 97 additions and 24 deletions
Showing only changes of commit d016538408 - Show all commits

10
Cargo.lock generated
View File

@ -397,6 +397,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -1742,6 +1751,7 @@ name = "lemur"
version = "0.2.5" version = "0.2.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atoi",
"bitflags 2.6.0", "bitflags 2.6.0",
"bytemuck", "bytemuck",
"cc", "cc",

View File

@ -9,6 +9,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
atoi = "2"
bitflags = { version = "2", features = ["serde"] } bitflags = { version = "2", features = ["serde"] }
bytemuck = { version = "1", features = ["derive"] } bytemuck = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }

View File

@ -212,6 +212,33 @@ impl GdbConnection {
} else if req.match_str("c") || req.match_str("vCont;c:") { } else if req.match_str("c") || req.match_str("vCont;c:") {
self.client.send_command(EmulatorCommand::Resume); self.client.send_command(EmulatorCommand::Resume);
// Don't send a response until we hit a breakpoint or get interrupted // Don't send a response until we hit a breakpoint or get interrupted
} else if req.match_str("p") {
let Some(register) = req.match_hex::<u32>() else {
let res = self.response();
self.send(res).await?;
continue;
};
let res = self.response().write_hex(register);
self.send(res).await?;
} else if req.match_str("m") {
let mut read_params = || {
let start = req.match_hex::<u32>()?;
if !req.match_str(",") {
return None;
};
let size = req.match_hex::<u32>()?;
Some((start, size))
};
let Some((start, size)) = read_params() else {
let res = self.response();
self.send(res).await?;
continue;
};
let mut res = self.response();
for i in 0..size {
res = res.write_hex((start + i) as u8);
}
self.send(res).await?;
} else { } else {
// unrecognized command // unrecognized command
let res = self.response(); let res = self.response();

View File

@ -1,4 +1,5 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use atoi::FromRadix16;
use tokio::io::{AsyncRead, AsyncReadExt as _}; use tokio::io::{AsyncRead, AsyncReadExt as _};
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -6,11 +7,32 @@ pub enum RequestKind {
Signal, Signal,
Command, Command,
} }
impl RequestKind {
fn name(self) -> &'static str {
match self {
Self::Signal => "Signal",
Self::Command => "Command",
}
}
}
#[derive(Debug)]
pub struct Request<'a> { pub struct Request<'a> {
pub kind: RequestKind, pub kind: RequestKind,
body: &'a str, buffer: &'a [u8],
}
impl std::fmt::Debug for Request<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_tuple(self.kind.name());
match self.kind {
RequestKind::Signal => ds.field(&self.buffer),
RequestKind::Command => match std::str::from_utf8(self.buffer) {
Ok(str) => ds.field(&str),
Err(_) => ds.field(&self.buffer),
},
};
ds.finish()
}
} }
impl<'a> Request<'a> { impl<'a> Request<'a> {
@ -31,10 +53,9 @@ impl<'a> Request<'a> {
if char == 0x03 { if char == 0x03 {
// This is how the client "cancels an in-flight request" // This is how the client "cancels an in-flight request"
buffer.push(char); buffer.push(char);
let body = std::str::from_utf8(buffer)?;
return Ok(Self { return Ok(Self {
kind: RequestKind::Signal, kind: RequestKind::Signal,
body, buffer,
}); });
} }
if char != b'$' { if char != b'$' {
@ -61,24 +82,34 @@ impl<'a> Request<'a> {
let mut checksum_bytes = [b'0'; 2]; let mut checksum_bytes = [b'0'; 2];
reader.read_exact(&mut checksum_bytes).await?; reader.read_exact(&mut checksum_bytes).await?;
let checksum_str = std::str::from_utf8(&checksum_bytes)?; let (real_checksum, 2) = u8::from_radix_16(&checksum_bytes) else {
let real_checksum = u8::from_str_radix(checksum_str, 16)?;
if checksum != real_checksum {
bail!("invalid checksum"); bail!("invalid checksum");
};
if checksum != real_checksum {
bail!("mismatched checksum");
} }
let body = std::str::from_utf8(buffer)?;
Ok(Self { Ok(Self {
kind: RequestKind::Command, kind: RequestKind::Command,
body, buffer,
}) })
} }
pub fn match_str(&mut self, prefix: &str) -> bool { pub fn match_str(&mut self, prefix: &str) -> bool {
if let Some(new_body) = self.body.strip_prefix(prefix) { if let Some(new_buffer) = self.buffer.strip_prefix(prefix.as_bytes()) {
self.body = new_body; self.buffer = new_buffer;
return true; return true;
} }
false false
} }
pub fn match_hex<I: FromRadix16>(&mut self) -> Option<I> {
match I::from_radix_16(self.buffer) {
(_, 0) => None,
(val, used) => {
self.buffer = self.buffer.split_at(used).1;
Some(val)
}
}
}
} }

View File

@ -1,3 +1,5 @@
use num_traits::ToBytes;
pub struct Response { pub struct Response {
buffer: Vec<u8>, buffer: Vec<u8>,
checksum: u8, checksum: u8,
@ -17,15 +19,16 @@ impl Response {
} }
pub fn write_str(mut self, str: &str) -> Self { pub fn write_str(mut self, str: &str) -> Self {
for char in str.as_bytes() { for byte in str.as_bytes() {
self.buffer.push(*char); self.buffer.push(*byte);
self.checksum = self.checksum.wrapping_add(*char); self.checksum = self.checksum.wrapping_add(*byte);
} }
self self
} }
pub fn write_hex_u8(mut self, value: u8) -> Self { pub fn write_hex<T: ToBytes>(mut self, value: T) -> Self {
for digit in [(value >> 4), (value & 0xf)] { for byte in value.to_be_bytes().as_ref() {
for digit in [(byte >> 4), (byte & 0xf)] {
let char = if digit > 9 { let char = if digit > 9 {
b'a' + digit - 10 b'a' + digit - 10
} else { } else {
@ -34,12 +37,13 @@ impl Response {
self.buffer.push(char); self.buffer.push(char);
self.checksum = self.checksum.wrapping_add(char); self.checksum = self.checksum.wrapping_add(char);
} }
}
self self
} }
pub fn finish(mut self) -> Vec<u8> { pub fn finish(mut self) -> Vec<u8> {
let checksum = self.checksum; let checksum = self.checksum;
self.buffer.push(b'#'); self.buffer.push(b'#');
self.write_hex_u8(checksum).buffer self.write_hex(checksum).buffer
} }
} }