From d6eb8ec7efd75d50976fd83e85ee9078d0291455 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sun, 5 Jan 2025 00:47:58 -0500 Subject: [PATCH] Implement execute breakpoints --- src/emulator.rs | 50 +++++++++++++++++---- src/emulator/shrooms_vb_core.rs | 78 +++++++++++++++++++++++++++++---- src/gdbserver.rs | 19 +++++++- 3 files changed, 129 insertions(+), 18 deletions(-) diff --git a/src/emulator.rs b/src/emulator.rs index 3de6a38..9bc37b3 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -18,7 +18,7 @@ use bytemuck::NoUninit; use egui_toast::{Toast, ToastKind, ToastOptions}; use crate::{audio::Audio, graphics::TextureSink}; -use shrooms_vb_core::{Sim, EXPECTED_FRAME_SIZE}; +use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE}; pub use shrooms_vb_core::{VBKey, VBRegister}; mod shrooms_vb_core; @@ -302,6 +302,9 @@ impl Emulator { } fn stop_debugging(&mut self, sim_id: SimId) { + if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { + sim.clear_breakpoints(); + } self.debuggers.remove(&sim_id); if self.debuggers.is_empty() { let _ = self.state.compare_exchange( @@ -314,17 +317,17 @@ impl Emulator { } fn debug_interrupt(&mut self, sim_id: SimId) { + self.debug_stop(sim_id, DebugStopReason::Trapped); + } + + fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) { 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() - { + if debugger.stop_reason != Some(reason) { + debugger.stop_reason = Some(reason); + if debugger.sender.send(DebugEvent::Stopped(reason)).is_err() { self.stop_debugging(sim_id); } } @@ -389,6 +392,21 @@ impl Emulator { self.sims[SimId::Player2.to_index()].emulate(); } + // Debug state + if state == EmulatorState::Debugging { + for sim_id in SimId::values() { + let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { + continue; + }; + if let Some(reason) = sim.stop_reason() { + let stop_reason = match reason { + StopReason::Breakpoint => DebugStopReason::Breakpoint, + }; + self.debug_stop(sim_id, stop_reason); + } + } + } + // Video for sim_id in SimId::values() { let Some(renderer) = self.renderers.get_mut(&sim_id) else { @@ -493,6 +511,18 @@ impl Emulator { sim.read_memory(addresses, &mut buffer); let _ = done.send(buffer); } + EmulatorCommand::AddBreakpoint(sim_id, address) => { + let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { + return; + }; + sim.add_breakpoint(address); + } + EmulatorCommand::RemoveBreakpoint(sim_id, address) => { + let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { + return; + }; + sim.remove_breakpoint(address); + } EmulatorCommand::SetAudioEnabled(p1, p2) => { self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release); self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release); @@ -556,6 +586,8 @@ pub enum EmulatorCommand { DebugContinue(SimId), ReadRegister(SimId, VBRegister, oneshot::Sender), ReadMemory(SimId, Range, Vec, oneshot::Sender>), + AddBreakpoint(SimId, u32), + RemoveBreakpoint(SimId, u32), SetAudioEnabled(bool, bool), Link, Unlink, @@ -584,6 +616,8 @@ type DebugSender = tokio::sync::mpsc::UnboundedSender; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DebugStopReason { + // We hit a breakpoint + Breakpoint, // The debugger told us to pause Trapped, } diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 0fffe62..e027869 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -63,6 +63,8 @@ pub enum VBRegister { } type OnFrame = extern "C" fn(sim: *mut VB) -> c_int; +type OnExecute = + extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int; #[link(name = "vb")] extern "C" { @@ -109,8 +111,10 @@ extern "C" { fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int; #[link_name = "vbSetCartROM"] fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int; + #[link_name = "vbSetExecuteCallback"] + fn vb_set_execute_callback(sim: *mut VB, callback: Option); #[link_name = "vbSetFrameCallback"] - fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame); + fn vb_set_frame_callback(sim: *mut VB, callback: Option); #[link_name = "vbSetKeys"] fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; #[link_name = "vbSetOption"] @@ -130,7 +134,7 @@ extern "C" { fn vb_size_of() -> usize; } -extern "C" fn on_frame(sim: *mut VB) -> i32 { +extern "C" fn on_frame(sim: *mut VB) -> c_int { // SAFETY: the *mut VB owns its userdata. // There is no way for the userdata to be null or otherwise invalid. let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; @@ -138,12 +142,27 @@ extern "C" fn on_frame(sim: *mut VB) -> i32 { 1 } +extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int { + // SAFETY: the *mut VB owns its userdata. + // There is no way for the userdata to be null or otherwise invalid. + let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; + + if data.breakpoints.binary_search(&address).is_err() { + return 0; + } + + data.stop_reason = Some(StopReason::Breakpoint); + 1 +} + const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4; const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2; pub const EXPECTED_FRAME_SIZE: usize = 834 * 2; struct VBState { frame_seen: bool, + stop_reason: Option, + breakpoints: Vec, } #[repr(transparent)] @@ -151,8 +170,9 @@ pub struct Sim { sim: *mut VB, } -// SAFETY: the memory pointed to by sim is valid -unsafe impl Send for Sim {} +pub enum StopReason { + Breakpoint, +} impl Sim { pub fn new() -> Self { @@ -166,9 +186,13 @@ impl Sim { unsafe { vb_reset(sim) }; // set up userdata - let state = VBState { frame_seen: false }; + let state = VBState { + frame_seen: false, + stop_reason: None, + breakpoints: vec![], + }; unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) }; - unsafe { vb_set_frame_callback(sim, on_frame) }; + unsafe { vb_set_frame_callback(sim, Some(on_frame)) }; // set up audio buffer let audio_buffer = vec![0.0f32; AUDIO_CAPACITY_FLOATS]; @@ -257,9 +281,7 @@ impl Sim { } pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool { - // SAFETY: the *mut VB owns its userdata. - // There is no way for the userdata to be null or otherwise invalid. - let data: &mut VBState = unsafe { &mut *vb_get_user_data(self.sim).cast() }; + let data = self.get_state(); if !data.frame_seen { return false; } @@ -323,6 +345,44 @@ impl Sim { into.push(byte as u8); } } + + pub fn add_breakpoint(&mut self, address: u32) { + let data = self.get_state(); + if let Err(index) = data.breakpoints.binary_search(&address) { + data.breakpoints.insert(index, address); + } + + unsafe { + vb_set_execute_callback(self.sim, Some(on_execute)); + } + } + + pub fn remove_breakpoint(&mut self, address: u32) { + let data = self.get_state(); + if let Ok(index) = data.breakpoints.binary_search(&address) { + data.breakpoints.remove(index); + if data.breakpoints.is_empty() { + unsafe { vb_set_execute_callback(self.sim, None) }; + } + } + } + + pub fn clear_breakpoints(&mut self) { + let data = self.get_state(); + data.breakpoints.clear(); + unsafe { vb_set_execute_callback(self.sim, None) }; + } + + pub fn stop_reason(&mut self) -> Option { + let data = self.get_state(); + data.stop_reason.take() + } + + fn get_state(&mut self) -> &mut VBState { + // SAFETY: the *mut VB owns its userdata. + // There is no way for the userdata to be null or otherwise invalid. + unsafe { &mut *vb_get_user_data(self.sim).cast() } + } } impl Drop for Sim { diff --git a/src/gdbserver.rs b/src/gdbserver.rs index ffeaf13..0223fcc 100644 --- a/src/gdbserver.rs +++ b/src/gdbserver.rs @@ -307,6 +307,22 @@ impl GdbConnection { } else { self.response() } + } else if req.match_str("Z0,") { + if let Some(address) = req.match_hex() { + self.client + .send_command(EmulatorCommand::AddBreakpoint(self.sim_id, address)); + self.response().write_str("OK") + } else { + self.response() + } + } else if req.match_str("z0,") { + if let Some(address) = req.match_hex() { + self.client + .send_command(EmulatorCommand::RemoveBreakpoint(self.sim_id, address)); + self.response().write_str("OK") + } else { + self.response() + } } else { // unrecognized command self.response() @@ -331,7 +347,8 @@ impl Drop for GdbConnection { fn debug_stop_reason_string(reason: Option) -> &'static str { match reason { - Some(DebugStopReason::Trapped) => "T05;thread:p1.t1;threads:p1.t1;reason:trap;", + Some(DebugStopReason::Breakpoint) => "T05;thread:p1.t1;threads;p1.t1;reason:breakpoint", + Some(DebugStopReason::Trapped) => "T05;swbreak;thread:p1.t1;threads:p1.t1;reason:trap;", None => "T00;thread:p1.t1;threads:p1.t1;", } }