Skip to content

Commit efd7ab7

Browse files
codewizclaude
andcommitted
debugger: compare and display CPU addresses through the model's bus mask
UI_ADDR_MASK hardcoded A0-A23, so on 020+ machines breakpoints and watches at Zorro III addresses aliased chip RAM and displays truncated 32-bit addresses. Chip-DMA surfaces (copper, blitter) stay 24-bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8e1f882 commit efd7ab7

5 files changed

Lines changed: 75 additions & 40 deletions

File tree

src/cpu.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ impl M68kMachine {
279279
dbg_crash_on: crate::envcfg::flag("COPPERLINE_DIAG_CRASH"),
280280
dbg_crash_dumped: false,
281281
dbg: crate::debugger::Debugger::from_env(),
282-
ui_breaks: crate::debugger::InteractiveBreaks::default(),
282+
ui_breaks: crate::debugger::InteractiveBreaks::new(address_mask_for_model(cpu_model)),
283283
ui_stop: None,
284284
ui_last_this_task: None,
285285
ui_pc_history: [0; UI_PC_HISTORY_CAP],
@@ -1241,6 +1241,13 @@ impl M68kMachine {
12411241
&self.ui_breaks
12421242
}
12431243

1244+
/// The CPU model's address-bus mask, for debugger surfaces that
1245+
/// normalize or display CPU addresses (A0-A23 on 24-bit models,
1246+
/// full 32 bits on 020+).
1247+
pub fn ui_addr_mask(&self) -> u32 {
1248+
self.cpu.address_mask
1249+
}
1250+
12441251
/// Toggle a PC breakpoint carrying an optional condition and ignore count.
12451252
/// Returns true when the breakpoint is now set.
12461253
pub fn ui_set_breakpoint(
@@ -1277,7 +1284,7 @@ impl M68kMachine {
12771284
addr: u32,
12781285
filter: Option<crate::debugger::WatchSource>,
12791286
) -> bool {
1280-
let addr = addr & crate::debugger::UI_ADDR_MASK & !1;
1287+
let addr = addr & self.cpu.address_mask & !1;
12811288
let current = self.bus.bus.peek_word_any(addr);
12821289
let added = self.ui_breaks.toggle_watch(addr, current, filter);
12831290
let addrs: Vec<u32> = self.ui_breaks.watches.iter().map(|w| w.addr).collect();
@@ -1443,7 +1450,7 @@ impl M68kMachine {
14431450
/// is the instruction that just retired.
14441451
fn ui_check_breaks_after_step(&mut self) {
14451452
use crate::debugger::DebugStop;
1446-
let pc = self.cpu.pc & crate::debugger::UI_ADDR_MASK;
1453+
let pc = self.cpu.pc & self.cpu.address_mask;
14471454
// Exception catchpoints: the core records every exception entry
14481455
// (trap, fault, or interrupt) as it loads the handler vector;
14491456
// drain it here so a hit stops at the handler's first
@@ -1467,7 +1474,7 @@ impl M68kMachine {
14671474
if self.ui_stop.is_some() {
14681475
return;
14691476
}
1470-
let writer_pc = self.cpu.ppc & crate::debugger::UI_ADDR_MASK;
1477+
let writer_pc = self.cpu.ppc & self.cpu.address_mask;
14711478
for i in 0..self.ui_breaks.watches.len() {
14721479
let addr = self.ui_breaks.watches[i].addr;
14731480
let new = self.bus.bus.peek_word_any(addr);
@@ -1548,7 +1555,7 @@ impl M68kMachine {
15481555
// a caught vector; stop before the handler's first
15491556
// instruction executes.
15501557
if self.ui_breaks.armed() {
1551-
let pc = self.cpu.pc & crate::debugger::UI_ADDR_MASK;
1558+
let pc = self.cpu.pc & self.cpu.address_mask;
15521559
if let Some(vector) = self.cpu.last_exception_vector.take() {
15531560
let vector = vector.min(u32::from(u16::MAX)) as u16;
15541561
if self.ui_breaks.catches.contains(&vector) {

src/debugger.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ pub struct Watch {
6161
pub len: u32,
6262
}
6363

64-
/// The 68000 address-bus width the interactive debugger compares PCs and
65-
/// watch addresses through (A0-A23).
64+
/// The 68000/EC020 address-bus mask (A0-A23). Debugger surfaces compare
65+
/// and display through the machine's model mask (`ui_addr_mask()`); this
66+
/// constant remains for tests and 24-bit callers.
6667
pub const UI_ADDR_MASK: u32 = 0x00FF_FFFF;
6768

6869
/// An interactive memory watchpoint: a 16-bit word and the value it held
@@ -687,8 +688,11 @@ pub struct Breakpoint {
687688
/// The debugger window's breakpoint/watchpoint set. Owned by the CPU
688689
/// machine so it stays armed while the window is closed; `armed` is the
689690
/// single per-instruction gate the hot loop checks.
690-
#[derive(Default)]
691691
pub struct InteractiveBreaks {
692+
/// Address-bus mask breakpoint/watch addresses and PCs are compared
693+
/// through: A0-A23 on 24-bit models, full 32 bits on 020+ (set from
694+
/// the CPU model at machine construction).
695+
pub addr_mask: u32,
692696
pub breakpoints: Vec<Breakpoint>,
693697
pub watches: Vec<UiWatch>,
694698
/// Watched custom-register word offsets into $DFF000 ($000-$1FE).
@@ -706,6 +710,20 @@ pub struct InteractiveBreaks {
706710
}
707711

708712
impl InteractiveBreaks {
713+
/// An empty break set comparing addresses through `addr_mask` (the
714+
/// owning machine's address-bus mask; see `address_mask_for_model`).
715+
pub fn new(addr_mask: u32) -> Self {
716+
Self {
717+
addr_mask,
718+
breakpoints: Vec::new(),
719+
watches: Vec::new(),
720+
reg_watches: Vec::new(),
721+
catches: Vec::new(),
722+
task_catch: None,
723+
armed: false,
724+
}
725+
}
726+
709727
pub fn armed(&self) -> bool {
710728
self.armed
711729
}
@@ -721,7 +739,7 @@ impl InteractiveBreaks {
721739
/// Whether any breakpoint is set at `pc`, ignoring its condition. Used for
722740
/// display (marking the address) and the reverse-debug scan.
723741
pub fn is_breakpoint(&self, pc: u32) -> bool {
724-
let pc = pc & UI_ADDR_MASK;
742+
let pc = pc & self.addr_mask;
725743
self.breakpoints.iter().any(|bp| bp.addr == pc)
726744
}
727745

@@ -734,7 +752,7 @@ impl InteractiveBreaks {
734752
cond: Option<BreakCond>,
735753
ignore: u32,
736754
) -> bool {
737-
let addr = addr & UI_ADDR_MASK;
755+
let addr = addr & self.addr_mask;
738756
let added = match self.breakpoints.iter().position(|bp| bp.addr == addr) {
739757
Some(pos) => {
740758
self.breakpoints.remove(pos);
@@ -759,7 +777,7 @@ impl InteractiveBreaks {
759777
/// ignore count has been exhausted -- each qualifying hit before that is
760778
/// counted and skipped.
761779
pub fn breakpoint_stops(&mut self, pc: u32, ctx: &dyn BreakContext) -> bool {
762-
let pc = pc & UI_ADDR_MASK;
780+
let pc = pc & self.addr_mask;
763781
let Some(bp) = self.breakpoints.iter_mut().find(|bp| bp.addr == pc) else {
764782
return false;
765783
};
@@ -1118,7 +1136,7 @@ mod tests {
11181136

11191137
#[test]
11201138
fn interactive_breakpoints_toggle_mask_and_arm() {
1121-
let mut breaks = InteractiveBreaks::default();
1139+
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
11221140
assert!(!breaks.armed());
11231141

11241142
// Adding masks the address to the 68000 bus width.
@@ -1132,6 +1150,16 @@ mod tests {
11321150
assert!(!breaks.is_breakpoint(0x00C0_33C2));
11331151
}
11341152

1153+
#[test]
1154+
fn full_mask_keeps_z3_breakpoints_distinct_from_chip_aliases() {
1155+
// On a 32-bit CPU a Zorro III breakpoint must not fire at the
1156+
// chip-RAM address it would alias through a 24-bit mask.
1157+
let mut breaks = InteractiveBreaks::new(0xFFFF_FFFF);
1158+
assert!(breaks.toggle_breakpoint_full(0x4000_1000, None, 0));
1159+
assert!(breaks.is_breakpoint(0x4000_1000));
1160+
assert!(!breaks.is_breakpoint(0x0000_1000));
1161+
}
1162+
11351163
/// Fixed register/memory snapshot for exercising condition evaluation.
11361164
#[derive(Default)]
11371165
struct FakeCtx {
@@ -1162,7 +1190,7 @@ mod tests {
11621190

11631191
#[test]
11641192
fn conditional_breakpoint_stops_only_when_condition_holds() {
1165-
let mut breaks = InteractiveBreaks::default();
1193+
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
11661194
breaks.toggle_breakpoint_full(
11671195
0x1000,
11681196
Some(BreakCond {
@@ -1186,7 +1214,7 @@ mod tests {
11861214

11871215
#[test]
11881216
fn ignore_count_skips_the_first_qualifying_hits() {
1189-
let mut breaks = InteractiveBreaks::default();
1217+
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
11901218
// Stop on the 4th qualifying hit (ignore the first 3).
11911219
breaks.toggle_breakpoint_full(0x1000, None, 3);
11921220
let ctx = FakeCtx::default();
@@ -1200,7 +1228,7 @@ mod tests {
12001228

12011229
#[test]
12021230
fn bit_test_condition_uses_memory_word() {
1203-
let mut breaks = InteractiveBreaks::default();
1231+
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
12041232
breaks.toggle_breakpoint_full(
12051233
0x40,
12061234
Some(BreakCond {
@@ -1219,7 +1247,7 @@ mod tests {
12191247

12201248
#[test]
12211249
fn interactive_watches_record_baselines_and_clear() {
1222-
let mut breaks = InteractiveBreaks::default();
1250+
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
12231251
assert!(breaks.toggle_watch(0x1000, 0xABCD, None));
12241252
assert_eq!(breaks.watches[0].last, 0xABCD);
12251253
// The register watch normalizes a full $DFFxxx address to the

src/gdbstub.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! memory-mapped devices; Amiga custom-chip state is exposed through `monitor`
88
//! commands so inspection remains side-effect-free.
99
10-
use crate::debugger::{custom_reg_name, UI_ADDR_MASK};
10+
use crate::debugger::custom_reg_name;
1111
use crate::emulator::Emulator;
1212
use crate::timetravel::ReverseOutcome;
1313
use anyhow::{anyhow, bail, Context, Result};
@@ -391,7 +391,7 @@ impl Session {
391391

392392
fn add_breakpoint(&mut self, packet: &str) -> Result<String> {
393393
let (addr, _) = parse_z_packet(packet)?;
394-
let addr = addr & UI_ADDR_MASK;
394+
let addr = addr & self.emu.machine.ui_addr_mask();
395395
if !self.breakpoints.contains(&addr) {
396396
self.breakpoints.push(addr);
397397
}
@@ -400,7 +400,7 @@ impl Session {
400400

401401
fn remove_breakpoint(&mut self, packet: &str) -> Result<String> {
402402
let (addr, _) = parse_z_packet(packet)?;
403-
let addr = addr & UI_ADDR_MASK;
403+
let addr = addr & self.emu.machine.ui_addr_mask();
404404
self.breakpoints.retain(|&candidate| candidate != addr);
405405
Ok("OK".to_string())
406406
}
@@ -485,7 +485,7 @@ impl Session {
485485
if self.emu.bus_mut().take_ui_copper_hit().is_some() {
486486
return Ok(Some(StopReason::CopperBreak));
487487
}
488-
let pc = self.emu.machine.pc() & UI_ADDR_MASK;
488+
let pc = self.emu.machine.pc() & self.emu.machine.ui_addr_mask();
489489
if self.breakpoints.contains(&pc) {
490490
return Ok(Some(StopReason::Breakpoint));
491491
}

src/video/window.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,7 +3206,7 @@ impl App {
32063206
let mut panel = ui::DebuggerPanel::new();
32073207
// Start the memory view at the current program counter's
32083208
// neighbourhood; it is usually what you came to look at.
3209-
panel.mem_addr = self.emu.machine.pc() & 0x00FF_FFF0;
3209+
panel.mem_addr = self.emu.machine.pc() & self.emu.machine.ui_addr_mask() & !0xF;
32103210
self.debugger_panel = Some(panel);
32113211
self.emu.machine.ui_set_pc_history_enabled(true);
32123212
// Arm reverse debugging so the < Step / < Run controls work. A
@@ -4453,7 +4453,7 @@ impl App {
44534453
self.reported_double_fault = true;
44544454
let message = format!(
44554455
"CPU halted: double fault at pc ${:06X} (bus/address error during exception)",
4456-
self.emu.machine.pc() & 0x00FF_FFFF
4456+
self.emu.machine.pc() & self.emu.machine.ui_addr_mask()
44574457
);
44584458
warn!("{message}");
44594459
self.last_debug_stop = Some(message.clone());
@@ -4498,7 +4498,7 @@ impl App {
44984498
let set = self.emu.machine.ui_set_breakpoint(addr, cond, ignore);
44994499
let mut msg = format!(
45004500
"Breakpoint ${:06X} {}",
4501-
addr & 0x00FF_FFFF,
4501+
addr & self.emu.machine.ui_addr_mask(),
45024502
if set { "set" } else { "removed" }
45034503
);
45044504
if set {
@@ -4517,7 +4517,7 @@ impl App {
45174517
let Some(addr) = self.debugger_entry_addr("Watch") else {
45184518
return;
45194519
};
4520-
let addr = addr & 0x00FF_FFFE;
4520+
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
45214521
let set = self.emu.machine.ui_toggle_watch(addr);
45224522
self.show_osd(format!(
45234523
"Watchpoint ${addr:06X} {}",
@@ -4564,7 +4564,7 @@ impl App {
45644564
panel.mem_addr.wrapping_sub(delta)
45654565
} else {
45664566
panel.mem_addr.wrapping_add(delta)
4567-
} & 0x00FF_FFFF;
4567+
} & self.emu.machine.ui_addr_mask();
45684568
if !panel.mem_view_bits {
45694569
panel.mem_addr &= !0xF;
45704570
}
@@ -4587,7 +4587,7 @@ impl App {
45874587
panel.mem_addr.wrapping_sub(delta)
45884588
} else {
45894589
panel.mem_addr.wrapping_add(delta)
4590-
} & 0x00FF_FFFF;
4590+
} & self.emu.machine.ui_addr_mask();
45914591
self.request_redraw();
45924592
}
45934593
}
@@ -4608,7 +4608,7 @@ impl App {
46084608
.mem_last_find
46094609
.map(|addr| addr.wrapping_add(1))
46104610
.unwrap_or(panel.mem_addr)
4611-
& 0x00FF_FFFF;
4611+
& self.emu.machine.ui_addr_mask();
46124612
const SPACE: u64 = 0x0100_0000;
46134613
const CHUNK: usize = 4096;
46144614
let mut offset = 0u64;
@@ -4689,7 +4689,7 @@ impl App {
46894689
self.show_osd("Writer: type a hex address first");
46904690
return;
46914691
};
4692-
let addr = addr & 0x00FF_FFFE;
4692+
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
46934693
let before = self.emu.retired_instructions();
46944694
match self.emu.tt_last_writer(addr, before) {
46954695
Ok(ReverseOutcome::Found(rec)) => {
@@ -4698,7 +4698,7 @@ impl App {
46984698
rec.addr,
46994699
rec.old,
47004700
rec.new,
4701-
rec.pc & 0x00FF_FFFF,
4701+
rec.pc & self.emu.machine.ui_addr_mask(),
47024702
rec.frame
47034703
);
47044704
info!("last-writer {message}");
@@ -5367,7 +5367,7 @@ impl App {
53675367
if panel.mem_view_bits {
53685368
let stride = panel.mem_bitmap_stride.max(1) as usize;
53695369
let rows = ui::mem_bitmap_rows();
5370-
let base = panel.mem_addr & 0x00FF_FFFF;
5370+
let base = panel.mem_addr & machine.ui_addr_mask();
53715371
lines.push(ui::DbgLine::plain(format!(
53725372
"bitplane at ${base:06X}, stride {stride} bytes ({} px), {rows} rows",
53735373
stride * 8
@@ -5383,9 +5383,9 @@ impl App {
53835383
"$ box: jump / \"ADDR VALUE\" poke / \"ADDR LEN\" save / hex bytes find",
53845384
));
53855385
lines.push(ui::DbgLine::plain(""));
5386-
let base = panel.mem_addr & 0x00FF_FFF0;
5386+
let base = panel.mem_addr & machine.ui_addr_mask() & !0xF;
53875387
for row in 0..16u32 {
5388-
let addr = base.wrapping_add(row * 16) & 0x00FF_FFFF;
5388+
let addr = base.wrapping_add(row * 16) & machine.ui_addr_mask();
53895389
let mut bytes = [0u8; 16];
53905390
for word in 0..8u32 {
53915391
let value = bus.peek_word_any(addr.wrapping_add(word * 2));

src/video/window/console.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ impl App {
378378
let set = self.emu.machine.ui_set_breakpoint(addr, cond, ignore);
379379
ConsoleOutcome::one(format!(
380380
"breakpoint ${:06X} {}",
381-
addr & 0x00FF_FFFF,
381+
addr & self.emu.machine.ui_addr_mask(),
382382
if set { "set" } else { "removed" }
383383
))
384384
}
@@ -398,7 +398,7 @@ impl App {
398398
let set = self.emu.machine.ui_toggle_watch_filtered(addr, filter);
399399
ConsoleOutcome::one(format!(
400400
"watchpoint ${:06X}{} {}",
401-
addr & 0x00FF_FFFE,
401+
addr & self.emu.machine.ui_addr_mask() & !1,
402402
filter
403403
.map(|f| format!(" ({} writes only)", f.label()))
404404
.unwrap_or_default(),
@@ -482,7 +482,7 @@ impl App {
482482
// exec's Alert() lives at LVO -108; the jump-table
483483
// entry itself executes, so a PC breakpoint there
484484
// fires on every alert with D7 = the guru code.
485-
vec![format!("{:06X}", base.wrapping_sub(108) & 0x00FF_FFFF)]
485+
vec![format!("{:06X}", base.wrapping_sub(108) & self.emu.machine.ui_addr_mask())]
486486
});
487487
let Some(addr) = lvo
488488
.first()
@@ -805,7 +805,7 @@ impl App {
805805
let Some(addr) = args.first().and_then(|t| hex32(t)) else {
806806
return ConsoleOutcome::error("usage: WRITER ADDR (hex, word)");
807807
};
808-
let addr = addr & 0x00FF_FFFE;
808+
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
809809
let before = self.emu.retired_instructions();
810810
let outcome = match self.emu.tt_last_writer(addr, before) {
811811
Ok(crate::timetravel::ReverseOutcome::Found(rec)) => {
@@ -814,7 +814,7 @@ impl App {
814814
rec.addr,
815815
rec.old,
816816
rec.new,
817-
rec.pc & 0x00FF_FFFF,
817+
rec.pc & self.emu.machine.ui_addr_mask(),
818818
rec.frame
819819
))
820820
}
@@ -1284,16 +1284,16 @@ impl App {
12841284
let cpu_type = machine.cpu_type();
12851285
let mut lines = vec![format!(
12861286
"#0 pc ${:06X} sp ${:06X}",
1287-
machine.pc() & 0x00FF_FFFF,
1288-
sp & 0x00FF_FFFF
1287+
machine.pc() & machine.ui_addr_mask(),
1288+
sp & machine.ui_addr_mask()
12891289
)];
12901290
let mut frame = 1usize;
12911291
for slot in 0..SLOTS {
12921292
if frame > FRAMES {
12931293
break;
12941294
}
12951295
let slot_addr = sp.wrapping_add(slot * 4);
1296-
let value = peek32(slot_addr) & 0x00FF_FFFF;
1296+
let value = peek32(slot_addr) & machine.ui_addr_mask();
12971297
if !looks_like_return(value) {
12981298
continue;
12991299
}

0 commit comments

Comments
 (0)