diff --git a/src/emulator.rs b/src/emulator.rs index b4d5ab8..b52cc75 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -315,7 +315,7 @@ 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(); + sim.clear_debug_state(); } self.debuggers.remove(&sim_id); if self.debuggers.is_empty() { @@ -345,12 +345,21 @@ impl Emulator { } } - fn debug_continue(&mut self, sim_id: SimId) { + fn debug_continue(&mut self, sim_id: SimId) -> bool { let Some(debugger) = self.debuggers.get_mut(&sim_id) else { self.stop_debugging(sim_id); - return; + return false; }; debugger.stop_reason = None; + true + } + fn debug_step(&mut self, sim_id: SimId) { + if self.debug_continue(sim_id) { + let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { + return; + }; + sim.step(); + } } pub fn run(&mut self) { @@ -412,6 +421,7 @@ impl Emulator { }; if let Some(reason) = sim.stop_reason() { let stop_reason = match reason { + StopReason::Stepped => DebugStopReason::Trace, StopReason::Breakpoint => DebugStopReason::Breakpoint, }; self.debug_stop(sim_id, stop_reason); @@ -509,6 +519,9 @@ impl Emulator { EmulatorCommand::DebugContinue(sim_id) => { self.debug_continue(sim_id); } + EmulatorCommand::DebugStep(sim_id) => { + self.debug_step(sim_id); + } EmulatorCommand::ReadRegister(sim_id, register, done) => { let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { return; @@ -596,6 +609,7 @@ pub enum EmulatorCommand { StopDebugging(SimId), DebugInterrupt(SimId), DebugContinue(SimId), + DebugStep(SimId), ReadRegister(SimId, VBRegister, oneshot::Sender), ReadMemory(SimId, Range, Vec, oneshot::Sender>), AddBreakpoint(SimId, u32), @@ -628,6 +642,8 @@ type DebugSender = tokio::sync::mpsc::UnboundedSender; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DebugStopReason { + // We are stepping + Trace, // We hit a breakpoint Breakpoint, // The debugger told us to pause diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index e027869..5ce58de 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -147,12 +147,18 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: // 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; + let mut stopped = 0; + if data.step_from.is_some_and(|s| s != address) { + data.step_from = None; + data.stop_reason = Some(StopReason::Stepped); + stopped = 1; + } + if data.breakpoints.binary_search(&address).is_ok() { + data.stop_reason = Some(StopReason::Breakpoint); + stopped = 1; } - data.stop_reason = Some(StopReason::Breakpoint); - 1 + stopped } const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4; @@ -162,6 +168,7 @@ pub const EXPECTED_FRAME_SIZE: usize = 834 * 2; struct VBState { frame_seen: bool, stop_reason: Option, + step_from: Option, breakpoints: Vec, } @@ -172,6 +179,7 @@ pub struct Sim { pub enum StopReason { Breakpoint, + Stepped, } impl Sim { @@ -183,12 +191,14 @@ impl Sim { let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast(); unsafe { vb_init(sim) }; unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) }; + unsafe { vb_set_keys(sim, VBKey::SGN.bits()) }; unsafe { vb_reset(sim) }; // set up userdata let state = VBState { frame_seen: false, stop_reason: None, + step_from: None, breakpoints: vec![], }; unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) }; @@ -361,21 +371,35 @@ impl Sim { let data = self.get_state(); if let Ok(index) = data.breakpoints.binary_search(&address) { data.breakpoints.remove(index); - if data.breakpoints.is_empty() { + if data.step_from.is_none() && data.breakpoints.is_empty() { unsafe { vb_set_execute_callback(self.sim, None) }; } } } - pub fn clear_breakpoints(&mut self) { + pub fn step(&mut self) { + let current_pc = unsafe { vb_get_program_counter(self.sim) }; let data = self.get_state(); + data.step_from = Some(current_pc); + unsafe { + vb_set_execute_callback(self.sim, Some(on_execute)); + } + } + + pub fn clear_debug_state(&mut self) { + let data = self.get_state(); + data.step_from = None; 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() + let reason = data.stop_reason.take(); + if data.step_from.is_none() && data.breakpoints.is_empty() { + unsafe { vb_set_execute_callback(self.sim, None) }; + } + reason } fn get_state(&mut self) -> &mut VBState { diff --git a/src/gdbserver.rs b/src/gdbserver.rs index fd95938..7e2b5af 100644 --- a/src/gdbserver.rs +++ b/src/gdbserver.rs @@ -258,6 +258,12 @@ impl GdbConnection { 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("s") { + 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::()?; @@ -348,7 +354,8 @@ impl Drop for GdbConnection { fn debug_stop_reason_string(reason: Option) -> &'static str { match reason { - Some(DebugStopReason::Breakpoint) => "T05;thread:p1.t1;threads;p1.t1;reason:breakpoint", + Some(DebugStopReason::Trace) => "T05;thread:p1.t1;threads:p1.t1;reason:trace;", + 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;", }