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>,
 | 
			
		||||
    renderers: HashMap<SimId, TextureSink>,
 | 
			
		||||
    messages: HashMap<SimId, mpsc::Sender<Toast>>,
 | 
			
		||||
    debuggers: HashMap<SimId, DebugInfo>,
 | 
			
		||||
    eye_contents: Vec<u8>,
 | 
			
		||||
    audio_samples: Vec<f32>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +175,7 @@ impl Emulator {
 | 
			
		|||
            linked,
 | 
			
		||||
            renderers: HashMap::new(),
 | 
			
		||||
            messages: HashMap::new(),
 | 
			
		||||
            debuggers: HashMap::new(),
 | 
			
		||||
            eye_contents: vec![0u8; 384 * 224 * 2],
 | 
			
		||||
            audio_samples: vec![0.0; EXPECTED_FRAME_SIZE],
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -288,6 +290,48 @@ impl Emulator {
 | 
			
		|||
        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) {
 | 
			
		||||
        loop {
 | 
			
		||||
            let idle = self.tick();
 | 
			
		||||
| 
						 | 
				
			
			@ -320,8 +364,14 @@ impl Emulator {
 | 
			
		|||
        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 state = self.state.load(Ordering::Acquire);
 | 
			
		||||
        let p1_running = state == EmulatorState::Running && p1_state == SimState::Ready;
 | 
			
		||||
        let p2_running = state == EmulatorState::Running && p2_state == SimState::Ready;
 | 
			
		||||
        // Don't emulate if the state is "paused", or if any sim is paused in the debugger
 | 
			
		||||
        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;
 | 
			
		||||
        if p1_running && p2_running {
 | 
			
		||||
            Sim::emulate_many(&mut self.sims);
 | 
			
		||||
| 
						 | 
				
			
			@ -405,6 +455,18 @@ impl Emulator {
 | 
			
		|||
            EmulatorCommand::Resume => {
 | 
			
		||||
                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) => {
 | 
			
		||||
                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
			
		||||
                    return;
 | 
			
		||||
| 
						 | 
				
			
			@ -476,6 +538,10 @@ pub enum EmulatorCommand {
 | 
			
		|||
    StopSecondSim,
 | 
			
		||||
    Pause,
 | 
			
		||||
    Resume,
 | 
			
		||||
    StartDebugging(SimId, DebugSender),
 | 
			
		||||
    StopDebugging(SimId),
 | 
			
		||||
    DebugInterrupt(SimId),
 | 
			
		||||
    DebugContinue(SimId),
 | 
			
		||||
    ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
 | 
			
		||||
    ReadMemory(SimId, Range<u32>, Vec<u8>, oneshot::Sender<Vec<u8>>),
 | 
			
		||||
    SetAudioEnabled(bool, bool),
 | 
			
		||||
| 
						 | 
				
			
			@ -499,6 +565,24 @@ pub enum SimState {
 | 
			
		|||
pub enum EmulatorState {
 | 
			
		||||
    Paused,
 | 
			
		||||
    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)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										248
									
								
								src/gdbserver.rs
								
								
								
								
							
							
						
						
									
										248
									
								
								src/gdbserver.rs
								
								
								
								
							| 
						 | 
				
			
			@ -1,22 +1,19 @@
 | 
			
		|||
use anyhow::{bail, Result};
 | 
			
		||||
use registers::REGISTERS;
 | 
			
		||||
use request::{Request, RequestKind};
 | 
			
		||||
use request::{Request, RequestKind, RequestSource};
 | 
			
		||||
use response::Response;
 | 
			
		||||
use std::{
 | 
			
		||||
    sync::{Arc, Mutex},
 | 
			
		||||
    thread,
 | 
			
		||||
};
 | 
			
		||||
use tokio::{
 | 
			
		||||
    io::{AsyncWriteExt as _, BufReader, BufWriter},
 | 
			
		||||
    net::{
 | 
			
		||||
        tcp::{OwnedReadHalf, OwnedWriteHalf},
 | 
			
		||||
        TcpListener, TcpStream,
 | 
			
		||||
    },
 | 
			
		||||
    io::{AsyncWriteExt as _, BufReader},
 | 
			
		||||
    net::{TcpListener, TcpStream},
 | 
			
		||||
    select,
 | 
			
		||||
    sync::oneshot,
 | 
			
		||||
    sync::{mpsc, oneshot},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
 | 
			
		||||
use crate::emulator::{DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId};
 | 
			
		||||
 | 
			
		||||
mod registers;
 | 
			
		||||
mod request;
 | 
			
		||||
| 
						 | 
				
			
			@ -81,8 +78,8 @@ async fn run_server(
 | 
			
		|||
    let Some(stream) = try_connect(port, status).await else {
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    let connection = GdbConnection::new(sim_id, client, stream);
 | 
			
		||||
    match connection.run().await {
 | 
			
		||||
    let mut connection = GdbConnection::new(sim_id, client);
 | 
			
		||||
    match connection.run(stream).await {
 | 
			
		||||
        Ok(()) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Stopped;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -130,122 +127,151 @@ impl GdbServerStatus {
 | 
			
		|||
struct GdbConnection {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    client: EmulatorClient,
 | 
			
		||||
    stream_in: BufReader<OwnedReadHalf>,
 | 
			
		||||
    stream_out: BufWriter<OwnedWriteHalf>,
 | 
			
		||||
    ack_messages: bool,
 | 
			
		||||
    request_buf: Vec<u8>,
 | 
			
		||||
    stop_reason: Option<DebugStopReason>,
 | 
			
		||||
    response_buf: Option<Vec<u8>>,
 | 
			
		||||
    memory_buf: Option<Vec<u8>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GdbConnection {
 | 
			
		||||
    fn new(sim_id: SimId, client: EmulatorClient, stream: TcpStream) -> Self {
 | 
			
		||||
        let (rx, tx) = stream.into_split();
 | 
			
		||||
    fn new(sim_id: SimId, client: EmulatorClient) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            sim_id,
 | 
			
		||||
            client,
 | 
			
		||||
            stream_in: BufReader::new(rx),
 | 
			
		||||
            stream_out: BufWriter::new(tx),
 | 
			
		||||
            ack_messages: true,
 | 
			
		||||
            request_buf: vec![],
 | 
			
		||||
            stop_reason: None,
 | 
			
		||||
            response_buf: None,
 | 
			
		||||
            memory_buf: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    async fn run(mut self) -> Result<()> {
 | 
			
		||||
        println!("Connected for {}", self.sim_id);
 | 
			
		||||
        self.client.send_command(EmulatorCommand::Pause);
 | 
			
		||||
    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 mut req = Request::read(&mut self.stream_in, &mut self.request_buf).await?;
 | 
			
		||||
            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::Pause);
 | 
			
		||||
                let res = self
 | 
			
		||||
                    .response()
 | 
			
		||||
                    .write_str("T05;thread:p1.t1;threads:p1.t1;reason:trap;");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
                continue;
 | 
			
		||||
            self.client
 | 
			
		||||
                .send_command(EmulatorCommand::DebugInterrupt(self.sim_id));
 | 
			
		||||
            return Ok(None); // we'll send a message when the emulator reports it has stopped
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if req.match_str("QStartNoAckMode") {
 | 
			
		||||
        let res = if req.match_str("QStartNoAckMode") {
 | 
			
		||||
            let res = self.response().write_str("OK");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            self.ack_messages = false;
 | 
			
		||||
            res
 | 
			
		||||
        } 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")
 | 
			
		||||
            self.response()
 | 
			
		||||
                .write_str("multiprocess+;swbreak+;vContSupported+;PacketSize=10000")
 | 
			
		||||
        } else if req
 | 
			
		||||
            .match_some_str([
 | 
			
		||||
                "QThreadSuffixSupported",
 | 
			
		||||
                "QListThreadsInStopReply",
 | 
			
		||||
                "QEnableErrorStrings",
 | 
			
		||||
            ])
 | 
			
		||||
            .is_some()
 | 
			
		||||
        {
 | 
			
		||||
                let res = self.response().write_str("OK");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            self.response().write_str("OK")
 | 
			
		||||
        } else if req.match_str("qHostInfo") {
 | 
			
		||||
                let res = self.response().write_str(&format!(
 | 
			
		||||
            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!(
 | 
			
		||||
            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?;
 | 
			
		||||
            if let Some(reg_info) = get_reg_info() {
 | 
			
		||||
                self.response().write_str(®_info.to_description())
 | 
			
		||||
            } else {
 | 
			
		||||
                self.response()
 | 
			
		||||
            }
 | 
			
		||||
        } else if req.match_str("vCont?") {
 | 
			
		||||
                let res = self.response().write_str("vCont;c;");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            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.
 | 
			
		||||
                let res = self.response().write_str("QCp1.t1");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            self.response().write_str("QCp1.t1")
 | 
			
		||||
        } else if req.match_str("qfThreadInfo") {
 | 
			
		||||
                let res = self.response().write_str("mp1.t1");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            self.response().write_str("mp1.t1")
 | 
			
		||||
        } else if req.match_str("qsThreadInfo") {
 | 
			
		||||
                let res = self.response().write_str("l");
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            self.response().write_str("l")
 | 
			
		||||
        } 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);
 | 
			
		||||
            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,
 | 
			
		||||
                    ));
 | 
			
		||||
                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") {
 | 
			
		||||
            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(",") {
 | 
			
		||||
| 
						 | 
				
			
			@ -263,58 +289,29 @@ impl GdbConnection {
 | 
			
		|||
                ));
 | 
			
		||||
                rx.recv().ok()
 | 
			
		||||
            };
 | 
			
		||||
                let Some(memory) = read_memory() else {
 | 
			
		||||
                    self.send_empty().await?;
 | 
			
		||||
                    continue;
 | 
			
		||||
                };
 | 
			
		||||
            if let Some(memory) = read_memory() {
 | 
			
		||||
                let mut res = self.response();
 | 
			
		||||
                if op == "m" {
 | 
			
		||||
                    // send the hex-encoded byte stream
 | 
			
		||||
                    for byte in &memory {
 | 
			
		||||
                        res = res.write_hex(*byte);
 | 
			
		||||
                    }
 | 
			
		||||
                self.memory_buf = Some(memory);
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
            } else if req.match_str("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()
 | 
			
		||||
                };
 | 
			
		||||
                let Some(memory) = read_memory() else {
 | 
			
		||||
                    self.send_empty().await?;
 | 
			
		||||
                    continue;
 | 
			
		||||
                };
 | 
			
		||||
                let mut res = self.response();
 | 
			
		||||
                if memory.is_empty() {
 | 
			
		||||
                    res = res.write_str("OK");
 | 
			
		||||
                } else {
 | 
			
		||||
                    // send the raw byte stream
 | 
			
		||||
                    for byte in &memory {
 | 
			
		||||
                        res = res.write_byte(*byte);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                self.memory_buf = Some(memory);
 | 
			
		||||
                self.send(res).await?;
 | 
			
		||||
                res
 | 
			
		||||
            } else {
 | 
			
		||||
                self.response()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // unrecognized command
 | 
			
		||||
                self.send_empty().await?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn send_empty(&mut self) -> std::io::Result<()> {
 | 
			
		||||
        let res = self.response();
 | 
			
		||||
        self.send(res).await
 | 
			
		||||
            self.response()
 | 
			
		||||
        };
 | 
			
		||||
        Ok(Some(res))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn response(&mut self) -> Response {
 | 
			
		||||
| 
						 | 
				
			
			@ -323,15 +320,18 @@ impl GdbConnection {
 | 
			
		|||
            self.ack_messages,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    async fn send(&mut self, res: Response) -> std::io::Result<()> {
 | 
			
		||||
        let buffer = res.finish();
 | 
			
		||||
        match std::str::from_utf8(&buffer) {
 | 
			
		||||
            Ok(text) => println!("response: {text}"),
 | 
			
		||||
            Err(_) => println!("response: {buffer:02x?}"),
 | 
			
		||||
        }
 | 
			
		||||
        self.stream_out.write_all(&buffer).await?;
 | 
			
		||||
        self.response_buf = Some(buffer);
 | 
			
		||||
        self.stream_out.flush().await
 | 
			
		||||
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>) -> &'static str {
 | 
			
		||||
    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> {
 | 
			
		||||
    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,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
impl Request<'_> {
 | 
			
		||||
    pub fn match_str(&mut self, prefix: &str) -> bool {
 | 
			
		||||
        if let Some(new_buffer) = self.buffer.strip_prefix(prefix.as_bytes()) {
 | 
			
		||||
            self.buffer = new_buffer;
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +44,13 @@ impl<'a> Request<'a> {
 | 
			
		|||
        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> {
 | 
			
		||||
        match I::from_radix_16(self.buffer) {
 | 
			
		||||
            (_, 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| {
 | 
			
		||||
            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 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);
 | 
			
		||||
                    ui.close_menu();
 | 
			
		||||
                }
 | 
			
		||||
            } else if ui
 | 
			
		||||
                .add_enabled(can_interact, Button::new("Resume"))
 | 
			
		||||
                .clicked()
 | 
			
		||||
            {
 | 
			
		||||
            } else if ui.add_enabled(can_pause, Button::new("Resume")).clicked() {
 | 
			
		||||
                self.client.send_command(EmulatorCommand::Resume);
 | 
			
		||||
                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
 | 
			
		||||
                    .send_command(EmulatorCommand::Reset(self.sim_id));
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue