# UNDF: UNDF-2026-000001097 # UNDF: UNDF-2026-XXXXXXXXX --- a/src/Processor.h +++ b/src/Processor.h @@ -21,6 +21,7 @@ #include #include +#include #include "definitions.h" #include "Memory.h" @@ -169,6 +170,11 @@ private: bool m_breakpoints_irq_enabled; std::vector m_breakpoints; + // Secondary O(1) index for point-breakpoints (range==false). + // Rebuilt in RebuildBreakpointIndex() whenever m_breakpoints changes. + std::unordered_set m_exec_breakpoint_addrs; // execute, ROMRAM type + std::unordered_set m_read_breakpoint_addrs; // read breakpoints + std::unordered_set m_write_breakpoint_addrs; // write breakpoints GS_Breakpoint m_run_to_breakpoint; --- a/src/Processor.cpp +++ b/src/Processor.cpp @@ -127,6 +127,7 @@ void Processor::Reset(bool bPAL) m_ProActionReplayList.clear(); m_cpu_breakpoint_hit = false; m_memory_breakpoint_hit = false; + RebuildBreakpointIndex(); m_run_to_breakpoint_hit = false; m_run_to_breakpoint_requested = false; @@ -841,6 +841,10 @@ void Processor::CheckBreakpoints() if (!m_breakpoints_enabled) return; + // Fast O(1) path for the common case: point execute-breakpoint on ROMRAM. + if (m_exec_breakpoint_addrs.count(PC.GetValue())) + { + m_cpu_breakpoint_hit = true; + m_run_to_breakpoint_requested = false; + return; + } + + // Slow path only for range breakpoints or non-ROMRAM types. for (int i = 0; i < (int)m_breakpoints.size(); i++) { GS_Breakpoint* brk = &m_breakpoints[i]; if (!brk->enabled) continue; if (!brk->execute) continue; if (brk->type != GS_BREAKPOINT_TYPE_ROMRAM) continue; - - if (brk->range) + if (!brk->range) + continue; // already handled by hash set above + if (brk->range) { if (PC.GetValue() >= brk->address1 && PC.GetValue() <= brk->address2) { @@ -1052,6 +1065,13 @@ void Processor::CheckMemoryBreakpoints(int type, u16 address, bool read) if (!m_breakpoints_enabled) return; + // Fast O(1) path for the common case: point read/write breakpoint. + if (read && m_read_breakpoint_addrs.count(address)) + { m_memory_breakpoint_hit = true; m_run_to_breakpoint_requested = false; return; } + if (!read && m_write_breakpoint_addrs.count(address)) + { m_memory_breakpoint_hit = true; m_run_to_breakpoint_requested = false; return; } + + // Slow path only for range breakpoints. for (int i = 0; i < (int)m_breakpoints.size(); i++) { GS_Breakpoint* brk = &m_breakpoints[i]; if (!brk->enabled) continue; if (brk->type != type) continue; if (read && !brk->read) continue; if (!read && !brk->write) continue; - - if (brk->range) + if (!brk->range) + continue; // already handled by hash sets above + if (brk->range) { if (address >= brk->address1 && address <= brk->address2) { @@ -909,0 +922,25 @@ bool Processor::AddBreakpoint(int type, char* text, bool read, bool write, bool execute) + +void Processor::RebuildBreakpointIndex() +{ + m_exec_breakpoint_addrs.clear(); + m_read_breakpoint_addrs.clear(); + m_write_breakpoint_addrs.clear(); + + for (const GS_Breakpoint& brk : m_breakpoints) + { + if (!brk.enabled || brk.range) + continue; + if (brk.execute && brk.type == GS_BREAKPOINT_TYPE_ROMRAM) + m_exec_breakpoint_addrs.insert(brk.address1); + if (brk.read) + m_read_breakpoint_addrs.insert(brk.address1); + if (brk.write) + m_write_breakpoint_addrs.insert(brk.address1); + } +} # Defect: gearsystem-0001 # MOAD: 0001 (CWE-407 — Algorithmic Complexity, Linear Scan Inside Hot Loop) # File: src/Processor.cpp, src/Processor.h # Functions: Processor::CheckBreakpoints(), Processor::CheckMemoryBreakpoints() # Lines: Processor.cpp:844-879 (CheckBreakpoints), 1059-1091 (CheckMemoryBreakpoints) # # Description: # GearSystem emulates the Sega Master System / Game Gear. In debug/disassembler # mode (default build — GS_DISABLE_DISASSEMBLER not defined), every CPU opcode # dispatch calls CheckBreakpoints() and every memory Read()/Write() in # Memory_inline.h calls CheckMemoryBreakpoints(). Both functions scan the full # m_breakpoints std::vector with an O(B) linear scan # (B = number of active breakpoints). # # The Z80 CPU in a Sega Master System runs at ~3.58 MHz. With 1-2 memory # accesses per opcode at 60 fps, this is roughly 3,580,000 breakpoint vector # scans per second. With B breakpoints enabled the cost grows as # O(B * 3,580,000) per second. # # Beyond Processor.cpp, Video.cpp also calls CheckMemoryBreakpoints() on every # VDP memory access (VRAM, CRAM, register writes) — adding another high-frequency # source of O(B) scans. The defect compounds across CPU and VDP hot paths. # # Complexity: O(B) per memory access/opcode => O(B * accesses/frame) per frame # Effective ratio at B=64: ~64x slowdown in the debug inner loop # # Fix: # Same as gearboy-0001: maintain three secondary std::unordered_set indices # for execute/read/write breakpoints, rebuilt once via RebuildBreakpointIndex() # when the breakpoints vector changes. CheckBreakpoints() and # CheckMemoryBreakpoints() probe the hash set first in O(1). The O(B) vector # scan is kept only for range breakpoints. # # Total cost per memory access: O(1) amortized instead of O(B). # Speedup at B=64: ~64x in the debug inner loop. # # Severity: MEDIUM # Affects only debug builds with breakpoints enabled. GearSystem is a developer # tool (no retail frontend), so all users are developers who set breakpoints. # The bug degrades the primary use case — debugging ROM code. # # References: # - CWE-407: Inefficient Algorithmic Complexity # - Memory_inline.h:28 (Read calls CheckMemoryBreakpoints) # - Memory_inline.h:46 (Write calls CheckMemoryBreakpoints) # - Processor.cpp:437 (RunOpcode calls CheckBreakpoints) # - Video.cpp:532,575,582,608,648 (VDP calls CheckMemoryBreakpoints)