Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ impl M68kMachine {
dbg_crash_on: crate::envcfg::flag("COPPERLINE_DIAG_CRASH"),
dbg_crash_dumped: false,
dbg: crate::debugger::Debugger::from_env(),
ui_breaks: crate::debugger::InteractiveBreaks::default(),
ui_breaks: crate::debugger::InteractiveBreaks::new(address_mask_for_model(cpu_model)),
ui_stop: None,
ui_last_this_task: None,
ui_pc_history: [0; UI_PC_HISTORY_CAP],
Expand Down Expand Up @@ -1241,6 +1241,13 @@ impl M68kMachine {
&self.ui_breaks
}

/// The CPU model's address-bus mask, for debugger surfaces that
/// normalize or display CPU addresses (A0-A23 on 24-bit models,
/// full 32 bits on 020+).
pub fn ui_addr_mask(&self) -> u32 {
self.cpu.address_mask
}

/// Toggle a PC breakpoint carrying an optional condition and ignore count.
/// Returns true when the breakpoint is now set.
pub fn ui_set_breakpoint(
Expand Down Expand Up @@ -1277,7 +1284,7 @@ impl M68kMachine {
addr: u32,
filter: Option<crate::debugger::WatchSource>,
) -> bool {
let addr = addr & crate::debugger::UI_ADDR_MASK & !1;
let addr = addr & self.cpu.address_mask & !1;
let current = self.bus.bus.peek_word_any(addr);
let added = self.ui_breaks.toggle_watch(addr, current, filter);
let addrs: Vec<u32> = self.ui_breaks.watches.iter().map(|w| w.addr).collect();
Expand Down Expand Up @@ -1443,7 +1450,7 @@ impl M68kMachine {
/// is the instruction that just retired.
fn ui_check_breaks_after_step(&mut self) {
use crate::debugger::DebugStop;
let pc = self.cpu.pc & crate::debugger::UI_ADDR_MASK;
let pc = self.cpu.pc & self.cpu.address_mask;
// Exception catchpoints: the core records every exception entry
// (trap, fault, or interrupt) as it loads the handler vector;
// drain it here so a hit stops at the handler's first
Expand All @@ -1467,7 +1474,7 @@ impl M68kMachine {
if self.ui_stop.is_some() {
return;
}
let writer_pc = self.cpu.ppc & crate::debugger::UI_ADDR_MASK;
let writer_pc = self.cpu.ppc & self.cpu.address_mask;
for i in 0..self.ui_breaks.watches.len() {
let addr = self.ui_breaks.watches[i].addr;
let new = self.bus.bus.peek_word_any(addr);
Expand Down Expand Up @@ -1548,7 +1555,7 @@ impl M68kMachine {
// a caught vector; stop before the handler's first
// instruction executes.
if self.ui_breaks.armed() {
let pc = self.cpu.pc & crate::debugger::UI_ADDR_MASK;
let pc = self.cpu.pc & self.cpu.address_mask;
if let Some(vector) = self.cpu.last_exception_vector.take() {
let vector = vector.min(u32::from(u16::MAX)) as u16;
if self.ui_breaks.catches.contains(&vector) {
Expand Down
50 changes: 39 additions & 11 deletions src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ pub struct Watch {
pub len: u32,
}

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

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

impl InteractiveBreaks {
/// An empty break set comparing addresses through `addr_mask` (the
/// owning machine's address-bus mask; see `address_mask_for_model`).
pub fn new(addr_mask: u32) -> Self {
Self {
addr_mask,
breakpoints: Vec::new(),
watches: Vec::new(),
reg_watches: Vec::new(),
catches: Vec::new(),
task_catch: None,
armed: false,
}
}

pub fn armed(&self) -> bool {
self.armed
}
Expand All @@ -721,7 +739,7 @@ impl InteractiveBreaks {
/// Whether any breakpoint is set at `pc`, ignoring its condition. Used for
/// display (marking the address) and the reverse-debug scan.
pub fn is_breakpoint(&self, pc: u32) -> bool {
let pc = pc & UI_ADDR_MASK;
let pc = pc & self.addr_mask;
self.breakpoints.iter().any(|bp| bp.addr == pc)
}

Expand All @@ -734,7 +752,7 @@ impl InteractiveBreaks {
cond: Option<BreakCond>,
ignore: u32,
) -> bool {
let addr = addr & UI_ADDR_MASK;
let addr = addr & self.addr_mask;
let added = match self.breakpoints.iter().position(|bp| bp.addr == addr) {
Some(pos) => {
self.breakpoints.remove(pos);
Expand All @@ -759,7 +777,7 @@ impl InteractiveBreaks {
/// ignore count has been exhausted -- each qualifying hit before that is
/// counted and skipped.
pub fn breakpoint_stops(&mut self, pc: u32, ctx: &dyn BreakContext) -> bool {
let pc = pc & UI_ADDR_MASK;
let pc = pc & self.addr_mask;
let Some(bp) = self.breakpoints.iter_mut().find(|bp| bp.addr == pc) else {
return false;
};
Expand Down Expand Up @@ -1118,7 +1136,7 @@ mod tests {

#[test]
fn interactive_breakpoints_toggle_mask_and_arm() {
let mut breaks = InteractiveBreaks::default();
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
assert!(!breaks.armed());

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

#[test]
fn full_mask_keeps_z3_breakpoints_distinct_from_chip_aliases() {
// On a 32-bit CPU a Zorro III breakpoint must not fire at the
// chip-RAM address it would alias through a 24-bit mask.
let mut breaks = InteractiveBreaks::new(0xFFFF_FFFF);
assert!(breaks.toggle_breakpoint_full(0x4000_1000, None, 0));
assert!(breaks.is_breakpoint(0x4000_1000));
assert!(!breaks.is_breakpoint(0x0000_1000));
}

/// Fixed register/memory snapshot for exercising condition evaluation.
#[derive(Default)]
struct FakeCtx {
Expand Down Expand Up @@ -1162,7 +1190,7 @@ mod tests {

#[test]
fn conditional_breakpoint_stops_only_when_condition_holds() {
let mut breaks = InteractiveBreaks::default();
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
breaks.toggle_breakpoint_full(
0x1000,
Some(BreakCond {
Expand All @@ -1186,7 +1214,7 @@ mod tests {

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

#[test]
fn bit_test_condition_uses_memory_word() {
let mut breaks = InteractiveBreaks::default();
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
breaks.toggle_breakpoint_full(
0x40,
Some(BreakCond {
Expand All @@ -1219,7 +1247,7 @@ mod tests {

#[test]
fn interactive_watches_record_baselines_and_clear() {
let mut breaks = InteractiveBreaks::default();
let mut breaks = InteractiveBreaks::new(UI_ADDR_MASK);
assert!(breaks.toggle_watch(0x1000, 0xABCD, None));
assert_eq!(breaks.watches[0].last, 0xABCD);
// The register watch normalizes a full $DFFxxx address to the
Expand Down
8 changes: 4 additions & 4 deletions src/gdbstub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! memory-mapped devices; Amiga custom-chip state is exposed through `monitor`
//! commands so inspection remains side-effect-free.

use crate::debugger::{custom_reg_name, UI_ADDR_MASK};
use crate::debugger::custom_reg_name;
use crate::emulator::Emulator;
use crate::timetravel::ReverseOutcome;
use anyhow::{anyhow, bail, Context, Result};
Expand Down Expand Up @@ -391,7 +391,7 @@ impl Session {

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

fn remove_breakpoint(&mut self, packet: &str) -> Result<String> {
let (addr, _) = parse_z_packet(packet)?;
let addr = addr & UI_ADDR_MASK;
let addr = addr & self.emu.machine.ui_addr_mask();
self.breakpoints.retain(|&candidate| candidate != addr);
Ok("OK".to_string())
}
Expand Down Expand Up @@ -485,7 +485,7 @@ impl Session {
if self.emu.bus_mut().take_ui_copper_hit().is_some() {
return Ok(Some(StopReason::CopperBreak));
}
let pc = self.emu.machine.pc() & UI_ADDR_MASK;
let pc = self.emu.machine.pc() & self.emu.machine.ui_addr_mask();
if self.breakpoints.contains(&pc) {
return Ok(Some(StopReason::Breakpoint));
}
Expand Down
24 changes: 12 additions & 12 deletions src/video/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3206,7 +3206,7 @@ impl App {
let mut panel = ui::DebuggerPanel::new();
// Start the memory view at the current program counter's
// neighbourhood; it is usually what you came to look at.
panel.mem_addr = self.emu.machine.pc() & 0x00FF_FFF0;
panel.mem_addr = self.emu.machine.pc() & self.emu.machine.ui_addr_mask() & !0xF;
self.debugger_panel = Some(panel);
self.emu.machine.ui_set_pc_history_enabled(true);
// Arm reverse debugging so the < Step / < Run controls work. A
Expand Down Expand Up @@ -4453,7 +4453,7 @@ impl App {
self.reported_double_fault = true;
let message = format!(
"CPU halted: double fault at pc ${:06X} (bus/address error during exception)",
self.emu.machine.pc() & 0x00FF_FFFF
self.emu.machine.pc() & self.emu.machine.ui_addr_mask()
);
warn!("{message}");
self.last_debug_stop = Some(message.clone());
Expand Down Expand Up @@ -4498,7 +4498,7 @@ impl App {
let set = self.emu.machine.ui_set_breakpoint(addr, cond, ignore);
let mut msg = format!(
"Breakpoint ${:06X} {}",
addr & 0x00FF_FFFF,
addr & self.emu.machine.ui_addr_mask(),
if set { "set" } else { "removed" }
);
if set {
Expand All @@ -4517,7 +4517,7 @@ impl App {
let Some(addr) = self.debugger_entry_addr("Watch") else {
return;
};
let addr = addr & 0x00FF_FFFE;
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
let set = self.emu.machine.ui_toggle_watch(addr);
self.show_osd(format!(
"Watchpoint ${addr:06X} {}",
Expand Down Expand Up @@ -4564,7 +4564,7 @@ impl App {
panel.mem_addr.wrapping_sub(delta)
} else {
panel.mem_addr.wrapping_add(delta)
} & 0x00FF_FFFF;
} & self.emu.machine.ui_addr_mask();
if !panel.mem_view_bits {
panel.mem_addr &= !0xF;
}
Expand All @@ -4587,7 +4587,7 @@ impl App {
panel.mem_addr.wrapping_sub(delta)
} else {
panel.mem_addr.wrapping_add(delta)
} & 0x00FF_FFFF;
} & self.emu.machine.ui_addr_mask();
self.request_redraw();
}
}
Expand All @@ -4608,7 +4608,7 @@ impl App {
.mem_last_find
.map(|addr| addr.wrapping_add(1))
.unwrap_or(panel.mem_addr)
& 0x00FF_FFFF;
& self.emu.machine.ui_addr_mask();
const SPACE: u64 = 0x0100_0000;
const CHUNK: usize = 4096;
let mut offset = 0u64;
Expand Down Expand Up @@ -4689,7 +4689,7 @@ impl App {
self.show_osd("Writer: type a hex address first");
return;
};
let addr = addr & 0x00FF_FFFE;
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
let before = self.emu.retired_instructions();
match self.emu.tt_last_writer(addr, before) {
Ok(ReverseOutcome::Found(rec)) => {
Expand All @@ -4698,7 +4698,7 @@ impl App {
rec.addr,
rec.old,
rec.new,
rec.pc & 0x00FF_FFFF,
rec.pc & self.emu.machine.ui_addr_mask(),
rec.frame
);
info!("last-writer {message}");
Expand Down Expand Up @@ -5367,7 +5367,7 @@ impl App {
if panel.mem_view_bits {
let stride = panel.mem_bitmap_stride.max(1) as usize;
let rows = ui::mem_bitmap_rows();
let base = panel.mem_addr & 0x00FF_FFFF;
let base = panel.mem_addr & machine.ui_addr_mask();
lines.push(ui::DbgLine::plain(format!(
"bitplane at ${base:06X}, stride {stride} bytes ({} px), {rows} rows",
stride * 8
Expand All @@ -5383,9 +5383,9 @@ impl App {
"$ box: jump / \"ADDR VALUE\" poke / \"ADDR LEN\" save / hex bytes find",
));
lines.push(ui::DbgLine::plain(""));
let base = panel.mem_addr & 0x00FF_FFF0;
let base = panel.mem_addr & machine.ui_addr_mask() & !0xF;
for row in 0..16u32 {
let addr = base.wrapping_add(row * 16) & 0x00FF_FFFF;
let addr = base.wrapping_add(row * 16) & machine.ui_addr_mask();
let mut bytes = [0u8; 16];
for word in 0..8u32 {
let value = bus.peek_word_any(addr.wrapping_add(word * 2));
Expand Down
19 changes: 11 additions & 8 deletions src/video/window/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ impl App {
let set = self.emu.machine.ui_set_breakpoint(addr, cond, ignore);
ConsoleOutcome::one(format!(
"breakpoint ${:06X} {}",
addr & 0x00FF_FFFF,
addr & self.emu.machine.ui_addr_mask(),
if set { "set" } else { "removed" }
))
}
Expand All @@ -398,7 +398,7 @@ impl App {
let set = self.emu.machine.ui_toggle_watch_filtered(addr, filter);
ConsoleOutcome::one(format!(
"watchpoint ${:06X}{} {}",
addr & 0x00FF_FFFE,
addr & self.emu.machine.ui_addr_mask() & !1,
filter
.map(|f| format!(" ({} writes only)", f.label()))
.unwrap_or_default(),
Expand Down Expand Up @@ -482,7 +482,10 @@ impl App {
// exec's Alert() lives at LVO -108; the jump-table
// entry itself executes, so a PC breakpoint there
// fires on every alert with D7 = the guru code.
vec![format!("{:06X}", base.wrapping_sub(108) & 0x00FF_FFFF)]
vec![format!(
"{:06X}",
base.wrapping_sub(108) & self.emu.machine.ui_addr_mask()
)]
});
let Some(addr) = lvo
.first()
Expand Down Expand Up @@ -805,7 +808,7 @@ impl App {
let Some(addr) = args.first().and_then(|t| hex32(t)) else {
return ConsoleOutcome::error("usage: WRITER ADDR (hex, word)");
};
let addr = addr & 0x00FF_FFFE;
let addr = addr & self.emu.machine.ui_addr_mask() & !1;
let before = self.emu.retired_instructions();
let outcome = match self.emu.tt_last_writer(addr, before) {
Ok(crate::timetravel::ReverseOutcome::Found(rec)) => {
Expand All @@ -814,7 +817,7 @@ impl App {
rec.addr,
rec.old,
rec.new,
rec.pc & 0x00FF_FFFF,
rec.pc & self.emu.machine.ui_addr_mask(),
rec.frame
))
}
Expand Down Expand Up @@ -1284,16 +1287,16 @@ impl App {
let cpu_type = machine.cpu_type();
let mut lines = vec![format!(
"#0 pc ${:06X} sp ${:06X}",
machine.pc() & 0x00FF_FFFF,
sp & 0x00FF_FFFF
machine.pc() & machine.ui_addr_mask(),
sp & machine.ui_addr_mask()
)];
let mut frame = 1usize;
for slot in 0..SLOTS {
if frame > FRAMES {
break;
}
let slot_addr = sp.wrapping_add(slot * 4);
let value = peek32(slot_addr) & 0x00FF_FFFF;
let value = peek32(slot_addr) & machine.ui_addr_mask();
if !looks_like_return(value) {
continue;
}
Expand Down
Loading