trapezoid_core/cpu/
debugger.rs

1use std::collections::{HashMap, HashSet};
2
3use super::{
4    instruction::{Instruction, Opcode},
5    register::Registers,
6    CpuBusProvider, CpuState,
7};
8
9#[derive(Default, Clone, Copy, PartialEq, Eq)]
10struct EnabledBreakpoints {
11    step_over: bool,
12    step_out: bool,
13    normal: bool,
14}
15
16type InstructionTraceHandler = Box<dyn Fn(&Registers, &Instruction, bool)>;
17
18pub struct Debugger {
19    paused: bool,
20    last_state: CpuState,
21
22    call_stack: Vec<u32>,
23
24    instruction_breakpoints: HashMap<u32, EnabledBreakpoints>,
25    write_breakpoints: HashSet<u32>,
26    read_breakpoints: HashSet<u32>,
27    // currently on top of breakpoint, so ignore it and continue when unpaused
28    // so that we don't get stuck in one instruction.
29    in_breakpoint: bool,
30    // allow to execute one instruction only
31    step: bool,
32    step_over: bool,
33
34    instruction_trace_handler: Option<InstructionTraceHandler>,
35
36    last_instruction: Instruction,
37}
38
39impl Debugger {
40    pub(crate) fn new() -> Self {
41        Self {
42            paused: false,
43            last_state: CpuState::Normal,
44
45            call_stack: Vec::new(),
46
47            instruction_breakpoints: HashMap::new(),
48            write_breakpoints: HashSet::new(),
49            read_breakpoints: HashSet::new(),
50            in_breakpoint: false,
51            step: false,
52            step_over: false,
53            instruction_trace_handler: None,
54
55            last_instruction: Instruction::from_u32(0, 0),
56        }
57    }
58
59    pub(crate) fn set_pause(&mut self, paused: bool) {
60        self.paused = paused;
61    }
62
63    pub(crate) fn paused(&self) -> bool {
64        self.paused
65    }
66
67    pub(crate) fn last_state(&self) -> CpuState {
68        self.last_state
69    }
70
71    pub(crate) fn clear_state(&mut self) {
72        self.last_state = CpuState::Normal;
73        self.paused = false;
74    }
75
76    /// Perform some processing that is required by the `Debugger` but require access
77    /// to the `bus`, since we don't have that normally unless we are inside
78    /// the `clock` function in the cpu.
79    ///
80    /// so outside code that wants to use the `Debugger` that require `bus` access, we will stack
81    /// those operations and perform them here.
82    ///
83    /// This is called first by the `clock` function in the `Cpu`.
84    ///
85    pub(crate) fn handle_pending_processing<P: CpuBusProvider>(
86        &mut self,
87        bus: &mut P,
88        regs: &Registers,
89        jumping: bool,
90    ) {
91        // need to step over
92        if self.step_over {
93            self.step_over = false;
94
95            // check that the instruction we just executed is `Jal/r` and we are in the middle
96            // of jump
97            //
98            // If so, we need to check the previous instruction (offset -4)
99            // and if its a match, we need to break in the next instruction (we are in the middle
100            // of jump) (+4)
101            //
102            // Otherwise, we will break on the instruction after the jump (+8)
103            let offset = if jumping { 4 } else { 0 };
104
105            // PC is always word aligned
106            let instr = bus.read_u32(regs.pc - offset).unwrap();
107            let instr = Instruction::from_u32(instr, regs.pc);
108
109            // check that the instruction we are about to execute is `Jal/r`
110            if let Opcode::Jal | Opcode::Jalr = instr.opcode {
111                self.instruction_breakpoints
112                    .entry(regs.pc + 8 - offset)
113                    .or_default()
114                    .step_over = true;
115            } else {
116                self.step = true;
117            }
118        }
119    }
120
121    pub(crate) fn trace_exception(&mut self, return_addr: u32) {
122        self.call_stack.push(return_addr);
123    }
124
125    pub(crate) fn trace_instruction(
126        &mut self,
127        regs: &Registers,
128        jumping: bool,
129        instruction: &Instruction,
130    ) -> bool {
131        if let Some(breakpoints_data) = self.instruction_breakpoints.get_mut(&regs.pc) {
132            if breakpoints_data.step_over {
133                breakpoints_data.step_over = false;
134                if *breakpoints_data == EnabledBreakpoints::default() {
135                    self.instruction_breakpoints.remove(&regs.pc);
136                }
137                self.set_pause(true);
138                self.last_state = CpuState::StepOver;
139                return true;
140            }
141
142            if breakpoints_data.step_out {
143                breakpoints_data.step_out = false;
144                if *breakpoints_data == EnabledBreakpoints::default() {
145                    self.instruction_breakpoints.remove(&regs.pc);
146                }
147                self.set_pause(true);
148                self.last_state = CpuState::StepOut;
149                return true;
150            }
151
152            if !self.in_breakpoint && breakpoints_data.normal {
153                self.in_breakpoint = true;
154                self.set_pause(true);
155                self.last_state = CpuState::InstructionBreakpoint(regs.pc);
156                return true;
157            }
158        }
159
160        // -- the instruction will execute after this point
161        //    i.e. will return `false`
162
163        self.in_breakpoint = false;
164
165        if jumping {
166            match self.last_instruction.opcode {
167                Opcode::Jal | Opcode::Jalr => {
168                    self.call_stack.push(self.last_instruction.pc + 8);
169                }
170                Opcode::Jr => {
171                    // Sometimes, the return address is not always the last on the stack.
172                    // For example, when a program calls into the bios with
173                    // 0xA0,0xB0,0xC0 functions, an inner function might return
174                    // to the user space and not the main handler, which results
175                    // in a frame being stuck in the middle.
176                    //
177                    // That's why we have to check if the return address is any
178                    // of the previous frames.
179                    let target = regs.read_general(self.last_instruction.rs_raw);
180
181                    if !self.call_stack.is_empty() {
182                        let mut c = 1;
183                        for x in self.call_stack.iter().rev() {
184                            if *x == target {
185                                self.call_stack.truncate(self.call_stack.len() - c);
186                                break;
187                            }
188
189                            c += 1;
190                        }
191                    }
192                }
193                _ => {}
194            }
195        }
196
197        if let Some(handler) = &self.instruction_trace_handler {
198            handler(regs, instruction, jumping);
199        }
200
201        if self.step {
202            self.set_pause(true);
203            self.step = false;
204            self.last_state = CpuState::Step;
205        }
206
207        self.last_instruction = instruction.clone();
208
209        // even if we are in step breakpoint, we must execute the current instruction
210        false
211    }
212
213    pub(crate) fn trace_write(&mut self, addr: u32, bits: u8) {
214        if !self.write_breakpoints.is_empty() && self.write_breakpoints.contains(&addr) {
215            self.set_pause(true);
216            self.last_state = CpuState::WriteBreakpoint { addr, bits };
217        }
218    }
219
220    pub(crate) fn trace_read(&mut self, addr: u32, bits: u8) {
221        if !self.read_breakpoints.is_empty() && self.read_breakpoints.contains(&addr) {
222            self.set_pause(true);
223            self.last_state = CpuState::ReadBreakpoint { addr, bits };
224        }
225    }
226}
227
228impl Debugger {
229    pub fn single_step(&mut self) {
230        self.step = true;
231    }
232
233    pub fn step_over(&mut self) {
234        self.step_over = true;
235    }
236
237    pub fn step_out(&mut self) {
238        let Some(last_frame) = self.call_stack.last() else {
239            return;
240        };
241
242        self.instruction_breakpoints
243            .entry(*last_frame)
244            .or_default()
245            .step_out = true;
246    }
247
248    /// The handler function's arguments are:
249    /// - registers
250    /// - instruction
251    /// - jumping: indicates if we are in the middle of a jump
252    pub fn set_instruction_trace_handler(&mut self, handler: Option<InstructionTraceHandler>) {
253        self.instruction_trace_handler = handler;
254    }
255
256    pub fn add_breakpoint(&mut self, address: u32) {
257        self.instruction_breakpoints
258            .entry(address)
259            .or_default()
260            .normal = true;
261    }
262
263    pub fn remove_breakpoint(&mut self, address: u32) -> bool {
264        if let Some(v) = self.instruction_breakpoints.get_mut(&address) {
265            v.normal = false;
266            // empty
267            if *v == EnabledBreakpoints::default() {
268                self.instruction_breakpoints.remove(&address);
269            }
270            true
271        } else {
272            false
273        }
274    }
275
276    pub fn add_write_breakpoint(&mut self, address: u32) {
277        self.write_breakpoints.insert(address);
278    }
279
280    pub fn remove_write_breakpoint(&mut self, address: u32) -> bool {
281        self.write_breakpoints.remove(&address)
282    }
283
284    pub fn add_read_breakpoint(&mut self, address: u32) {
285        self.read_breakpoints.insert(address);
286    }
287
288    pub fn remove_read_breakpoint(&mut self, address: u32) -> bool {
289        self.read_breakpoints.remove(&address)
290    }
291
292    pub fn instruction_breakpoints(&self) -> HashSet<u32> {
293        self.instruction_breakpoints
294            .iter()
295            .filter_map(|(k, v)| if v.normal { Some(*k) } else { None })
296            .collect::<HashSet<_>>()
297    }
298
299    pub fn write_breakpoints(&self) -> &HashSet<u32> {
300        &self.write_breakpoints
301    }
302
303    pub fn read_breakpoints(&self) -> &HashSet<u32> {
304        &self.read_breakpoints
305    }
306
307    pub fn call_stack(&self) -> &[u32] {
308        &self.call_stack
309    }
310}