debug.rsannotateddebug.rssource336 lines · 10.5 KB · raw
1use crate::WhichCpu;
2use crate::api::debug::{Sega32XDebugView, Sega32XDebugger};
3use crate::bus::{OtherCpu, Sh2Bus, Sh2BusGuard};
4use genesis_components::cartridge::Cartridge;
5use sh2_emu::Sh2;
6use sh2_emu::bus::{AccessContext, BusInterface, OpSize};
7use sh2_emu::debug::Sh2Debugger;
8use std::marker::PhantomData;
9use std::ops::{Deref, DerefMut};
10use std::ptr::NonNull;
11use std::{cmp, ptr};
12
13pub struct DebugSh2Bus {
14    bus: Sh2Bus,
15    debugger: NonNull<dyn Sega32XDebugger>,
16    other_sh2: *mut Sh2,
17}
18
19impl DebugSh2Bus {
20    pub fn cycle_counter(&self) -> u64 {
21        self.bus.cycle_counter
22    }
23
24    pub fn cycle_limit(&self) -> u64 {
25        self.bus.cycle_limit
26    }
27
28    fn maybe_sync_other_cpu(&mut self, address: u32, cpu: &mut Sh2) {
29        if !self.bus.need_to_sync_other(address) {
30            return;
31        }
32
33        let Some(OtherCpu { cpu: other_cpu, cycle_counter: other_cycle_counter }) =
34            &mut self.bus.other_sh2
35        else {
36            return;
37        };
38
39        let cycle_limit = cmp::min(self.bus.cycle_counter, self.bus.cycle_limit);
40
41        // SAFETY: Similar to Sh2Bus::maybe_sync_other_cpu but also attaches a pointer to the
42        // current CPU, created from a mutable reference here. The current CPU and the original bus
43        // are not used while the other CPU is executing against the debug bus.
44        unsafe {
45            let mut other_bus = Self {
46                bus: Sh2Bus {
47                    s32x_bus: self.bus.s32x_bus,
48                    other_sh2: None,
49                    which: self.bus.which.other(),
50                    cycle_counter: other_cycle_counter.read(),
51                    cycle_limit,
52                },
53                debugger: self.debugger,
54                other_sh2: ptr::from_mut(cpu),
55            };
56
57            while other_bus.bus.cycle_counter < cycle_limit {
58                other_cpu.as_mut().execute(crate::api::SH2_EXECUTION_SLICE_LEN, &mut other_bus);
59            }
60            other_cycle_counter.write(other_bus.bus.cycle_counter);
61        }
62    }
63}
64
65pub struct DebugSh2BusGuard<'debug, 'bus, 'cartridge, 'other> {
66    bus: DebugSh2Bus,
67    _debug_marker: PhantomData<&'debug ()>,
68    _bus_marker: PhantomData<&'bus ()>,
69    _cartridge_marker: PhantomData<&'cartridge ()>,
70    _other_marker: PhantomData<&'other ()>,
71}
72
73impl DebugSh2Bus {
74    #[inline]
75    pub fn create<'debug, 'bus, 'cartridge, 'other, Debugger: Sega32XDebugger>(
76        bus_guard: Sh2BusGuard<'bus, 'cartridge, 'other>,
77        debugger: &'debug mut Debugger,
78    ) -> DebugSh2BusGuard<'debug, 'bus, 'cartridge, 'other> {
79        // SAFETY: Consumes an Sh2BusGuard to get the inner bus, and returns another guard that has
80        // the same lifetimes plus the debugger lifetime.
81        // Converts the debugger mutable reference to a raw pointer here and uses the debugger
82        // lifetime to ensure that the caller cannot access the debugger until after this guard is
83        // dropped.
84        let debugger: &mut dyn Sega32XDebugger = debugger;
85        let debug_bus = DebugSh2Bus {
86            bus: bus_guard.bus,
87            debugger: debugger.into(),
88            other_sh2: ptr::null_mut(),
89        };
90
91        DebugSh2BusGuard {
92            bus: debug_bus,
93            _debug_marker: PhantomData,
94            _bus_marker: PhantomData,
95            _cartridge_marker: PhantomData,
96            _other_marker: PhantomData,
97        }
98    }
99}
100
101impl Deref for DebugSh2BusGuard<'_, '_, '_, '_> {
102    type Target = DebugSh2Bus;
103
104    fn deref(&self) -> &Self::Target {
105        &self.bus
106    }
107}
108
109impl DerefMut for DebugSh2BusGuard<'_, '_, '_, '_> {
110    fn deref_mut(&mut self) -> &mut Self::Target {
111        &mut self.bus
112    }
113}
114
115pub struct DebugSh2BusView<'bus>(&'bus mut DebugSh2Bus);
116
117impl DebugSh2BusView<'_> {
118    fn which(&self) -> WhichCpu {
119        self.0.bus.which
120    }
121
122    fn as_debug_view<'slf, 'cpu, 'ret>(
123        &'slf mut self,
124        cpu: &'cpu mut Sh2,
125    ) -> Option<(Sega32XDebugView<'ret>, Option<&'slf mut Cartridge>)>
126    where
127        'slf: 'ret,
128        'cpu: 'ret,
129    {
130        let s32x_bus = unsafe { self.0.bus.s32x_bus.as_mut() };
131
132        // SAFETY: other_sh2 is only set on DebugSh2Bus when created in DebugSh2Bus::maybe_sync_other_cpu
133        // above, where it is created from a mutable reference. Otherwise this pulls the other SH-2
134        // out of the inner Sh2Bus, which is only ever set in Sh2Bus::create.
135        let other_sh2 = unsafe {
136            if !self.0.other_sh2.is_null() {
137                self.0.other_sh2.as_mut().unwrap()
138            } else if let Some(other_sh2) = self.0.bus.other_sh2.as_mut() {
139                other_sh2.cpu.as_mut()
140            } else {
141                // This _shouldn't_ ever happen, but don't crash if it does
142                log::error!(
143                    "Invalid debugger state; no pointer to other SH-2 while {:?} is executing",
144                    self.0.bus.which
145                );
146                return None;
147            }
148        };
149
150        let (sh2_master, sh2_slave) = match self.0.bus.which {
151            WhichCpu::Master => (cpu, other_sh2),
152            WhichCpu::Slave => (other_sh2, cpu),
153        };
154
155        let debug_view = Sega32XDebugView {
156            sdram: s32x_bus.sdram.as_mut_slice(),
157            sh2_master,
158            sh2_slave,
159            system_registers: &mut s32x_bus.registers,
160            vdp: &mut s32x_bus.vdp,
161            pwm: &mut s32x_bus.pwm,
162        };
163
164        let cartridge = s32x_bus.cartridge.as_mut();
165
166        Some((debug_view, cartridge))
167    }
168}
169
170impl Sh2Debugger for DebugSh2BusView<'_> {
171    fn check_read<const SIZE: u8>(&mut self, address: u32, cpu: &mut Sh2) {
172        let which = self.which();
173
174        // SAFETY: Debugger was created from a mutable reference, and DebugSh2Bus is only accessible
175        // through a guard
176        unsafe {
177            let debugger = self.0.debugger.as_mut();
178            if debugger.check_sh2_read_breakpoint(which, address, OpSize::enum_value::<SIZE>())
179                && let Some((debug_view, cartridge)) = self.as_debug_view(cpu)
180            {
181                log::info!("SH-2 {which:?} triggered read breakpoint: {address:08X}");
182
183                debugger.handle_sh2_breakpoint(which, cartridge, debug_view);
184            }
185        }
186    }
187
188    fn apply_read<const SIZE: u8>(
189        &mut self,
190        address: u32,
191        ctx: AccessContext,
192        cpu: &mut Sh2,
193    ) -> u32 {
194        self.0.maybe_sync_other_cpu(address, cpu);
195        self.0.bus.read::<SIZE>(address, ctx)
196    }
197
198    fn check_write<const SIZE: u8>(&mut self, address: u32, value: u32, cpu: &mut Sh2) {
199        let which = self.which();
200
201        // SAFETY: Debugger was created from a mutable reference, and DebugSh2Bus is only accessible
202        // through a guard
203        unsafe {
204            let debugger = self.0.debugger.as_mut();
205            if debugger.check_sh2_write_breakpoint(
206                which,
207                address,
208                value,
209                OpSize::enum_value::<SIZE>(),
210            ) && let Some((debug_view, cartridge)) = self.as_debug_view(cpu)
211            {
212                log::info!("SH-2 {which:?} triggered write breakpoint: {address:08X} {value:08X}");
213
214                debugger.handle_sh2_breakpoint(which, cartridge, debug_view);
215            }
216        }
217    }
218
219    fn apply_write<const SIZE: u8>(
220        &mut self,
221        address: u32,
222        value: u32,
223        ctx: AccessContext,
224        cpu: &mut Sh2,
225    ) {
226        self.0.maybe_sync_other_cpu(address, cpu);
227        self.0.bus.write::<SIZE>(address, value, ctx);
228    }
229
230    fn apply_read_cache_line(
231        &mut self,
232        address: u32,
233        ctx: AccessContext,
234        cpu: &mut Sh2,
235    ) -> [u16; 8] {
236        self.0.maybe_sync_other_cpu(address, cpu);
237        self.0.bus.read_cache_line(address, ctx)
238    }
239
240    fn check_execute(&mut self, pc: u32, opcode: u16, cpu: &mut Sh2) {
241        let which = self.which();
242
243        // SAFETY: Debugger was created from a mutable reference, and DebugSh2Bus is only accessible
244        // through a guard
245        unsafe {
246            let debugger = self.0.debugger.as_mut();
247            if debugger.check_sh2_execute_breakpoint(which, pc, opcode)
248                && let Some((debug_view, cartridge)) = self.as_debug_view(cpu)
249            {
250                log::info!("SH-2 {which:?} triggered execute breakpoint: PC={pc:08X}");
251
252                debugger.handle_sh2_breakpoint(which, cartridge, debug_view);
253            }
254        }
255    }
256
257    fn check_interrupt(&mut self, interrupt_level: u8, cpu: &mut Sh2) {
258        let which = self.which();
259
260        // SAFETY: Debugger was created from a mutable reference, and DebugSh2Bus is only accessible
261        // through a guard
262        unsafe {
263            let debugger = self.0.debugger.as_mut();
264            if debugger.check_sh2_interrupt_breakpoint(which, interrupt_level)
265                && let Some((debug_view, cartridge)) = self.as_debug_view(cpu)
266            {
267                log::info!(
268                    "SH-2 {which:?} triggered interrupt breakpoint, interrupt level {interrupt_level}"
269                );
270
271                debugger.handle_sh2_breakpoint(which, cartridge, debug_view);
272            }
273        }
274    }
275}
276
277impl BusInterface for DebugSh2Bus {
278    type DebugView<'a>
279        = DebugSh2BusView<'a>
280    where
281        Self: 'a;
282
283    fn read<const SIZE: u8>(&mut self, address: u32, ctx: AccessContext) -> u32 {
284        self.bus.read::<SIZE>(address, ctx)
285    }
286
287    fn read_cache_line(&mut self, address: u32, ctx: AccessContext) -> [u16; 8] {
288        self.bus.read_cache_line(address, ctx)
289    }
290
291    fn write<const SIZE: u8>(&mut self, address: u32, value: u32, ctx: AccessContext) {
292        self.bus.write::<SIZE>(address, value, ctx);
293    }
294
295    fn reset(&self) -> bool {
296        self.bus.reset()
297    }
298
299    fn interrupt_level(&self) -> u8 {
300        self.bus.interrupt_level()
301    }
302
303    fn dma_request_0(&self) -> bool {
304        self.bus.dma_request_0()
305    }
306
307    fn dma_request_1(&self) -> bool {
308        self.bus.dma_request_1()
309    }
310
311    fn acknowledge_dreq_1(&mut self) {
312        self.bus.acknowledge_dreq_1();
313    }
314
315    fn serial_rx(&mut self) -> Option<u8> {
316        self.bus.serial_rx()
317    }
318
319    fn serial_tx(&mut self, value: u8) {
320        self.bus.serial_tx(value);
321    }
322
323    fn increment_cycle_counter(&mut self, cycles: u64) {
324        self.bus.increment_cycle_counter(cycles);
325    }
326
327    fn should_stop_execution(&self) -> bool {
328        self.bus.should_stop_execution()
329    }
330
331    fn debug_view(&mut self) -> Option<Self::DebugView<'_>> {
332        Some(DebugSh2BusView(self))
333    }
334
335    sh2_emu::impl_sh2_opcode_table!(DebugSh2Bus);
336}