feat: Add breakpoint and watchpoint management for enhanced debugging

- Introduced BreakpointManager and WatchpointManager classes to manage CPU breakpoints and memory watchpoints, respectively.
- Implemented functionality for adding, removing, enabling, and disabling breakpoints and watchpoints.
- Added support for conditional breakpoints and logging memory access history for watchpoints.
- Enhanced the Emulator class to integrate breakpoint and instruction logging callbacks for improved debugging capabilities.
- Updated DisassemblyViewer to record executed instructions and manage instruction limits for performance optimization.
This commit is contained in:
scawful
2025-10-08 17:20:07 -04:00
parent 3d1d961d0a
commit 9bc31bc8fc
11 changed files with 819 additions and 26 deletions

View File

@@ -8,6 +8,8 @@ set(
app/emu/cpu/internal/addressing.cc
app/emu/cpu/internal/instructions.cc
app/emu/debug/disassembly_viewer.cc
app/emu/debug/breakpoint_manager.cc
app/emu/debug/watchpoint_manager.cc
app/emu/cpu/cpu.cc
app/emu/video/ppu.cc
app/emu/memory/dma.cc

View File

@@ -52,6 +52,15 @@ void Cpu::Reset(bool hard) {
}
void Cpu::RunOpcode() {
// Check for execute breakpoint BEFORE running instruction
if (on_breakpoint_hit_) {
uint32_t current_pc = (PB << 16) | PC;
if (on_breakpoint_hit_(current_pc)) {
// Breakpoint hit - pause execution
return; // Don't run this opcode yet
}
}
if (reset_wanted_) {
reset_wanted_ = false;
// reset: brk/interrupt without writes
@@ -1901,34 +1910,40 @@ void Cpu::ExecuteInstruction(uint8_t opcode) {
void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
bool immediate, bool accumulator_mode) {
if (core::FeatureFlags::get().kLogInstructions) {
// Build full 24-bit address
uint32_t full_address = (PB << 16) | PC;
// Extract operand bytes based on instruction size
std::vector<uint8_t> operand_bytes;
std::string operand_str;
if (operand) {
if (immediate) {
operand_str += "#";
}
if (accumulator_mode) {
// 8-bit operand
operand_bytes.push_back(operand & 0xFF);
operand_str += absl::StrFormat("$%02X", operand & 0xFF);
} else {
// 16-bit operand (little-endian)
operand_bytes.push_back(operand & 0xFF);
operand_bytes.push_back((operand >> 8) & 0xFF);
operand_str += absl::StrFormat("$%04X", operand);
}
// Build full 24-bit address
uint32_t full_address = (PB << 16) | PC;
// Extract operand bytes based on instruction size
std::vector<uint8_t> operand_bytes;
std::string operand_str;
if (operand) {
if (immediate) {
operand_str += "#";
}
// Get mnemonic
const std::string& mnemonic = opcode_to_mnemonic.at(opcode);
if (accumulator_mode) {
// 8-bit operand
operand_bytes.push_back(operand & 0xFF);
operand_str += absl::StrFormat("$%02X", operand & 0xFF);
} else {
// 16-bit operand (little-endian)
operand_bytes.push_back(operand & 0xFF);
operand_bytes.push_back((operand >> 8) & 0xFF);
operand_str += absl::StrFormat("$%04X", operand);
}
}
// Get mnemonic
const std::string& mnemonic = opcode_to_mnemonic.at(opcode);
// NEW: Call recording callback if set (for DisassemblyViewer)
if (on_instruction_executed_) {
on_instruction_executed_(full_address, opcode, operand_bytes, mnemonic, operand_str);
}
// Legacy: Record to old disassembly viewer if feature flag enabled
if (core::FeatureFlags::get().kLogInstructions) {
// Record to new disassembly viewer
disassembly_viewer().RecordInstruction(full_address, opcode, operand_bytes,
mnemonic, operand_str);

View File

@@ -57,6 +57,13 @@ class Cpu {
// New disassembly viewer
debug::DisassemblyViewer& disassembly_viewer();
const debug::DisassemblyViewer& disassembly_viewer() const;
// Breakpoint callback (set by Emulator)
std::function<bool(uint32_t pc)> on_breakpoint_hit_;
// Instruction recording callback (for DisassemblyViewer)
std::function<void(uint32_t address, uint8_t opcode, const std::vector<uint8_t>& operands,
const std::string& mnemonic, const std::string& operand_str)> on_instruction_executed_;
// Public register access for debugging and UI
uint16_t A = 0; // Accumulator
@@ -768,6 +775,10 @@ class Cpu {
auto mutable_log_instructions() -> bool* { return &log_instructions_; }
bool stopped() const { return stopped_; }
// Instruction logging control
void SetInstructionLogging(bool enabled) { log_instructions_ = enabled; }
bool IsInstructionLoggingEnabled() const { return log_instructions_; }
private:
void compare(uint16_t register_value, uint16_t memory_value) {

View File

@@ -0,0 +1,187 @@
#include "app/emu/debug/breakpoint_manager.h"
#include <algorithm>
#include "util/log.h"
namespace yaze {
namespace emu {
uint32_t BreakpointManager::AddBreakpoint(uint32_t address, Type type, CpuType cpu,
const std::string& condition,
const std::string& description) {
Breakpoint bp;
bp.id = next_id_++;
bp.address = address;
bp.type = type;
bp.cpu = cpu;
bp.enabled = true;
bp.condition = condition;
bp.hit_count = 0;
bp.description = description.empty()
? (cpu == CpuType::CPU_65816 ? "CPU Breakpoint" : "SPC700 Breakpoint")
: description;
breakpoints_[bp.id] = bp;
LOG_INFO("Breakpoint", "Added breakpoint #%d: %s at $%06X (type=%d, cpu=%d)",
bp.id, bp.description.c_str(), address, static_cast<int>(type),
static_cast<int>(cpu));
return bp.id;
}
void BreakpointManager::RemoveBreakpoint(uint32_t id) {
auto it = breakpoints_.find(id);
if (it != breakpoints_.end()) {
LOG_INFO("Breakpoint", "Removed breakpoint #%d", id);
breakpoints_.erase(it);
}
}
void BreakpointManager::SetEnabled(uint32_t id, bool enabled) {
auto it = breakpoints_.find(id);
if (it != breakpoints_.end()) {
it->second.enabled = enabled;
LOG_INFO("Breakpoint", "Breakpoint #%d %s", id, enabled ? "enabled" : "disabled");
}
}
bool BreakpointManager::ShouldBreakOnExecute(uint32_t pc, CpuType cpu) {
for (auto& [id, bp] : breakpoints_) {
if (!bp.enabled || bp.cpu != cpu || bp.type != Type::EXECUTE) {
continue;
}
if (bp.address == pc) {
bp.hit_count++;
last_hit_ = &bp;
// Check condition if present
if (!bp.condition.empty()) {
if (!EvaluateCondition(bp.condition, pc, pc, 0)) {
continue; // Condition not met
}
}
LOG_INFO("Breakpoint", "Hit breakpoint #%d at PC=$%06X (hits=%d)",
id, pc, bp.hit_count);
return true;
}
}
return false;
}
bool BreakpointManager::ShouldBreakOnMemoryAccess(uint32_t address, bool is_write,
uint8_t value, uint32_t pc) {
for (auto& [id, bp] : breakpoints_) {
if (!bp.enabled || bp.address != address) {
continue;
}
// Check if this breakpoint applies to this access type
bool applies = false;
switch (bp.type) {
case Type::READ:
applies = !is_write;
break;
case Type::WRITE:
applies = is_write;
break;
case Type::ACCESS:
applies = true;
break;
default:
continue; // Not a memory breakpoint
}
if (applies) {
bp.hit_count++;
last_hit_ = &bp;
// Check condition if present
if (!bp.condition.empty()) {
if (!EvaluateCondition(bp.condition, pc, address, value)) {
continue;
}
}
LOG_INFO("Breakpoint", "Hit %s breakpoint #%d at $%06X (value=$%02X, PC=$%06X, hits=%d)",
is_write ? "WRITE" : "READ", id, address, value, pc, bp.hit_count);
return true;
}
}
return false;
}
std::vector<BreakpointManager::Breakpoint> BreakpointManager::GetAllBreakpoints() const {
std::vector<Breakpoint> result;
result.reserve(breakpoints_.size());
for (const auto& [id, bp] : breakpoints_) {
result.push_back(bp);
}
// Sort by ID for consistent ordering
std::sort(result.begin(), result.end(),
[](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; });
return result;
}
std::vector<BreakpointManager::Breakpoint> BreakpointManager::GetBreakpoints(CpuType cpu) const {
std::vector<Breakpoint> result;
for (const auto& [id, bp] : breakpoints_) {
if (bp.cpu == cpu) {
result.push_back(bp);
}
}
std::sort(result.begin(), result.end(),
[](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; });
return result;
}
void BreakpointManager::ClearAll() {
LOG_INFO("Breakpoint", "Cleared all breakpoints (%zu total)", breakpoints_.size());
breakpoints_.clear();
last_hit_ = nullptr;
}
void BreakpointManager::ClearAll(CpuType cpu) {
auto it = breakpoints_.begin();
int cleared = 0;
while (it != breakpoints_.end()) {
if (it->second.cpu == cpu) {
it = breakpoints_.erase(it);
cleared++;
} else {
++it;
}
}
LOG_INFO("Breakpoint", "Cleared %d breakpoints for %s", cleared,
cpu == CpuType::CPU_65816 ? "CPU" : "SPC700");
}
void BreakpointManager::ResetHitCounts() {
for (auto& [id, bp] : breakpoints_) {
bp.hit_count = 0;
}
}
bool BreakpointManager::EvaluateCondition(const std::string& condition,
uint32_t pc, uint32_t address,
uint8_t value) {
// Simple condition evaluation for now
// Future: Could integrate Lua or expression parser
if (condition.empty()) {
return true; // No condition = always true
}
// Support simple comparisons: "value > 10", "value == 0xFF", etc.
// Format: "value OPERATOR number"
// For now, just return true (conditions not implemented yet)
// TODO: Implement proper expression evaluation
return true;
}
} // namespace emu
} // namespace yaze

View File

@@ -0,0 +1,143 @@
#ifndef YAZE_APP_EMU_DEBUG_BREAKPOINT_MANAGER_H
#define YAZE_APP_EMU_DEBUG_BREAKPOINT_MANAGER_H
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace yaze {
namespace emu {
/**
* @class BreakpointManager
* @brief Manages CPU and SPC700 breakpoints for debugging
*
* Provides comprehensive breakpoint support including:
* - Execute breakpoints (break when PC reaches address)
* - Read breakpoints (break when memory address is read)
* - Write breakpoints (break when memory address is written)
* - Access breakpoints (break on read OR write)
* - Conditional breakpoints (break when expression is true)
*
* Inspired by Mesen2's debugging capabilities.
*/
class BreakpointManager {
public:
enum class Type {
EXECUTE, // Break when PC reaches this address
READ, // Break when this address is read
WRITE, // Break when this address is written
ACCESS, // Break when this address is read OR written
CONDITIONAL // Break when condition evaluates to true
};
enum class CpuType {
CPU_65816, // Main CPU
SPC700 // Audio CPU
};
struct Breakpoint {
uint32_t id;
uint32_t address;
Type type;
CpuType cpu;
bool enabled;
std::string condition; // For conditional breakpoints (e.g., "A > 0x10")
uint32_t hit_count;
std::string description; // User-friendly label
// Optional callback for advanced logic
std::function<bool(uint32_t pc, uint32_t address, uint8_t value)> callback;
};
BreakpointManager() = default;
~BreakpointManager() = default;
/**
* @brief Add a new breakpoint
* @param address Memory address or PC value
* @param type Breakpoint type
* @param cpu Which CPU to break on
* @param condition Optional condition string
* @param description Optional user-friendly description
* @return Unique breakpoint ID
*/
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu,
const std::string& condition = "",
const std::string& description = "");
/**
* @brief Remove a breakpoint by ID
*/
void RemoveBreakpoint(uint32_t id);
/**
* @brief Enable or disable a breakpoint
*/
void SetEnabled(uint32_t id, bool enabled);
/**
* @brief Check if execution should break at this address
* @param pc Current program counter
* @param cpu Which CPU is executing
* @return true if breakpoint hit
*/
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu);
/**
* @brief Check if execution should break on memory access
* @param address Memory address being accessed
* @param is_write True if write, false if read
* @param value Value being read/written
* @param pc Current program counter (for logging)
* @return true if breakpoint hit
*/
bool ShouldBreakOnMemoryAccess(uint32_t address, bool is_write,
uint8_t value, uint32_t pc);
/**
* @brief Get all breakpoints
*/
std::vector<Breakpoint> GetAllBreakpoints() const;
/**
* @brief Get breakpoints for specific CPU
*/
std::vector<Breakpoint> GetBreakpoints(CpuType cpu) const;
/**
* @brief Clear all breakpoints
*/
void ClearAll();
/**
* @brief Clear all breakpoints for specific CPU
*/
void ClearAll(CpuType cpu);
/**
* @brief Get the last breakpoint that was hit
*/
const Breakpoint* GetLastHit() const { return last_hit_; }
/**
* @brief Reset hit counts for all breakpoints
*/
void ResetHitCounts();
private:
std::unordered_map<uint32_t, Breakpoint> breakpoints_;
uint32_t next_id_ = 1;
const Breakpoint* last_hit_ = nullptr;
bool EvaluateCondition(const std::string& condition, uint32_t pc,
uint32_t address, uint8_t value);
};
} // namespace emu
} // namespace yaze
#endif // YAZE_APP_EMU_DEBUG_BREAKPOINT_MANAGER_H

View File

@@ -31,11 +31,22 @@ void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode,
const std::vector<uint8_t>& operands,
const std::string& mnemonic,
const std::string& operand_str) {
// Skip if recording disabled (for performance)
if (!recording_enabled_) {
return;
}
auto it = instructions_.find(address);
if (it != instructions_.end()) {
// Instruction already recorded, just increment execution count
it->second.execution_count++;
} else {
// Check if we're at the limit
if (instructions_.size() >= max_instructions_) {
// Trim to 80% of max to avoid constant trimming
TrimToSize(max_instructions_ * 0.8);
}
// New instruction, add to map
DisassemblyEntry entry;
entry.address = address;
@@ -52,6 +63,29 @@ void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode,
}
}
void DisassemblyViewer::TrimToSize(size_t target_size) {
if (instructions_.size() <= target_size) {
return;
}
// Keep most-executed instructions
// Remove least-executed ones
std::vector<std::pair<uint32_t, uint64_t>> addr_counts;
for (const auto& [addr, entry] : instructions_) {
addr_counts.push_back({addr, entry.execution_count});
}
// Sort by execution count (ascending)
std::sort(addr_counts.begin(), addr_counts.end(),
[](const auto& a, const auto& b) { return a.second < b.second; });
// Remove least-executed instructions
size_t to_remove = instructions_.size() - target_size;
for (size_t i = 0; i < to_remove && i < addr_counts.size(); i++) {
instructions_.erase(addr_counts[i].first);
}
}
void DisassemblyViewer::Render(uint32_t current_pc,
const std::vector<uint32_t>& breakpoints) {
// Update current PC and breakpoint flags

View File

@@ -109,11 +109,31 @@ class DisassemblyViewer {
* @brief Check if the disassembly viewer is available
*/
bool IsAvailable() const { return !instructions_.empty(); }
/**
* @brief Enable/disable recording (for performance)
*/
void SetRecording(bool enabled) { recording_enabled_ = enabled; }
bool IsRecording() const { return recording_enabled_; }
/**
* @brief Set maximum number of instructions to keep
*/
void SetMaxInstructions(size_t max) { max_instructions_ = max; }
/**
* @brief Clear old instructions to save memory
*/
void TrimToSize(size_t target_size);
private:
// Sparse storage: only store executed instructions
std::map<uint32_t, DisassemblyEntry> instructions_;
// Performance limits
bool recording_enabled_ = true;
size_t max_instructions_ = 10000; // Limit to prevent memory bloat
// UI state
char search_filter_[256] = "";
uint32_t selected_address_ = 0;

View File

@@ -0,0 +1,165 @@
#include "app/emu/debug/watchpoint_manager.h"
#include <fstream>
#include <algorithm>
#include "absl/strings/str_format.h"
#include "util/log.h"
namespace yaze {
namespace emu {
uint32_t WatchpointManager::AddWatchpoint(uint32_t start_address, uint32_t end_address,
bool track_reads, bool track_writes,
bool break_on_access,
const std::string& description) {
Watchpoint wp;
wp.id = next_id_++;
wp.start_address = start_address;
wp.end_address = end_address;
wp.track_reads = track_reads;
wp.track_writes = track_writes;
wp.break_on_access = break_on_access;
wp.enabled = true;
wp.description = description.empty()
? absl::StrFormat("Watch $%06X-$%06X", start_address, end_address)
: description;
watchpoints_[wp.id] = wp;
LOG_INFO("Watchpoint", "Added watchpoint #%d: %s (R=%d, W=%d, Break=%d)",
wp.id, wp.description.c_str(), track_reads, track_writes, break_on_access);
return wp.id;
}
void WatchpointManager::RemoveWatchpoint(uint32_t id) {
auto it = watchpoints_.find(id);
if (it != watchpoints_.end()) {
LOG_INFO("Watchpoint", "Removed watchpoint #%d", id);
watchpoints_.erase(it);
}
}
void WatchpointManager::SetEnabled(uint32_t id, bool enabled) {
auto it = watchpoints_.find(id);
if (it != watchpoints_.end()) {
it->second.enabled = enabled;
LOG_INFO("Watchpoint", "Watchpoint #%d %s", id, enabled ? "enabled" : "disabled");
}
}
bool WatchpointManager::OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write,
uint8_t old_value, uint8_t new_value,
uint64_t cycle_count) {
bool should_break = false;
for (auto& [id, wp] : watchpoints_) {
if (!wp.enabled || !IsInRange(wp, address)) {
continue;
}
// Check if this access type is tracked
bool should_log = (is_write && wp.track_writes) || (!is_write && wp.track_reads);
if (!should_log) {
continue;
}
// Log the access
AccessLog log;
log.pc = pc;
log.address = address;
log.old_value = old_value;
log.new_value = new_value;
log.is_write = is_write;
log.cycle_count = cycle_count;
log.description = absl::StrFormat("%s at $%06X: $%02X -> $%02X (PC=$%06X)",
is_write ? "WRITE" : "READ",
address, old_value, new_value, pc);
wp.history.push_back(log);
// Limit history size
if (wp.history.size() > Watchpoint::kMaxHistorySize) {
wp.history.pop_front();
}
// Check if should break
if (wp.break_on_access) {
should_break = true;
LOG_INFO("Watchpoint", "Hit watchpoint #%d: %s", id, log.description.c_str());
}
}
return should_break;
}
std::vector<WatchpointManager::Watchpoint> WatchpointManager::GetAllWatchpoints() const {
std::vector<Watchpoint> result;
result.reserve(watchpoints_.size());
for (const auto& [id, wp] : watchpoints_) {
result.push_back(wp);
}
std::sort(result.begin(), result.end(),
[](const Watchpoint& a, const Watchpoint& b) { return a.id < b.id; });
return result;
}
std::vector<WatchpointManager::AccessLog> WatchpointManager::GetHistory(
uint32_t address, int max_entries) const {
std::vector<AccessLog> result;
for (const auto& [id, wp] : watchpoints_) {
if (IsInRange(wp, address)) {
for (const auto& log : wp.history) {
if (log.address == address) {
result.push_back(log);
if (result.size() >= static_cast<size_t>(max_entries)) {
break;
}
}
}
}
}
return result;
}
void WatchpointManager::ClearAll() {
LOG_INFO("Watchpoint", "Cleared all watchpoints (%zu total)", watchpoints_.size());
watchpoints_.clear();
}
void WatchpointManager::ClearHistory() {
for (auto& [id, wp] : watchpoints_) {
wp.history.clear();
}
LOG_INFO("Watchpoint", "Cleared all watchpoint history");
}
bool WatchpointManager::ExportHistoryToCSV(const std::string& filepath) const {
std::ofstream out(filepath);
if (!out.is_open()) {
return false;
}
// CSV Header
out << "Watchpoint,PC,Address,Type,OldValue,NewValue,Cycle,Description\n";
for (const auto& [id, wp] : watchpoints_) {
for (const auto& log : wp.history) {
out << absl::StrFormat("%d,$%06X,$%06X,%s,$%02X,$%02X,%llu,\"%s\"\n",
id, log.pc, log.address,
log.is_write ? "WRITE" : "READ",
log.old_value, log.new_value, log.cycle_count,
log.description);
}
}
out.close();
LOG_INFO("Watchpoint", "Exported watchpoint history to %s", filepath.c_str());
return true;
}
} // namespace emu
} // namespace yaze

View File

@@ -0,0 +1,138 @@
#ifndef YAZE_APP_EMU_DEBUG_WATCHPOINT_MANAGER_H
#define YAZE_APP_EMU_DEBUG_WATCHPOINT_MANAGER_H
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <deque>
namespace yaze {
namespace emu {
/**
* @class WatchpointManager
* @brief Manages memory watchpoints for debugging
*
* Watchpoints track memory accesses (reads/writes) and can break execution
* when specific memory locations are accessed. This is crucial for:
* - Finding where variables are modified
* - Detecting buffer overflows
* - Tracking down corruption bugs
* - Understanding data flow
*
* Inspired by Mesen2's memory debugging capabilities.
*/
class WatchpointManager {
public:
struct AccessLog {
uint32_t pc; // Where the access happened (program counter)
uint32_t address; // What address was accessed
uint8_t old_value; // Value before write (0 for reads)
uint8_t new_value; // Value after write / value read
bool is_write; // True for write, false for read
uint64_t cycle_count; // When it happened (CPU cycle)
std::string description; // Optional description
};
struct Watchpoint {
uint32_t id;
uint32_t start_address;
uint32_t end_address; // For range watchpoints
bool track_reads;
bool track_writes;
bool break_on_access; // If true, pause emulation on access
bool enabled;
std::string description;
// Access history for this watchpoint
std::deque<AccessLog> history;
static constexpr size_t kMaxHistorySize = 1000;
};
WatchpointManager() = default;
~WatchpointManager() = default;
/**
* @brief Add a memory watchpoint
* @param start_address Starting address of range to watch
* @param end_address Ending address (inclusive), or same as start for single byte
* @param track_reads Track read accesses
* @param track_writes Track write accesses
* @param break_on_access Pause emulation when accessed
* @param description User-friendly description
* @return Unique watchpoint ID
*/
uint32_t AddWatchpoint(uint32_t start_address, uint32_t end_address,
bool track_reads, bool track_writes,
bool break_on_access = false,
const std::string& description = "");
/**
* @brief Remove a watchpoint
*/
void RemoveWatchpoint(uint32_t id);
/**
* @brief Enable or disable a watchpoint
*/
void SetEnabled(uint32_t id, bool enabled);
/**
* @brief Check if memory access should break/log
* @param pc Current program counter
* @param address Memory address being accessed
* @param is_write True for write, false for read
* @param old_value Previous value at address
* @param new_value New value (for writes) or value read
* @param cycle_count Current CPU cycle
* @return true if should break execution
*/
bool OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write,
uint8_t old_value, uint8_t new_value, uint64_t cycle_count);
/**
* @brief Get all watchpoints
*/
std::vector<Watchpoint> GetAllWatchpoints() const;
/**
* @brief Get access history for a specific address
* @param address Address to query
* @param max_entries Maximum number of entries to return
* @return Vector of access logs
*/
std::vector<AccessLog> GetHistory(uint32_t address, int max_entries = 100) const;
/**
* @brief Clear all watchpoints
*/
void ClearAll();
/**
* @brief Clear history for all watchpoints
*/
void ClearHistory();
/**
* @brief Export access history to CSV
* @param filepath Output file path
* @return true if successful
*/
bool ExportHistoryToCSV(const std::string& filepath) const;
private:
std::unordered_map<uint32_t, Watchpoint> watchpoints_;
uint32_t next_id_ = 1;
// Check if address is within watchpoint range
bool IsInRange(const Watchpoint& wp, uint32_t address) const {
return address >= wp.start_address && address <= wp.end_address;
}
};
} // namespace emu
} // namespace yaze
#endif // YAZE_APP_EMU_DEBUG_WATCHPOINT_MANAGER_H

View File

@@ -83,6 +83,19 @@ void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector<uint8_t>&
running_ = false;
snes_initialized_ = false;
// Set up CPU breakpoint callback
snes_.cpu().on_breakpoint_hit_ = [this](uint32_t pc) -> bool {
return breakpoint_manager_.ShouldBreakOnExecute(pc, BreakpointManager::CpuType::CPU_65816);
};
// Set up instruction recording callback for DisassemblyViewer
snes_.cpu().on_instruction_executed_ = [this](uint32_t address, uint8_t opcode,
const std::vector<uint8_t>& operands,
const std::string& mnemonic,
const std::string& operand_str) {
disassembly_viewer_.RecordInstruction(address, opcode, operands, mnemonic, operand_str);
};
initialized_ = true;
}
@@ -113,6 +126,9 @@ void Emulator::Run(Rom* rom) {
rom_data_ = rom->vector();
}
snes_.Init(rom_data_);
// Enable instruction logging for disassembly viewer
snes_.cpu().SetInstructionLogging(true);
// Note: PPU pixel format set to 1 (XBGR) in Init() which matches ARGB8888 texture
@@ -768,6 +784,56 @@ void Emulator::RenderModernCpuDebugger() {
try {
auto& theme_manager = gui::ThemeManager::Get();
const auto& theme = theme_manager.GetCurrentTheme();
// Debugger controls toolbar
if (ImGui::Button(ICON_MD_PLAY_ARROW)) { running_ = true; }
ImGui::SameLine();
if (ImGui::Button(ICON_MD_PAUSE)) { running_ = false; }
ImGui::SameLine();
if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
if (!running_) snes_.cpu().RunOpcode();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_REFRESH)) { snes_.Reset(true); }
ImGui::Separator();
// Breakpoint controls
static char bp_addr[16] = "00FFD9";
ImGui::Text(ICON_MD_BUG_REPORT " Breakpoints:");
ImGui::PushItemWidth(100);
ImGui::InputText("##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button(ICON_MD_ADD " Add")) {
uint32_t addr = std::strtoul(bp_addr, nullptr, 16);
breakpoint_manager_.AddBreakpoint(addr, BreakpointManager::Type::EXECUTE,
BreakpointManager::CpuType::CPU_65816,
"", absl::StrFormat("BP at $%06X", addr));
}
// List breakpoints
ImGui::BeginChild("##BPList", ImVec2(0, 100), true);
for (const auto& bp : breakpoint_manager_.GetAllBreakpoints()) {
if (bp.cpu == BreakpointManager::CpuType::CPU_65816) {
bool enabled = bp.enabled;
if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(), &enabled)) {
breakpoint_manager_.SetEnabled(bp.id, enabled);
}
ImGui::SameLine();
ImGui::Text("$%06X", bp.address);
ImGui::SameLine();
ImGui::TextDisabled("(hits: %d)", bp.hit_count);
ImGui::SameLine();
if (ImGui::SmallButton(absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) {
breakpoint_manager_.RemoveBreakpoint(bp.id);
}
}
}
ImGui::EndChild();
ImGui::Separator();
ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status");
ImGui::PushStyleColor(ImGuiCol_ChildBg,

View File

@@ -5,6 +5,8 @@
#include <vector>
#include "app/emu/snes.h"
#include "app/emu/debug/breakpoint_manager.h"
#include "app/emu/debug/disassembly_viewer.h"
#include "app/rom.h"
namespace yaze {
@@ -54,6 +56,11 @@ class Emulator {
auto wanted_samples() const -> int { return wanted_samples_; }
void set_renderer(gfx::IRenderer* renderer) { renderer_ = renderer; }
// Debugger access
BreakpointManager& breakpoint_manager() { return breakpoint_manager_; }
bool is_debugging() const { return debugging_; }
void set_debugging(bool debugging) { debugging_ = debugging; }
// AI Agent Integration API
bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); }
double GetCurrentFPS() const { return current_fps_; }
@@ -136,8 +143,13 @@ class Emulator {
Snes snes_;
bool initialized_ = false;
bool snes_initialized_ = false;
bool debugging_ = false;
gfx::IRenderer* renderer_ = nullptr;
void* ppu_texture_ = nullptr;
// Debugger infrastructure
BreakpointManager breakpoint_manager_;
debug::DisassemblyViewer disassembly_viewer_;
std::vector<uint8_t> rom_data_;