use super::breakpoints::{BreakpointKind, BreakpointList};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TemporaryBreakpoint<I: Copy + PartialEq + Eq> {
pub pc: u16,
pub already_present: bool,
pub was_enabled_before: bool,
pub required_interrupt: Option<I>,
pub has_exited_required_interrupt: bool,
pub ignore_other_breakpoints: bool,
}
impl<I: Copy + PartialEq + Eq> TemporaryBreakpoint<I> {
pub fn new(pc: u16, already_present: bool, was_enabled_before: bool) -> Self {
Self {
pc,
already_present,
was_enabled_before,
required_interrupt: None,
has_exited_required_interrupt: true,
ignore_other_breakpoints: false,
}
}
pub fn new_for_interrupt(
pc: u16,
already_present: bool,
was_enabled_before: bool,
interrupt: I,
currently_in_interrupt: bool,
) -> Self {
Self {
pc,
already_present,
was_enabled_before,
required_interrupt: Some(interrupt),
has_exited_required_interrupt: !currently_in_interrupt,
ignore_other_breakpoints: true,
}
}
}
#[derive(Debug)]
pub struct DebuggerControllerCore<I: Copy + PartialEq + Eq> {
pub paused: bool,
pub debugger_open: bool,
pub breakpoints: BreakpointList,
pub temporary_breakpoint: Option<TemporaryBreakpoint<I>>,
pub breakpoint_ignore_once_at_pc: Option<u16>,
pub last_post_instruction_cycles: u64,
pub last_post_instruction_frame: u64,
}
impl<I: Copy + PartialEq + Eq> Default for DebuggerControllerCore<I> {
fn default() -> Self {
Self::new(&[], false)
}
}
impl<I: Copy + PartialEq + Eq> DebuggerControllerCore<I> {
pub fn new(config_breakpoints: &[BreakpointKind], debugger_enabled: bool) -> Self {
let mut breakpoints = BreakpointList::new();
for &kind in config_breakpoints {
breakpoints.add(kind);
}
Self {
paused: debugger_enabled,
debugger_open: debugger_enabled,
breakpoints,
temporary_breakpoint: None,
breakpoint_ignore_once_at_pc: None,
last_post_instruction_cycles: 0,
last_post_instruction_frame: 0,
}
}
pub fn is_paused(&self) -> bool {
self.paused
}
pub fn is_debugger_open(&self) -> bool {
self.debugger_open
}
pub fn breakpoints(&self) -> &BreakpointList {
&self.breakpoints
}
pub fn breakpoints_mut(&mut self) -> &mut BreakpointList {
&mut self.breakpoints
}
pub fn enter_debugger(&mut self) {
self.paused = true;
self.debugger_open = true;
}
pub fn continue_from_debugger(&mut self, current_pc: u16, cycles: u64, frame: u64) {
if self.breakpoints.has_enabled_pc_breakpoint_at(current_pc) {
self.breakpoint_ignore_once_at_pc = Some(current_pc);
}
self.last_post_instruction_cycles = cycles;
self.last_post_instruction_frame = frame;
self.paused = false;
self.debugger_open = false;
}
pub fn toggle_debugger(&mut self, current_pc: u16, cycles: u64, frame: u64) {
if self.debugger_open {
self.continue_from_debugger(current_pc, cycles, frame);
} else {
self.enter_debugger();
}
}
pub fn clear_temporary_breakpoint(&mut self) {
if let Some(tb) = self.temporary_breakpoint.take() {
if tb.already_present {
if !tb.was_enabled_before {
self.breakpoints.set_pc_breakpoint_enabled(tb.pc, false);
}
} else {
self.remove_pc_breakpoint(tb.pc);
}
}
}
pub fn set_temporary_breakpoint(&mut self, pc: u16) {
self.clear_temporary_breakpoint();
let already_present = self.breakpoints.has_pc_breakpoint_at(pc);
let was_enabled_before = if already_present {
self.breakpoints
.force_enable_pc_breakpoint_at(pc)
.unwrap_or(false)
} else {
self.add_pc_breakpoint(pc);
true
};
self.temporary_breakpoint = Some(TemporaryBreakpoint::new(
pc,
already_present,
was_enabled_before,
));
}
pub fn set_temporary_breakpoint_for_interrupt(
&mut self,
pc: u16,
interrupt: I,
currently_in_interrupt: bool,
) {
self.clear_temporary_breakpoint();
let already_present = self.breakpoints.has_pc_breakpoint_at(pc);
let was_enabled_before = if already_present {
self.breakpoints
.force_enable_pc_breakpoint_at(pc)
.unwrap_or(false)
} else {
self.add_pc_breakpoint(pc);
true
};
self.temporary_breakpoint = Some(TemporaryBreakpoint::new_for_interrupt(
pc,
already_present,
was_enabled_before,
interrupt,
currently_in_interrupt,
));
}
pub fn should_ignore_pc_breakpoint(&mut self, pc: u16) -> bool {
if self.breakpoint_ignore_once_at_pc == Some(pc) {
self.breakpoint_ignore_once_at_pc = None;
true
} else {
false
}
}
pub fn should_suppress_other_breakpoints(&self, pc: u16) -> bool {
if let Some(ref tb) = self.temporary_breakpoint {
tb.ignore_other_breakpoints && pc != tb.pc
} else {
false
}
}
pub fn update_interrupt_exit_tracking(&mut self, current_interrupt: Option<I>) {
if let Some(ref mut tb) = self.temporary_breakpoint
&& let Some(required) = tb.required_interrupt
&& !tb.has_exited_required_interrupt
&& current_interrupt != Some(required)
{
tb.has_exited_required_interrupt = true;
}
}
pub fn check_temporary_breakpoint_hit(
&mut self,
pc: u16,
current_interrupt: Option<I>,
) -> Option<bool> {
let tb = self.temporary_breakpoint.as_ref()?;
if tb.pc != pc {
return None;
}
let should_trigger = if let Some(required) = tb.required_interrupt {
tb.has_exited_required_interrupt && current_interrupt == Some(required)
} else {
true
};
Some(should_trigger)
}
pub fn cleanup_triggered_temporary_breakpoint(&mut self) {
if let Some(tb) = self.temporary_breakpoint.take() {
if tb.already_present {
if !tb.was_enabled_before {
self.breakpoints.set_pc_breakpoint_enabled(tb.pc, false);
}
} else {
self.remove_pc_breakpoint(tb.pc);
}
}
}
pub fn add_pc_breakpoint(&mut self, addr: u16) {
self.breakpoints.add(BreakpointKind::Pc(addr));
}
pub fn remove_pc_breakpoint(&mut self, addr: u16) {
if let Some(idx) = self
.breakpoints
.iter()
.position(|b| b.kind == BreakpointKind::Pc(addr))
{
self.breakpoints.remove(idx);
}
}
pub fn update_post_instruction_state(&mut self, cycles: u64, frame: u64) {
self.last_post_instruction_cycles = cycles;
self.last_post_instruction_frame = frame;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TestInterrupt;
#[test]
fn test_controller_core_new_default_state() {
let core: DebuggerControllerCore<TestInterrupt> = DebuggerControllerCore::new(&[], false);
assert!(!core.is_paused());
assert!(!core.is_debugger_open());
assert!(core.breakpoints().is_empty());
}
#[test]
fn test_controller_core_new_with_breakpoints() {
let core: DebuggerControllerCore<TestInterrupt> = DebuggerControllerCore::new(
&[BreakpointKind::Pc(0x8000), BreakpointKind::Cycle(1000)],
false,
);
assert_eq!(core.breakpoints().len(), 2);
}
#[test]
fn test_controller_core_debugger_enabled() {
let core: DebuggerControllerCore<TestInterrupt> = DebuggerControllerCore::new(&[], true);
assert!(core.is_paused());
assert!(core.is_debugger_open());
}
#[test]
fn test_enter_debugger() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
core.enter_debugger();
assert!(core.is_paused());
assert!(core.is_debugger_open());
}
#[test]
fn test_continue_from_debugger() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], true);
core.continue_from_debugger(0x8000, 100, 5);
assert!(!core.is_paused());
assert!(!core.is_debugger_open());
assert_eq!(core.last_post_instruction_cycles, 100);
assert_eq!(core.last_post_instruction_frame, 5);
}
#[test]
fn test_continue_from_debugger_with_breakpoint_at_pc() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[BreakpointKind::Pc(0x8000)], true);
core.continue_from_debugger(0x8000, 100, 5);
assert_eq!(core.breakpoint_ignore_once_at_pc, Some(0x8000));
}
#[test]
fn test_toggle_debugger() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
core.toggle_debugger(0x8000, 0, 0);
assert!(core.is_paused());
assert!(core.is_debugger_open());
core.toggle_debugger(0x8000, 0, 0);
assert!(!core.is_paused());
assert!(!core.is_debugger_open());
}
#[test]
fn test_temporary_breakpoint_new() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
core.set_temporary_breakpoint(0xC000);
assert!(core.temporary_breakpoint.is_some());
let tb = core.temporary_breakpoint.as_ref().unwrap();
assert_eq!(tb.pc, 0xC000);
assert!(!tb.already_present);
assert!(tb.was_enabled_before);
assert!(core.breakpoints.has_pc_breakpoint_at(0xC000));
}
#[test]
fn test_temporary_breakpoint_with_existing() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[BreakpointKind::Pc(0xC000)], false);
core.breakpoints.disable(0);
core.set_temporary_breakpoint(0xC000);
let tb = core.temporary_breakpoint.as_ref().unwrap();
assert!(tb.already_present);
assert!(!tb.was_enabled_before);
}
#[test]
fn test_clear_temporary_breakpoint_removes_added() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
core.set_temporary_breakpoint(0xC000);
assert!(core.breakpoints.has_pc_breakpoint_at(0xC000));
core.clear_temporary_breakpoint();
assert!(!core.breakpoints.has_pc_breakpoint_at(0xC000));
assert!(core.temporary_breakpoint.is_none());
}
#[test]
fn test_clear_temporary_breakpoint_restores_disabled() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[BreakpointKind::Pc(0xC000)], false);
core.breakpoints.disable(0);
core.set_temporary_breakpoint(0xC000);
assert!(core.breakpoints.iter().next().unwrap().enabled);
core.clear_temporary_breakpoint();
assert!(!core.breakpoints.iter().next().unwrap().enabled);
}
#[test]
fn test_should_ignore_pc_breakpoint() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
core.breakpoint_ignore_once_at_pc = Some(0x8000);
assert!(core.should_ignore_pc_breakpoint(0x8000));
assert!(core.breakpoint_ignore_once_at_pc.is_none());
assert!(!core.should_ignore_pc_breakpoint(0x8000));
core.breakpoint_ignore_once_at_pc = Some(0x8000);
assert!(!core.should_ignore_pc_breakpoint(0x9000));
}
#[test]
fn test_should_suppress_other_breakpoints() {
let mut core: DebuggerControllerCore<TestInterrupt> =
DebuggerControllerCore::new(&[], false);
assert!(!core.should_suppress_other_breakpoints(0x8000));
core.set_temporary_breakpoint(0xC000);
assert!(!core.should_suppress_other_breakpoints(0x8000));
core.set_temporary_breakpoint_for_interrupt(0xC000, TestInterrupt, false);
assert!(core.should_suppress_other_breakpoints(0x8000));
assert!(!core.should_suppress_other_breakpoints(0xC000)); }
}