use std::collections::{HashMap, HashSet};
use super::{
instruction::{Instruction, Opcode},
register::Registers,
CpuBusProvider, CpuState,
};
#[derive(Default, Clone, Copy, PartialEq, Eq)]
struct EnabledBreakpoints {
step_over: bool,
step_out: bool,
normal: bool,
}
type InstructionTraceHandler = Box<dyn Fn(&Registers, &Instruction, bool)>;
pub struct Debugger {
paused: bool,
last_state: CpuState,
call_stack: Vec<u32>,
instruction_breakpoints: HashMap<u32, EnabledBreakpoints>,
write_breakpoints: HashSet<u32>,
read_breakpoints: HashSet<u32>,
in_breakpoint: bool,
step: bool,
step_over: bool,
instruction_trace_handler: Option<InstructionTraceHandler>,
last_instruction: Instruction,
}
impl Debugger {
pub(crate) fn new() -> Self {
Self {
paused: false,
last_state: CpuState::Normal,
call_stack: Vec::new(),
instruction_breakpoints: HashMap::new(),
write_breakpoints: HashSet::new(),
read_breakpoints: HashSet::new(),
in_breakpoint: false,
step: false,
step_over: false,
instruction_trace_handler: None,
last_instruction: Instruction::from_u32(0, 0),
}
}
pub(crate) fn set_pause(&mut self, paused: bool) {
self.paused = paused;
}
pub(crate) fn paused(&self) -> bool {
self.paused
}
pub(crate) fn last_state(&self) -> CpuState {
self.last_state
}
pub(crate) fn clear_state(&mut self) {
self.last_state = CpuState::Normal;
self.paused = false;
}
pub(crate) fn handle_pending_processing<P: CpuBusProvider>(
&mut self,
bus: &mut P,
regs: &Registers,
jumping: bool,
) {
if self.step_over {
self.step_over = false;
let offset = if jumping { 4 } else { 0 };
let instr = bus.read_u32(regs.pc - offset).unwrap();
let instr = Instruction::from_u32(instr, regs.pc);
if let Opcode::Jal | Opcode::Jalr = instr.opcode {
self.instruction_breakpoints
.entry(regs.pc + 8 - offset)
.or_default()
.step_over = true;
} else {
self.step = true;
}
}
}
pub(crate) fn trace_exception(&mut self, return_addr: u32) {
self.call_stack.push(return_addr);
}
pub(crate) fn trace_instruction(
&mut self,
regs: &Registers,
jumping: bool,
instruction: &Instruction,
) -> bool {
if let Some(breakpoints_data) = self.instruction_breakpoints.get_mut(®s.pc) {
if breakpoints_data.step_over {
breakpoints_data.step_over = false;
if *breakpoints_data == EnabledBreakpoints::default() {
self.instruction_breakpoints.remove(®s.pc);
}
self.set_pause(true);
self.last_state = CpuState::StepOver;
return true;
}
if breakpoints_data.step_out {
breakpoints_data.step_out = false;
if *breakpoints_data == EnabledBreakpoints::default() {
self.instruction_breakpoints.remove(®s.pc);
}
self.set_pause(true);
self.last_state = CpuState::StepOut;
return true;
}
if !self.in_breakpoint && breakpoints_data.normal {
self.in_breakpoint = true;
self.set_pause(true);
self.last_state = CpuState::InstructionBreakpoint(regs.pc);
return true;
}
}
self.in_breakpoint = false;
if jumping {
match self.last_instruction.opcode {
Opcode::Jal | Opcode::Jalr => {
self.call_stack.push(self.last_instruction.pc + 8);
}
Opcode::Jr => {
let target = regs.read_general(self.last_instruction.rs_raw);
if !self.call_stack.is_empty() {
let mut c = 1;
for x in self.call_stack.iter().rev() {
if *x == target {
self.call_stack.truncate(self.call_stack.len() - c);
break;
}
c += 1;
}
}
}
_ => {}
}
}
if let Some(handler) = &self.instruction_trace_handler {
handler(regs, instruction, jumping);
}
if self.step {
self.set_pause(true);
self.step = false;
self.last_state = CpuState::Step;
}
self.last_instruction = instruction.clone();
false
}
pub(crate) fn trace_write(&mut self, addr: u32, bits: u8) {
if !self.write_breakpoints.is_empty() && self.write_breakpoints.contains(&addr) {
self.set_pause(true);
self.last_state = CpuState::WriteBreakpoint { addr, bits };
}
}
pub(crate) fn trace_read(&mut self, addr: u32, bits: u8) {
if !self.read_breakpoints.is_empty() && self.read_breakpoints.contains(&addr) {
self.set_pause(true);
self.last_state = CpuState::ReadBreakpoint { addr, bits };
}
}
}
impl Debugger {
pub fn single_step(&mut self) {
self.step = true;
}
pub fn step_over(&mut self) {
self.step_over = true;
}
pub fn step_out(&mut self) {
let Some(last_frame) = self.call_stack.last() else {
return;
};
self.instruction_breakpoints
.entry(*last_frame)
.or_default()
.step_out = true;
}
pub fn set_instruction_trace_handler(&mut self, handler: Option<InstructionTraceHandler>) {
self.instruction_trace_handler = handler;
}
pub fn add_breakpoint(&mut self, address: u32) {
self.instruction_breakpoints
.entry(address)
.or_default()
.normal = true;
}
pub fn remove_breakpoint(&mut self, address: u32) -> bool {
if let Some(v) = self.instruction_breakpoints.get_mut(&address) {
v.normal = false;
if *v == EnabledBreakpoints::default() {
self.instruction_breakpoints.remove(&address);
}
true
} else {
false
}
}
pub fn add_write_breakpoint(&mut self, address: u32) {
self.write_breakpoints.insert(address);
}
pub fn remove_write_breakpoint(&mut self, address: u32) -> bool {
self.write_breakpoints.remove(&address)
}
pub fn add_read_breakpoint(&mut self, address: u32) {
self.read_breakpoints.insert(address);
}
pub fn remove_read_breakpoint(&mut self, address: u32) -> bool {
self.read_breakpoints.remove(&address)
}
pub fn instruction_breakpoints(&self) -> HashSet<u32> {
self.instruction_breakpoints
.iter()
.filter_map(|(k, v)| if v.normal { Some(*k) } else { None })
.collect::<HashSet<_>>()
}
pub fn write_breakpoints(&self) -> &HashSet<u32> {
&self.write_breakpoints
}
pub fn read_breakpoints(&self) -> &HashSet<u32> {
&self.read_breakpoints
}
pub fn call_stack(&self) -> &[u32] {
&self.call_stack
}
}