Implement stepping

This commit is contained in:
Simon Gellis 2025-01-05 13:44:59 -05:00
parent 6fd8d8f5cf
commit 84f2cf7ece
3 changed files with 58 additions and 11 deletions

View File

@ -315,7 +315,7 @@ impl Emulator {
fn stop_debugging(&mut self, sim_id: SimId) { fn stop_debugging(&mut self, sim_id: SimId) {
if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
sim.clear_breakpoints(); sim.clear_debug_state();
} }
self.debuggers.remove(&sim_id); self.debuggers.remove(&sim_id);
if self.debuggers.is_empty() { 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 { let Some(debugger) = self.debuggers.get_mut(&sim_id) else {
self.stop_debugging(sim_id); self.stop_debugging(sim_id);
return; return false;
}; };
debugger.stop_reason = None; 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) { pub fn run(&mut self) {
@ -412,6 +421,7 @@ impl Emulator {
}; };
if let Some(reason) = sim.stop_reason() { if let Some(reason) = sim.stop_reason() {
let stop_reason = match reason { let stop_reason = match reason {
StopReason::Stepped => DebugStopReason::Trace,
StopReason::Breakpoint => DebugStopReason::Breakpoint, StopReason::Breakpoint => DebugStopReason::Breakpoint,
}; };
self.debug_stop(sim_id, stop_reason); self.debug_stop(sim_id, stop_reason);
@ -509,6 +519,9 @@ impl Emulator {
EmulatorCommand::DebugContinue(sim_id) => { EmulatorCommand::DebugContinue(sim_id) => {
self.debug_continue(sim_id); self.debug_continue(sim_id);
} }
EmulatorCommand::DebugStep(sim_id) => {
self.debug_step(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;
@ -596,6 +609,7 @@ pub enum EmulatorCommand {
StopDebugging(SimId), StopDebugging(SimId),
DebugInterrupt(SimId), DebugInterrupt(SimId),
DebugContinue(SimId), DebugContinue(SimId),
DebugStep(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>>),
AddBreakpoint(SimId, u32), AddBreakpoint(SimId, u32),
@ -628,6 +642,8 @@ type DebugSender = tokio::sync::mpsc::UnboundedSender<DebugEvent>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugStopReason { pub enum DebugStopReason {
// We are stepping
Trace,
// We hit a breakpoint // We hit a breakpoint
Breakpoint, Breakpoint,
// The debugger told us to pause // The debugger told us to pause

View File

@ -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. // 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() }; let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
if data.breakpoints.binary_search(&address).is_err() { let mut stopped = 0;
return 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); stopped
1
} }
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4; const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
@ -162,6 +168,7 @@ pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
struct VBState { struct VBState {
frame_seen: bool, frame_seen: bool,
stop_reason: Option<StopReason>, stop_reason: Option<StopReason>,
step_from: Option<u32>,
breakpoints: Vec<u32>, breakpoints: Vec<u32>,
} }
@ -172,6 +179,7 @@ pub struct Sim {
pub enum StopReason { pub enum StopReason {
Breakpoint, Breakpoint,
Stepped,
} }
impl Sim { impl Sim {
@ -183,12 +191,14 @@ impl Sim {
let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast(); let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast();
unsafe { vb_init(sim) }; unsafe { vb_init(sim) };
unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) }; unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) };
unsafe { vb_set_keys(sim, VBKey::SGN.bits()) };
unsafe { vb_reset(sim) }; unsafe { vb_reset(sim) };
// set up userdata // set up userdata
let state = VBState { let state = VBState {
frame_seen: false, frame_seen: false,
stop_reason: None, stop_reason: None,
step_from: None,
breakpoints: vec![], breakpoints: vec![],
}; };
unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) }; unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
@ -361,21 +371,35 @@ impl Sim {
let data = self.get_state(); let data = self.get_state();
if let Ok(index) = data.breakpoints.binary_search(&address) { if let Ok(index) = data.breakpoints.binary_search(&address) {
data.breakpoints.remove(index); 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) }; 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(); 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(); data.breakpoints.clear();
unsafe { vb_set_execute_callback(self.sim, None) }; unsafe { vb_set_execute_callback(self.sim, None) };
} }
pub fn stop_reason(&mut self) -> Option<StopReason> { pub fn stop_reason(&mut self) -> Option<StopReason> {
let data = self.get_state(); 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 { fn get_state(&mut self) -> &mut VBState {

View File

@ -258,6 +258,12 @@ impl GdbConnection {
self.stop_reason = None; self.stop_reason = None;
// Don't send a response until we hit a breakpoint or get interrupted // Don't send a response until we hit a breakpoint or get interrupted
return Ok(None); 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") { } else if req.match_str("p") {
let mut read_register = || { let mut read_register = || {
let register_index = req.match_hex::<usize>()?; let register_index = req.match_hex::<usize>()?;
@ -348,7 +354,8 @@ impl Drop for GdbConnection {
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str { fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
match reason { 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;", Some(DebugStopReason::Trapped) => "T05;swbreak;thread:p1.t1;threads:p1.t1;reason:trap;",
None => "T00;thread:p1.t1;threads:p1.t1;", None => "T00;thread:p1.t1;threads:p1.t1;",
} }