Skip to content

Commit

Permalink
debugger: add watch values an r/w option for watch
Browse files Browse the repository at this point in the history
  • Loading branch information
alloncm committed Jan 18, 2025
1 parent 19a5bae commit d19a106
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 57 deletions.
100 changes: 67 additions & 33 deletions core/src/debugger/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<u8>),
RemoveWatch(Address),
PpuInfo,
GetPpuLayer(PpuLayer)
}
Expand All @@ -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<u8>),
Disassembly(u16, u16, Vec<OpcodeEntry>),
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]>)
}
Expand Down Expand Up @@ -97,7 +131,7 @@ pub trait DebuggerInterface{

pub struct Debugger<UI:DebuggerInterface>{
ui:UI,
breakpoints:HashSet<(u16, u16)>,
breakpoints:HashSet<Address>,
skip_halt: bool
}

Expand All @@ -113,11 +147,11 @@ impl<UI:DebuggerInterface> Debugger<UI>{
(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!{{
Expand Down Expand Up @@ -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);
},
Expand All @@ -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()))),
Expand All @@ -190,14 +224,14 @@ impl_gameboy!{{
}}

pub struct MemoryWatcher{
pub watching_addresses: HashSet<(u16, u16)>,
pub watching_addresses: HashMap<Address, (WatchMode, Option<u8>)>,
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<u8>){_ = self.watching_addresses.insert(address, (mode, value))}
pub fn try_remove_address(&mut self, address:Address)->bool{self.watching_addresses.remove(&address).is_some()}
}
16 changes: 12 additions & 4 deletions core/src/mmu/gb_mmu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,25 @@ 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;
}

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);
Expand Down
59 changes: 39 additions & 20 deletions sdl/src/terminal_debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -77,18 +78,18 @@ impl TerminalDebugger{
fn handle_debugger_result(result:DebuggerResult, ppu_layer_sender:Sender<PpuLayerResult>, enabled:Arc<AtomicBool>){
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{
Expand All @@ -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()
Expand All @@ -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){
Expand All @@ -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<u8> = 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(),
Expand All @@ -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<Address, String>{
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<u16, String> {
Expand Down Expand Up @@ -209,6 +214,20 @@ fn parse_ppu_layer(buffer: &Vec<&str>)->Result<PpuLayer, String>{
};
}

fn parse_watch_mode(buffer: &Vec<&str>, index:usize)->Result<WatchMode, String>{
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)}

Expand Down

0 comments on commit d19a106

Please sign in to comment.