Let emulator tell debugger to stop
This commit is contained in:
parent
a5b5f8e80f
commit
11df670ff4
|
@ -151,6 +151,7 @@ pub struct Emulator {
|
||||||
linked: Arc<AtomicBool>,
|
linked: Arc<AtomicBool>,
|
||||||
renderers: HashMap<SimId, TextureSink>,
|
renderers: HashMap<SimId, TextureSink>,
|
||||||
messages: HashMap<SimId, mpsc::Sender<Toast>>,
|
messages: HashMap<SimId, mpsc::Sender<Toast>>,
|
||||||
|
debuggers: HashMap<SimId, DebugInfo>,
|
||||||
eye_contents: Vec<u8>,
|
eye_contents: Vec<u8>,
|
||||||
audio_samples: Vec<f32>,
|
audio_samples: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
@ -174,6 +175,7 @@ impl Emulator {
|
||||||
linked,
|
linked,
|
||||||
renderers: HashMap::new(),
|
renderers: HashMap::new(),
|
||||||
messages: HashMap::new(),
|
messages: HashMap::new(),
|
||||||
|
debuggers: HashMap::new(),
|
||||||
eye_contents: vec![0u8; 384 * 224 * 2],
|
eye_contents: vec![0u8; 384 * 224 * 2],
|
||||||
audio_samples: vec![0.0; EXPECTED_FRAME_SIZE],
|
audio_samples: vec![0.0; EXPECTED_FRAME_SIZE],
|
||||||
})
|
})
|
||||||
|
@ -288,6 +290,48 @@ impl Emulator {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_debugging(&mut self, sim_id: SimId, sender: DebugSender) {
|
||||||
|
let debug = DebugInfo {
|
||||||
|
sender,
|
||||||
|
stop_reason: Some(DebugStopReason::Trapped),
|
||||||
|
};
|
||||||
|
self.debuggers.insert(sim_id, debug);
|
||||||
|
self.state
|
||||||
|
.store(EmulatorState::Debugging, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_debugging(&mut self, sim_id: SimId) {
|
||||||
|
self.debuggers.remove(&sim_id);
|
||||||
|
if self.debuggers.is_empty() {
|
||||||
|
self.state.store(EmulatorState::Running, Ordering::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_interrupt(&mut self, sim_id: SimId) {
|
||||||
|
let Some(debugger) = self.debuggers.get_mut(&sim_id) else {
|
||||||
|
self.stop_debugging(sim_id);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !matches!(debugger.stop_reason, Some(DebugStopReason::Trapped)) {
|
||||||
|
debugger.stop_reason = Some(DebugStopReason::Trapped);
|
||||||
|
if debugger
|
||||||
|
.sender
|
||||||
|
.send(DebugEvent::Stopped(DebugStopReason::Trapped))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
self.stop_debugging(sim_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_continue(&mut self, sim_id: SimId) {
|
||||||
|
let Some(debugger) = self.debuggers.get_mut(&sim_id) else {
|
||||||
|
self.stop_debugging(sim_id);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
debugger.stop_reason = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
let idle = self.tick();
|
let idle = self.tick();
|
||||||
|
@ -320,8 +364,14 @@ impl Emulator {
|
||||||
let p1_state = self.sim_state[SimId::Player1.to_index()].load(Ordering::Acquire);
|
let p1_state = self.sim_state[SimId::Player1.to_index()].load(Ordering::Acquire);
|
||||||
let p2_state = self.sim_state[SimId::Player2.to_index()].load(Ordering::Acquire);
|
let p2_state = self.sim_state[SimId::Player2.to_index()].load(Ordering::Acquire);
|
||||||
let state = self.state.load(Ordering::Acquire);
|
let state = self.state.load(Ordering::Acquire);
|
||||||
let p1_running = state == EmulatorState::Running && p1_state == SimState::Ready;
|
// Don't emulate if the state is "paused", or if any sim is paused in the debugger
|
||||||
let p2_running = state == EmulatorState::Running && p2_state == SimState::Ready;
|
let running = match state {
|
||||||
|
EmulatorState::Paused => false,
|
||||||
|
EmulatorState::Running => true,
|
||||||
|
EmulatorState::Debugging => self.debuggers.values().all(|d| d.stop_reason.is_none()),
|
||||||
|
};
|
||||||
|
let p1_running = running && p1_state == SimState::Ready;
|
||||||
|
let p2_running = running && p2_state == SimState::Ready;
|
||||||
let mut idle = !p1_running && !p2_running;
|
let mut idle = !p1_running && !p2_running;
|
||||||
if p1_running && p2_running {
|
if p1_running && p2_running {
|
||||||
Sim::emulate_many(&mut self.sims);
|
Sim::emulate_many(&mut self.sims);
|
||||||
|
@ -405,6 +455,18 @@ impl Emulator {
|
||||||
EmulatorCommand::Resume => {
|
EmulatorCommand::Resume => {
|
||||||
self.resume_sims();
|
self.resume_sims();
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::StartDebugging(sim_id, debugger) => {
|
||||||
|
self.start_debugging(sim_id, debugger);
|
||||||
|
}
|
||||||
|
EmulatorCommand::StopDebugging(sim_id) => {
|
||||||
|
self.stop_debugging(sim_id);
|
||||||
|
}
|
||||||
|
EmulatorCommand::DebugInterrupt(sim_id) => {
|
||||||
|
self.debug_interrupt(sim_id);
|
||||||
|
}
|
||||||
|
EmulatorCommand::DebugContinue(sim_id) => {
|
||||||
|
self.debug_continue(sim_id);
|
||||||
|
}
|
||||||
EmulatorCommand::ReadRegister(sim_id, register, done) => {
|
EmulatorCommand::ReadRegister(sim_id, register, done) => {
|
||||||
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
return;
|
return;
|
||||||
|
@ -476,6 +538,10 @@ pub enum EmulatorCommand {
|
||||||
StopSecondSim,
|
StopSecondSim,
|
||||||
Pause,
|
Pause,
|
||||||
Resume,
|
Resume,
|
||||||
|
StartDebugging(SimId, DebugSender),
|
||||||
|
StopDebugging(SimId),
|
||||||
|
DebugInterrupt(SimId),
|
||||||
|
DebugContinue(SimId),
|
||||||
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
||||||
ReadMemory(SimId, Range<u32>, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
ReadMemory(SimId, Range<u32>, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
||||||
SetAudioEnabled(bool, bool),
|
SetAudioEnabled(bool, bool),
|
||||||
|
@ -499,6 +565,24 @@ pub enum SimState {
|
||||||
pub enum EmulatorState {
|
pub enum EmulatorState {
|
||||||
Paused,
|
Paused,
|
||||||
Running,
|
Running,
|
||||||
|
Debugging,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebugSender = tokio::sync::mpsc::UnboundedSender<DebugEvent>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum DebugStopReason {
|
||||||
|
// The debugger told us to pause
|
||||||
|
Trapped,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebugInfo {
|
||||||
|
sender: DebugSender,
|
||||||
|
stop_reason: Option<DebugStopReason>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DebugEvent {
|
||||||
|
Stopped(DebugStopReason),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
354
src/gdbserver.rs
354
src/gdbserver.rs
|
@ -1,22 +1,19 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use registers::REGISTERS;
|
use registers::REGISTERS;
|
||||||
use request::{Request, RequestKind};
|
use request::{Request, RequestKind, RequestSource};
|
||||||
use response::Response;
|
use response::Response;
|
||||||
use std::{
|
use std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncWriteExt as _, BufReader, BufWriter},
|
io::{AsyncWriteExt as _, BufReader},
|
||||||
net::{
|
net::{TcpListener, TcpStream},
|
||||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
|
||||||
TcpListener, TcpStream,
|
|
||||||
},
|
|
||||||
select,
|
select,
|
||||||
sync::oneshot,
|
sync::{mpsc, oneshot},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
|
use crate::emulator::{DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId};
|
||||||
|
|
||||||
mod registers;
|
mod registers;
|
||||||
mod request;
|
mod request;
|
||||||
|
@ -81,8 +78,8 @@ async fn run_server(
|
||||||
let Some(stream) = try_connect(port, status).await else {
|
let Some(stream) = try_connect(port, status).await else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let connection = GdbConnection::new(sim_id, client, stream);
|
let mut connection = GdbConnection::new(sim_id, client);
|
||||||
match connection.run().await {
|
match connection.run(stream).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||||
}
|
}
|
||||||
|
@ -130,191 +127,191 @@ impl GdbServerStatus {
|
||||||
struct GdbConnection {
|
struct GdbConnection {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
stream_in: BufReader<OwnedReadHalf>,
|
|
||||||
stream_out: BufWriter<OwnedWriteHalf>,
|
|
||||||
ack_messages: bool,
|
ack_messages: bool,
|
||||||
request_buf: Vec<u8>,
|
stop_reason: Option<DebugStopReason>,
|
||||||
response_buf: Option<Vec<u8>>,
|
response_buf: Option<Vec<u8>>,
|
||||||
memory_buf: Option<Vec<u8>>,
|
memory_buf: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GdbConnection {
|
impl GdbConnection {
|
||||||
fn new(sim_id: SimId, client: EmulatorClient, stream: TcpStream) -> Self {
|
fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
||||||
let (rx, tx) = stream.into_split();
|
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
client,
|
client,
|
||||||
stream_in: BufReader::new(rx),
|
|
||||||
stream_out: BufWriter::new(tx),
|
|
||||||
ack_messages: true,
|
ack_messages: true,
|
||||||
request_buf: vec![],
|
stop_reason: None,
|
||||||
response_buf: None,
|
response_buf: None,
|
||||||
memory_buf: None,
|
memory_buf: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn run(mut self) -> Result<()> {
|
async fn run(&mut self, stream: TcpStream) -> Result<()> {
|
||||||
println!("Connected for {}", self.sim_id);
|
let (debug_sink, mut debug_source) = mpsc::unbounded_channel();
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
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 {
|
loop {
|
||||||
let mut req = Request::read(&mut self.stream_in, &mut self.request_buf).await?;
|
let response = select! {
|
||||||
println!("received {:02x?}", req);
|
maybe_event = debug_source.recv() => {
|
||||||
|
let Some(event) = maybe_event else {
|
||||||
if req.kind == RequestKind::Signal {
|
// debugger has stopped running
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
break;
|
||||||
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 size = req.match_hex::<u32>()?;
|
self.handle_event(event)
|
||||||
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.memory_buf = Some(memory);
|
maybe_request = request_source.recv() => {
|
||||||
self.send(res).await?;
|
let req = maybe_request?;
|
||||||
} else if req.match_str("x") {
|
self.handle_request(req)?
|
||||||
let mut read_memory = || {
|
}
|
||||||
let start = req.match_hex::<u32>()?;
|
};
|
||||||
if !req.match_str(",") {
|
if let Some(res) = response {
|
||||||
return None;
|
let buffer = res.finish();
|
||||||
};
|
match std::str::from_utf8(&buffer) {
|
||||||
let size = req.match_hex::<u32>()?;
|
Ok(text) => println!("response: {text}"),
|
||||||
let mut buf = self.memory_buf.take().unwrap_or_default();
|
Err(_) => println!("response: {buffer:02x?}"),
|
||||||
buf.clear();
|
}
|
||||||
let (tx, rx) = ::oneshot::channel();
|
tx.write_all(&buffer).await?;
|
||||||
self.client.send_command(EmulatorCommand::ReadMemory(
|
self.response_buf = Some(buffer);
|
||||||
self.sim_id,
|
tx.flush().await?;
|
||||||
start..(start + size),
|
}
|
||||||
buf,
|
}
|
||||||
tx,
|
Ok(())
|
||||||
));
|
}
|
||||||
rx.recv().ok()
|
|
||||||
};
|
fn handle_event(&mut self, event: DebugEvent) -> Option<Response> {
|
||||||
let Some(memory) = read_memory() else {
|
let res = match event {
|
||||||
self.send_empty().await?;
|
DebugEvent::Stopped(reason) => {
|
||||||
continue;
|
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();
|
let mut res = self.response();
|
||||||
if memory.is_empty() {
|
if op == "m" {
|
||||||
res = res.write_str("OK");
|
// send the hex-encoded byte stream
|
||||||
|
for byte in &memory {
|
||||||
|
res = res.write_hex(*byte);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// send the raw byte stream
|
||||||
for byte in &memory {
|
for byte in &memory {
|
||||||
res = res.write_byte(*byte);
|
res = res.write_byte(*byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.memory_buf = Some(memory);
|
self.memory_buf = Some(memory);
|
||||||
self.send(res).await?;
|
res
|
||||||
} else {
|
} else {
|
||||||
// unrecognized command
|
self.response()
|
||||||
self.send_empty().await?;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
// unrecognized command
|
||||||
|
self.response()
|
||||||
async fn send_empty(&mut self) -> std::io::Result<()> {
|
};
|
||||||
let res = self.response();
|
Ok(Some(res))
|
||||||
self.send(res).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&mut self) -> Response {
|
fn response(&mut self) -> Response {
|
||||||
|
@ -323,15 +320,18 @@ impl GdbConnection {
|
||||||
self.ack_messages,
|
self.ack_messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn send(&mut self, res: Response) -> std::io::Result<()> {
|
impl Drop for GdbConnection {
|
||||||
let buffer = res.finish();
|
fn drop(&mut self) {
|
||||||
match std::str::from_utf8(&buffer) {
|
self.client
|
||||||
Ok(text) => println!("response: {text}"),
|
.send_command(EmulatorCommand::StopDebugging(self.sim_id));
|
||||||
Err(_) => println!("response: {buffer:02x?}"),
|
}
|
||||||
}
|
}
|
||||||
self.stream_out.write_all(&buffer).await?;
|
|
||||||
self.response_buf = Some(buffer);
|
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
|
||||||
self.stream_out.flush().await
|
match reason {
|
||||||
|
Some(DebugStopReason::Trapped) => "T05;thread:p1.t1;threads:p1.t1;reason:trap;",
|
||||||
|
None => "T00;thread:p1.t1;threads:p1.t1;",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,66 +35,7 @@ impl std::fmt::Debug for Request<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Request<'a> {
|
impl Request<'_> {
|
||||||
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);
|
|
||||||
return Ok(Self {
|
|
||||||
kind: RequestKind::Signal,
|
|
||||||
buffer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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 (real_checksum, 2) = u8::from_radix_16(&checksum_bytes) else {
|
|
||||||
bail!("invalid checksum");
|
|
||||||
};
|
|
||||||
if checksum != real_checksum {
|
|
||||||
bail!("mismatched checksum");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
kind: RequestKind::Command,
|
|
||||||
buffer,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn match_str(&mut self, prefix: &str) -> bool {
|
pub fn match_str(&mut self, prefix: &str) -> bool {
|
||||||
if let Some(new_buffer) = self.buffer.strip_prefix(prefix.as_bytes()) {
|
if let Some(new_buffer) = self.buffer.strip_prefix(prefix.as_bytes()) {
|
||||||
self.buffer = new_buffer;
|
self.buffer = new_buffer;
|
||||||
|
@ -103,6 +44,13 @@ impl<'a> Request<'a> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn match_some_str<'a, I: IntoIterator<Item = &'a str>>(
|
||||||
|
&mut self,
|
||||||
|
prefixes: I,
|
||||||
|
) -> Option<&'a str> {
|
||||||
|
prefixes.into_iter().find(|&prefix| self.match_str(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn match_hex<I: FromRadix16>(&mut self) -> Option<I> {
|
pub fn match_hex<I: FromRadix16>(&mut self) -> Option<I> {
|
||||||
match I::from_radix_16(self.buffer) {
|
match I::from_radix_16(self.buffer) {
|
||||||
(_, 0) => None,
|
(_, 0) => None,
|
||||||
|
@ -113,3 +61,119 @@ impl<'a> Request<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RequestSource<R> {
|
||||||
|
reader: R,
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
state: RequestReadState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsyncRead + Unpin> RequestSource<R> {
|
||||||
|
pub fn new(reader: R) -> Self {
|
||||||
|
Self {
|
||||||
|
reader,
|
||||||
|
buffer: vec![],
|
||||||
|
state: RequestReadState::Header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn recv(&mut self) -> Result<Request<'_>> {
|
||||||
|
let mut char = self.reader.read_u8().await?;
|
||||||
|
if matches!(self.state, RequestReadState::Start) {
|
||||||
|
self.buffer.clear();
|
||||||
|
self.state = RequestReadState::Header;
|
||||||
|
}
|
||||||
|
if matches!(self.state, RequestReadState::Header) {
|
||||||
|
// Just ignore positive acks
|
||||||
|
while char == b'+' {
|
||||||
|
char = self.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"
|
||||||
|
self.buffer.push(char);
|
||||||
|
self.state = RequestReadState::Start;
|
||||||
|
return Ok(Request {
|
||||||
|
kind: RequestKind::Signal,
|
||||||
|
buffer: &self.buffer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if char != b'$' {
|
||||||
|
// Messages are supposed to start with a dollar sign
|
||||||
|
bail!("malformed message");
|
||||||
|
}
|
||||||
|
self.state = RequestReadState::Body {
|
||||||
|
checksum: 0,
|
||||||
|
escaping: false,
|
||||||
|
};
|
||||||
|
char = self.reader.read_u8().await?;
|
||||||
|
}
|
||||||
|
while let RequestReadState::Body { checksum, escaping } = &mut self.state {
|
||||||
|
if char == b'#' && !*escaping {
|
||||||
|
self.state = RequestReadState::Checksum {
|
||||||
|
expected: *checksum,
|
||||||
|
actual: 0,
|
||||||
|
digits: 0,
|
||||||
|
};
|
||||||
|
char = self.reader.read_u8().await?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*checksum = checksum.wrapping_add(char);
|
||||||
|
|
||||||
|
if *escaping {
|
||||||
|
// escaped character
|
||||||
|
self.buffer.push(char ^ 0x20);
|
||||||
|
*escaping = false;
|
||||||
|
} else if char == b'}' {
|
||||||
|
// next character will be escaped
|
||||||
|
*escaping = true;
|
||||||
|
} else {
|
||||||
|
self.buffer.push(char);
|
||||||
|
}
|
||||||
|
char = self.reader.read_u8().await?;
|
||||||
|
}
|
||||||
|
while let RequestReadState::Checksum {
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
digits,
|
||||||
|
} = &mut self.state
|
||||||
|
{
|
||||||
|
let digit = match char {
|
||||||
|
b'0'..=b'9' => char - b'0',
|
||||||
|
b'a'..=b'f' => char - b'a' + 10,
|
||||||
|
b'A'..=b'F' => char - b'A' + 10,
|
||||||
|
_ => bail!("invalid checksum"),
|
||||||
|
};
|
||||||
|
*actual = (*actual << 4) + digit;
|
||||||
|
*digits += 1;
|
||||||
|
if *digits == 2 {
|
||||||
|
if *expected != *actual {
|
||||||
|
bail!("mismatched checksum");
|
||||||
|
}
|
||||||
|
self.state = RequestReadState::Start;
|
||||||
|
return Ok(Request {
|
||||||
|
kind: RequestKind::Command,
|
||||||
|
buffer: &self.buffer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
char = self.reader.read_u8().await?;
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RequestReadState {
|
||||||
|
Start,
|
||||||
|
Header,
|
||||||
|
Body {
|
||||||
|
checksum: u8,
|
||||||
|
escaping: bool,
|
||||||
|
},
|
||||||
|
Checksum {
|
||||||
|
expected: u8,
|
||||||
|
actual: u8,
|
||||||
|
digits: u8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -83,20 +83,18 @@ impl GameWindow {
|
||||||
});
|
});
|
||||||
ui.menu_button("Emulation", |ui| {
|
ui.menu_button("Emulation", |ui| {
|
||||||
let state = self.client.emulator_state();
|
let state = self.client.emulator_state();
|
||||||
let can_interact = self.client.sim_state(self.sim_id) == SimState::Ready;
|
let is_ready = self.client.sim_state(self.sim_id) == SimState::Ready;
|
||||||
|
let can_pause = is_ready && state == EmulatorState::Running;
|
||||||
if state == EmulatorState::Running {
|
if state == EmulatorState::Running {
|
||||||
if ui.add_enabled(can_interact, Button::new("Pause")).clicked() {
|
if ui.add_enabled(is_ready, Button::new("Pause")).clicked() {
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
self.client.send_command(EmulatorCommand::Pause);
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
} else if ui
|
} else if ui.add_enabled(can_pause, Button::new("Resume")).clicked() {
|
||||||
.add_enabled(can_interact, Button::new("Resume"))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.client.send_command(EmulatorCommand::Resume);
|
self.client.send_command(EmulatorCommand::Resume);
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.add_enabled(can_interact, Button::new("Reset")).clicked() {
|
if ui.add_enabled(is_ready, Button::new("Reset")).clicked() {
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::Reset(self.sim_id));
|
.send_command(EmulatorCommand::Reset(self.sim_id));
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
|
|
Loading…
Reference in New Issue