From d19a1063826c569697f66998af774e0d026f1274 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Jan 2025 18:47:57 +0200 Subject: [PATCH] debugger: add watch values an r/w option for watch --- core/src/debugger/mod.rs | 100 +++++++++++++++++++++++------------ core/src/mmu/gb_mmu.rs | 16 ++++-- sdl/src/terminal_debugger.rs | 59 ++++++++++++++------- 3 files changed, 118 insertions(+), 57 deletions(-) diff --git a/core/src/debugger/mod.rs b/core/src/debugger/mod.rs index 3f8c6094..9807a57d 100644 --- a/core/src/debugger/mod.rs +++ b/core/src/debugger/mod.rs @@ -1,6 +1,7 @@ mod disassembler; -use std::collections::HashSet; +use std::fmt::{Formatter, Display, Result}; +use std::collections::{HashSet, HashMap}; use crate::{*, machine::gameboy::*, cpu::gb_cpu::GbCpu, utils::vec2::Vec2, ppu::{ppu_state::PpuState, gb_ppu::GbPpu}}; use self::disassembler::{OpcodeEntry, disassemble}; @@ -12,18 +13,51 @@ pub enum PpuLayer{ Sprites } +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Address{ + pub mem_addr: u16, + pub bank: u16 +} + +impl Address{ + pub fn new(mem_addr:u16, bank:u16)->Self { Self { mem_addr, bank} } +} + +impl Display for Address{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_fmt(format_args!("{:#X}:{}", self.mem_addr, self.bank)) + } +} + +#[derive(Clone, Copy)] +pub enum WatchMode{ + Read, + Write, + ReadWrite +} + +impl PartialEq for WatchMode{ + fn eq(&self, other: &Self) -> bool { + let self_val = core::mem::discriminant(self); + let other_val = core::mem::discriminant(other); + let read_write_val = core::mem::discriminant(&WatchMode::ReadWrite); + + return self_val == other_val || read_write_val == self_val || read_write_val == other_val; + } +} + pub enum DebuggerCommand{ Stop, Step, Continue, SkipHalt, Registers, - Break(u16, u16), - RemoveBreak(u16, u16), + Break(Address), + RemoveBreak(Address), DumpMemory(u16, u16), Disassemble(u16), - Watch(u16, u16), - RemoveWatch(u16, u16), + Watch(Address, WatchMode, Option), + RemoveWatch(Address), PpuInfo, GetPpuLayer(PpuLayer) } @@ -34,20 +68,20 @@ pub const PPU_BUFFER_SIZE:usize = PPU_BUFFER_HEIGHT * PPU_BUFFER_WIDTH; pub enum DebuggerResult{ Registers(Registers), - AddedBreak(u16, u16), + AddedBreak(Address), HitBreak(u16, u16), - RemovedBreak(u16, u16), - BreakDoNotExist(u16, u16), + RemovedBreak(Address), + BreakDoNotExist(Address), Continuing, HaltWakeup, Stepped(u16, u16), Stopped(u16, u16), MemoryDump(u16, u16, Vec), Disassembly(u16, u16, Vec), - AddedWatch(u16, u16), + AddedWatch(Address), HitWatch(u16, u16, u16, u16, u8), - RemovedWatch(u16, u16), - WatchDoNotExist(u16, u16), + RemovedWatch(Address), + WatchDoNotExist(Address), PpuInfo(PpuInfo), PpuLayer(PpuLayer, Box<[Pixel;PPU_BUFFER_SIZE]>) } @@ -97,7 +131,7 @@ pub trait DebuggerInterface{ pub struct Debugger{ ui:UI, - breakpoints:HashSet<(u16, u16)>, + breakpoints:HashSet
, skip_halt: bool } @@ -113,11 +147,11 @@ impl Debugger{ (self.check_for_break(cpu.program_counter, bank) || self.ui.should_stop() || hit_watch) && !(cpu.halt && self.skip_halt) } - fn check_for_break(&self, pc:u16, bank:u16)->bool{self.breakpoints.contains(&(pc, bank))} + fn check_for_break(&self, pc:u16, bank:u16)->bool{self.breakpoints.contains(&Address::new(pc, bank))} - fn add_breakpoint(&mut self, address:u16, bank:u16){_ = self.breakpoints.insert((address, bank))} + fn add_breakpoint(&mut self, address:Address){_ = self.breakpoints.insert(address)} - fn try_remove_breakpoint(&mut self, address:u16, bank:u16)->bool{self.breakpoints.remove(&(address, bank))} + fn try_remove_breakpoint(&mut self, address:Address)->bool{self.breakpoints.remove(&address)} } impl_gameboy!{{ @@ -146,14 +180,14 @@ impl_gameboy!{{ }, DebuggerCommand::SkipHalt => self.debugger.skip_halt = true, DebuggerCommand::Registers => self.debugger.send(DebuggerResult::Registers(Registers::new(&self.cpu))), - DebuggerCommand::Break(address, bank) => { - self.debugger.add_breakpoint(address, bank); - self.debugger.send(DebuggerResult::AddedBreak(address, bank)); + DebuggerCommand::Break(address) => { + self.debugger.add_breakpoint(address); + self.debugger.send(DebuggerResult::AddedBreak(address)); }, - DebuggerCommand::RemoveBreak(address, bank)=>{ - let result = match self.debugger.try_remove_breakpoint(address, bank) { - true => DebuggerResult::RemovedBreak(address, bank), - false => DebuggerResult::BreakDoNotExist(address, bank) + DebuggerCommand::RemoveBreak(address)=>{ + let result = match self.debugger.try_remove_breakpoint(address) { + true => DebuggerResult::RemovedBreak(address), + false => DebuggerResult::BreakDoNotExist(address) }; self.debugger.send(result); }, @@ -169,14 +203,14 @@ impl_gameboy!{{ let result = disassemble(&self.cpu, &mut self.mmu, len); self.debugger.send(DebuggerResult::Disassembly(len, self.mmu.get_current_bank(self.cpu.program_counter), result)); }, - DebuggerCommand::Watch(address, bank)=>{ - self.mmu.mem_watch.add_address(address, bank); - self.debugger.send(DebuggerResult::AddedWatch(address, bank)); + DebuggerCommand::Watch(address, mode, value)=>{ + self.mmu.mem_watch.add_address(address, mode, value); + self.debugger.send(DebuggerResult::AddedWatch(address)); }, - DebuggerCommand::RemoveWatch(address, bank)=>{ - match self.mmu.mem_watch.try_remove_address(address, bank){ - true=>self.debugger.send(DebuggerResult::RemovedWatch(address, bank)), - false=>self.debugger.send(DebuggerResult::WatchDoNotExist(address, bank)), + DebuggerCommand::RemoveWatch(address)=>{ + match self.mmu.mem_watch.try_remove_address(address){ + true=>self.debugger.send(DebuggerResult::RemovedWatch(address)), + false=>self.debugger.send(DebuggerResult::WatchDoNotExist(address)), } }, DebuggerCommand::PpuInfo=>self.debugger.send(DebuggerResult::PpuInfo(PpuInfo::new(self.mmu.get_ppu()))), @@ -190,14 +224,14 @@ impl_gameboy!{{ }} pub struct MemoryWatcher{ - pub watching_addresses: HashSet<(u16, u16)>, + pub watching_addresses: HashMap)>, pub hit_addr:Option<(u16, u16, u8)>, pub current_rom_bank_number: u16, pub current_ram_bank_number: u8, } impl MemoryWatcher{ - pub fn new()->Self{Self { watching_addresses: HashSet::new(), hit_addr: None, current_rom_bank_number: 0, current_ram_bank_number: 0 }} - pub fn add_address(&mut self, address:u16, bank:u16){_ = self.watching_addresses.insert((address, bank))} - pub fn try_remove_address(&mut self, address:u16, bank:u16)->bool{self.watching_addresses.remove(&(address, bank))} + pub fn new()->Self{Self { watching_addresses: HashMap::new(), hit_addr: None, current_rom_bank_number: 0, current_ram_bank_number: 0 }} + pub fn add_address(&mut self, address:Address, mode: WatchMode, value:Option){_ = self.watching_addresses.insert(address, (mode, value))} + pub fn try_remove_address(&mut self, address:Address)->bool{self.watching_addresses.remove(&address).is_some()} } \ No newline at end of file diff --git a/core/src/mmu/gb_mmu.rs b/core/src/mmu/gb_mmu.rs index 6c92ab85..8e76cc83 100644 --- a/core/src/mmu/gb_mmu.rs +++ b/core/src/mmu/gb_mmu.rs @@ -55,8 +55,12 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G }; #[cfg(feature = "dbg")] - if self.mem_watch.watching_addresses.contains(&(address, self.get_current_bank(address))){ - self.mem_watch.hit_addr = Some((address, self.get_current_bank(address), value)); + if let Some(watch_value) = self.mem_watch.watching_addresses.get(&crate::debugger::Address::new(address, self.get_current_bank(address))){ + if watch_value.0 == crate::debugger::WatchMode::Read { + if watch_value.1.is_none() || watch_value.1.is_some_and(|v|v == value){ + self.mem_watch.hit_addr = Some((address, self.get_current_bank(address), value)); + } + } } return value; @@ -64,8 +68,12 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G fn write(&mut self, address:u16, value:u8, m_cycles:u8){ #[cfg(feature = "dbg")] - if self.mem_watch.watching_addresses.contains(&(address, self.get_current_bank(address))){ - self.mem_watch.hit_addr = Some((address, self.get_current_bank(address), value)); + if let Some(watch_value) = self.mem_watch.watching_addresses.get(&crate::debugger::Address::new(address, self.get_current_bank(address))){ + if watch_value.0 == crate::debugger::WatchMode::Write { + if watch_value.1.is_none() || watch_value.1.is_some_and(|v|v == value){ + self.mem_watch.hit_addr = Some((address, self.get_current_bank(address), value)); + } + } } self.cycle(m_cycles); diff --git a/sdl/src/terminal_debugger.rs b/sdl/src/terminal_debugger.rs index dedd68e4..a1e94d80 100644 --- a/sdl/src/terminal_debugger.rs +++ b/sdl/src/terminal_debugger.rs @@ -2,7 +2,7 @@ use std::{io::stdin, sync::{atomic::{AtomicBool, Ordering}, Arc}, thread}; use crossbeam_channel::{bounded, Sender, Receiver}; -use magenboy_core::{debugger::{DebuggerCommand, DebuggerInterface, DebuggerResult, PpuLayer, PPU_BUFFER_SIZE}, Pixel}; +use magenboy_core::{debugger::{DebuggerCommand, DebuggerInterface, DebuggerResult, PpuLayer, PPU_BUFFER_SIZE, Address, WatchMode}, Pixel}; const HELP_MESSAGE:&'static str = r"Debugger commands: - halt(h) - start the debugging session (halt the program execution) @@ -11,13 +11,14 @@ const HELP_MESSAGE:&'static str = r"Debugger commands: - skip_halt - skip untill CPU is hanlted - break(b) [address:bank] - set a break point - remove_break(rb) [address:bank] - delete a breakpoint -- registers(reg) - print the cpu registers state +- reg(r) - print the cpu registers state - disassemble(di) [number_of_opcodes] - print the disassembly of the next opcodes - dump(du) [address number_of_bytes] - print memory addresses values from current bank -- watch(w) [address:bank] - set a watch point +- watch(w) [address:bank R/W/RW optional_watch_value] - set a watch point - remove_watch(rw) [address:bank] - delete a watch point - ppu_info(pi) - print info about the ppu execution state - ppu_layer(pl) [layer] - a debug window with one ppu layer (win, bg, spr) +- help - prints this help message "; pub struct PpuLayerResult(pub Box<[Pixel; PPU_BUFFER_SIZE]>, pub PpuLayer); @@ -77,18 +78,18 @@ impl TerminalDebugger{ fn handle_debugger_result(result:DebuggerResult, ppu_layer_sender:Sender, enabled:Arc){ match result{ DebuggerResult::Stopped(addr, bank) => println!("Stopped -> {:#X}:{}", addr, bank), - DebuggerResult::Registers(regs) => println!("AF: 0x{:X}\nBC: 0x{:X}\nDE: 0x{:X}\nHL: 0x{:X}\nSP: 0x{:X}\nPC: 0x{:X}", - regs.af, regs.bc, regs.de, regs.hl, regs.sp, regs.pc), + DebuggerResult::Registers(regs) => println!("AF: 0x{:04X}\nBC: 0x{:04X}\nDE: 0x{:04X}\nHL: 0x{:04X}\nSP: 0x{:04X}\nPC: 0x{:04X}\nIME: {}", + regs.af, regs.bc, regs.de, regs.hl, regs.sp, regs.pc, regs.ime), DebuggerResult::HitBreak(addr, bank) =>{ enabled.store(true, Ordering::SeqCst); println!("Hit break: {:#X}:{}", addr, bank); } DebuggerResult::HaltWakeup => println!("Waked up from halt"), - DebuggerResult::AddedBreak(addr, bank)=>println!("Added BreakPoint successfully at address: {:#X}:{bank}", addr), + DebuggerResult::AddedBreak(addr)=>println!("Added BreakPoint successfully at address: {addr}"), DebuggerResult::Continuing=>println!("Continuing execution"), DebuggerResult::Stepped(addr, bank)=>println!("-> {:#X}:{}", addr, bank), - DebuggerResult::RemovedBreak(addr, bank) => println!("Removed breakpoint successfully at {:#X}:{}", addr, bank), - DebuggerResult::BreakDoNotExist(addr, bank) => println!("Breakpoint {:#X}:{} does not exist", addr, bank), + DebuggerResult::RemovedBreak(addr) => println!("Removed breakpoint successfully at {addr}"), + DebuggerResult::BreakDoNotExist(addr) => println!("Breakpoint {addr} does not exist"), DebuggerResult::MemoryDump(address, bank, buffer) => { const SPACING: usize = 16; for i in 0..buffer.len() as usize{ @@ -105,13 +106,13 @@ impl TerminalDebugger{ println!("{:#X}:{} {}", opcodes[i].address, bank, opcodes[i].string); } }, - DebuggerResult::AddedWatch(addr, bank)=>println!("Set Watch point at: {addr:#X}:{bank} successfully"), + DebuggerResult::AddedWatch(addr)=>println!("Set Watch point at: {addr} successfully"), DebuggerResult::HitWatch(address, addr_bank, pc, pc_bank, value) => { println!("Hit watch point: {address:#X}:{addr_bank} at address: {pc:#X}:{pc_bank} with value: {value:#X}"); enabled.store(true, Ordering::SeqCst); }, - DebuggerResult::RemovedWatch(addr, bank) => println!("Removed watch point {addr:#X}:{bank}"), - DebuggerResult::WatchDoNotExist(addr, bank) => println!("Watch point {addr:#X}:{bank} do not exist"), + DebuggerResult::RemovedWatch(addr) => println!("Removed watch point {addr}"), + DebuggerResult::WatchDoNotExist(addr) => println!("Watch point {addr} do not exist"), DebuggerResult::PpuInfo(info) => println!("PpuInfo: \nstate: {} \nlcdc: {:#X} \nstat: {:#X} \nly: {} \nbackground [X: {}, Y: {}] \nwindow [X: {}, Y: {}], \nbank: {}", info.ppu_state as u8, info.lcdc, info.stat, info.ly, info.background_pos.x, info.background_pos.y, info.window_pos.x, info.window_pos.y, info.vram_bank), DebuggerResult::PpuLayer(layer, buffer) => ppu_layer_sender.send(PpuLayerResult(buffer, layer)).unwrap() @@ -133,12 +134,12 @@ impl TerminalDebugger{ } "s"|"step"=>sender.send(DebuggerCommand::Step).unwrap(), "b"|"break"=>match parse_address_string(&buffer, 1) { - Ok((address, bank)) => sender.send(DebuggerCommand::Break(address, bank)).unwrap(), + Ok(address) => sender.send(DebuggerCommand::Break(address)).unwrap(), Err(msg) => println!("Error setting BreakPoint {}", msg), }, - "reg"|"registers"=>sender.send(DebuggerCommand::Registers).unwrap(), + "r"|"reg"|"registers"=>sender.send(DebuggerCommand::Registers).unwrap(), "rb"|"remove_break"=>match parse_address_string(&buffer, 1) { - Ok((address, bank)) => sender.send(DebuggerCommand::RemoveBreak(address, bank)).unwrap(), + Ok(address) => sender.send(DebuggerCommand::RemoveBreak(address)).unwrap(), Err(msg) => println!("Error deleting BreakPoint {}", msg), }, "di"|"disassemble"=>match parse_number_string(&buffer, 1){ @@ -150,12 +151,16 @@ impl TerminalDebugger{ (Err(msg), _) | (_, Err(msg)) => println!("Error dumping memory: {}", msg), }, - "w"|"watch"=> match parse_address_string(&buffer, 1){ - Ok((addr, bank)) => sender.send(DebuggerCommand::Watch(addr, bank)).unwrap(), - Err(msg) => println!("Error setting watch point {}", msg), + "w"|"watch"=> match (parse_address_string(&buffer, 1), parse_watch_mode(&buffer, 2)){ + (Ok(addr), Ok(mode)) => { + let watch_value:Option = parse_number_string(&buffer, 3).ok().map(|v|v.try_into().unwrap()); + sender.send(DebuggerCommand::Watch(addr, mode, watch_value)).unwrap() + } + (Err(msg), _) | + (_, Err(msg)) => println!("Error setting watch point {}", msg), } "rw"|"remove_watch"=>match parse_address_string(&buffer, 1){ - Ok((addr, bank)) => sender.send(DebuggerCommand::RemoveWatch(addr, bank)).unwrap(), + Ok(addr) => sender.send(DebuggerCommand::RemoveWatch(addr)).unwrap(), Err(msg) => println!("Error deleting watch point: {}", msg), }, "pi"|"ppu_info"=>sender.send(DebuggerCommand::PpuInfo).unwrap(), @@ -174,14 +179,14 @@ impl TerminalDebugger{ } /// Address is "memory_address:bank" format -fn parse_address_string(buffer: &Vec<&str>, index:usize)->Result<(u16, u16), String>{ +fn parse_address_string(buffer: &Vec<&str>, index:usize)->Result{ let Some(param) = buffer.get(index) else { return Result::Err(String::from("No parameter")) }; let strs:Vec<&str> = param.split(":").collect(); let mem_addr = parse_number_string(&strs, 0)?; let bank = parse_number_string(&strs, 1)?; - return Ok((mem_addr, bank)); + return Ok(Address::new(mem_addr, bank)); } fn parse_number_string(buffer: &Vec<&str>, index:usize) -> Result { @@ -209,6 +214,20 @@ fn parse_ppu_layer(buffer: &Vec<&str>)->Result{ }; } +fn parse_watch_mode(buffer: &Vec<&str>, index:usize)->Result{ + let Some(param) = buffer.get(index) else { + return Result::Err(String::from("No parameter")) + }; + + return match param.to_ascii_lowercase().as_str(){ + "r" => Ok(WatchMode::Read), + "w" => Ok(WatchMode::Write), + "rw" | + "wr" => Ok(WatchMode::ReadWrite), + _=>Err(String::from("Could not find watch mode (r/w/rw")) + }; +} + impl DebuggerInterface for TerminalDebugger{ fn should_stop(&self)->bool {self.enabled_flag.load(Ordering::SeqCst)}