457 lines
15 KiB
Rust
457 lines
15 KiB
Rust
use anyhow::{bail, Result};
|
|
use registers::REGISTERS;
|
|
use request::{Request, RequestKind, RequestSource};
|
|
use response::Response;
|
|
use std::{
|
|
sync::{Arc, Mutex},
|
|
thread,
|
|
};
|
|
use tokio::{
|
|
io::{AsyncWriteExt as _, BufReader},
|
|
net::{TcpListener, TcpStream},
|
|
select,
|
|
sync::{mpsc, oneshot},
|
|
};
|
|
|
|
use crate::emulator::{
|
|
DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId, VBWatchpointType,
|
|
};
|
|
|
|
mod registers;
|
|
mod request;
|
|
mod response;
|
|
|
|
pub struct GdbServer {
|
|
sim_id: SimId,
|
|
client: EmulatorClient,
|
|
status: Arc<Mutex<GdbServerStatus>>,
|
|
killer: Option<oneshot::Sender<()>>,
|
|
}
|
|
|
|
impl GdbServer {
|
|
pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
|
Self {
|
|
sim_id,
|
|
client,
|
|
status: Arc::new(Mutex::new(GdbServerStatus::Stopped)),
|
|
killer: None,
|
|
}
|
|
}
|
|
|
|
pub fn status(&self) -> GdbServerStatus {
|
|
self.status.lock().unwrap().clone()
|
|
}
|
|
|
|
pub fn start(&mut self, port: u16) {
|
|
*self.status.lock().unwrap() = GdbServerStatus::Connecting;
|
|
let sim_id = self.sim_id;
|
|
let client = self.client.clone();
|
|
let status = self.status.clone();
|
|
let (tx, rx) = oneshot::channel();
|
|
self.killer = Some(tx);
|
|
thread::spawn(move || {
|
|
tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap()
|
|
.block_on(async move {
|
|
select! {
|
|
_ = run_server(sim_id, client, port, &status) => {}
|
|
_ = rx => {
|
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
|
}
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
pub fn stop(&mut self) {
|
|
if let Some(killer) = self.killer.take() {
|
|
let _ = killer.send(());
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn run_server(
|
|
sim_id: SimId,
|
|
client: EmulatorClient,
|
|
port: u16,
|
|
status: &Mutex<GdbServerStatus>,
|
|
) {
|
|
let Some(stream) = try_connect(port, status).await else {
|
|
return;
|
|
};
|
|
let mut connection = GdbConnection::new(sim_id, client);
|
|
match connection.run(stream).await {
|
|
Ok(()) => {
|
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
|
}
|
|
Err(error) => {
|
|
*status.lock().unwrap() = GdbServerStatus::Error(error.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn try_connect(port: u16, status: &Mutex<GdbServerStatus>) -> Option<TcpStream> {
|
|
*status.lock().unwrap() = GdbServerStatus::Connecting;
|
|
let listener = match TcpListener::bind(("127.0.0.1", port)).await {
|
|
Ok(l) => l,
|
|
Err(err) => {
|
|
*status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
|
|
return None;
|
|
}
|
|
};
|
|
match listener.accept().await {
|
|
Ok((stream, _)) => {
|
|
*status.lock().unwrap() = GdbServerStatus::Running;
|
|
Some(stream)
|
|
}
|
|
Err(err) => {
|
|
*status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum GdbServerStatus {
|
|
Stopped,
|
|
Connecting,
|
|
Running,
|
|
Error(String),
|
|
}
|
|
|
|
impl GdbServerStatus {
|
|
pub fn running(&self) -> bool {
|
|
matches!(self, Self::Connecting | Self::Running)
|
|
}
|
|
}
|
|
|
|
struct GdbConnection {
|
|
sim_id: SimId,
|
|
client: EmulatorClient,
|
|
ack_messages: bool,
|
|
stop_reason: Option<DebugStopReason>,
|
|
response_buf: Option<Vec<u8>>,
|
|
memory_buf: Option<Vec<u8>>,
|
|
}
|
|
|
|
impl GdbConnection {
|
|
fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
|
Self {
|
|
sim_id,
|
|
client,
|
|
ack_messages: true,
|
|
stop_reason: None,
|
|
response_buf: None,
|
|
memory_buf: None,
|
|
}
|
|
}
|
|
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 response = select! {
|
|
maybe_event = debug_source.recv() => {
|
|
let Some(event) = maybe_event else {
|
|
// debugger has stopped running
|
|
break;
|
|
};
|
|
self.handle_event(event)
|
|
}
|
|
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;s;")
|
|
} 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_some_str(["s", "vCont;s:"]).is_some() {
|
|
self.client
|
|
.send_command(EmulatorCommand::DebugStep(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::<u64>()?;
|
|
if !req.match_str(",") {
|
|
return None;
|
|
};
|
|
let length = req.match_hex::<usize>()?;
|
|
|
|
let mut buf = self.memory_buf.take().unwrap_or_default();
|
|
buf.clear();
|
|
|
|
// The v810 has a 32-bit address space.
|
|
// Addresses wrap within that space, but we don't need to implement that for 64-bit addresses.
|
|
// Just refuse to return any info for addresses above 0xffffffff.
|
|
let Ok(start) = u32::try_from(start) else {
|
|
return Some(buf);
|
|
};
|
|
let length = length.min((u32::MAX - start) as usize + 1);
|
|
if length == 0 {
|
|
return Some(buf);
|
|
}
|
|
let (tx, rx) = ::oneshot::channel();
|
|
self.client.send_command(EmulatorCommand::ReadMemory(
|
|
self.sim_id,
|
|
start,
|
|
length,
|
|
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");
|
|
} else 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);
|
|
res
|
|
} else {
|
|
self.response()
|
|
}
|
|
} else if req.match_str("Z") {
|
|
let mut parse_request = || {
|
|
let type_ = req.match_hex::<u8>()?;
|
|
if !req.match_str(",") {
|
|
return None;
|
|
}
|
|
let address = req.match_hex()?;
|
|
if type_ == 0 || type_ == 1 {
|
|
return Some(EmulatorCommand::AddBreakpoint(self.sim_id, address));
|
|
}
|
|
if !req.match_str(",") {
|
|
return None;
|
|
}
|
|
let length = req.match_hex()?;
|
|
let watch = match type_ {
|
|
2 => VBWatchpointType::Write,
|
|
3 => VBWatchpointType::Read,
|
|
4 => VBWatchpointType::Access,
|
|
_ => return None,
|
|
};
|
|
Some(EmulatorCommand::AddWatchpoint(
|
|
self.sim_id,
|
|
address,
|
|
length,
|
|
watch,
|
|
))
|
|
};
|
|
if let Some(command) = parse_request() {
|
|
self.client.send_command(command);
|
|
self.response().write_str("OK")
|
|
} else {
|
|
self.response()
|
|
}
|
|
} else if req.match_str("z") {
|
|
let mut parse_request = || {
|
|
let type_ = req.match_hex::<u8>()?;
|
|
if !req.match_str(",") {
|
|
return None;
|
|
}
|
|
let address = req.match_hex()?;
|
|
if type_ == 0 || type_ == 1 {
|
|
return Some(EmulatorCommand::RemoveBreakpoint(self.sim_id, address));
|
|
}
|
|
if !req.match_str(",") {
|
|
return None;
|
|
}
|
|
let length = req.match_hex()?;
|
|
let watch = match type_ {
|
|
2 => VBWatchpointType::Write,
|
|
3 => VBWatchpointType::Read,
|
|
4 => VBWatchpointType::Access,
|
|
_ => return None,
|
|
};
|
|
Some(EmulatorCommand::RemoveWatchpoint(
|
|
self.sim_id,
|
|
address,
|
|
length,
|
|
watch,
|
|
))
|
|
};
|
|
if let Some(command) = parse_request() {
|
|
self.client.send_command(command);
|
|
self.response().write_str("OK")
|
|
} else {
|
|
self.response()
|
|
}
|
|
} else {
|
|
// unrecognized command
|
|
self.response()
|
|
};
|
|
Ok(Some(res))
|
|
}
|
|
|
|
fn response(&mut self) -> Response {
|
|
Response::new(
|
|
self.response_buf.take().unwrap_or_default(),
|
|
self.ack_messages,
|
|
)
|
|
}
|
|
}
|
|
|
|
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>) -> String {
|
|
let mut result = String::new();
|
|
result += if reason.is_some() { "T05;" } else { "T00;" };
|
|
if let Some(DebugStopReason::Breakpoint) = reason {
|
|
result += "swbreak;";
|
|
}
|
|
if let Some(DebugStopReason::Watchpoint(watch, address)) = reason {
|
|
result += match watch {
|
|
VBWatchpointType::Write => "watch:",
|
|
VBWatchpointType::Read => "rwatch:",
|
|
VBWatchpointType::Access => "awatch:",
|
|
};
|
|
result += &format!("{address:08x};");
|
|
}
|
|
|
|
result += "thread:p1.t1;threads:p1.t1;";
|
|
|
|
if let Some(reason) = reason {
|
|
result += "reason:";
|
|
result += match reason {
|
|
DebugStopReason::Trace => "trace;",
|
|
DebugStopReason::Breakpoint => "breakpoint;",
|
|
DebugStopReason::Watchpoint(_, _) => "watchpoint;",
|
|
DebugStopReason::Trapped => "trap;",
|
|
};
|
|
}
|
|
|
|
if let Some(DebugStopReason::Watchpoint(_, address)) = reason {
|
|
result += "description:";
|
|
result += &hex::encode(address.to_string());
|
|
result += ";";
|
|
}
|
|
|
|
result
|
|
}
|