From 3edea91c8ea1066c9e691aeb0b00e9badc467a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 14 Aug 2026 10:21:20 +0200 Subject: [PATCH] Debugger: track hit counts on address breakpoints, expose via cpu.breakpoint.list BreakPoint (cpu.breakpoint.*) had no hit-count tracking at all, unlike MemCheck (memory.breakpoint.*), which already tracks numHits. This made it genuinely hard to tell "this breakpoint is never being reached" apart from "it's being reached but I'm not seeing the log/pause where I'm looking" - directly informed by repeatedly hitting exactly that ambiguity while debugging the VSH boot path this session (see docs/VSHBootInvestigation.md). Added BreakPoint::numHits, incremented in BreakpointManager::ExecBreakPoint() whenever a breakpoint's address is hit and any condition passes (matching MemCheck::Apply()'s existing semantics - counts real triggers, not just "execution passed through here"). Exposed as a new "hits" field in cpu.breakpoint.list's response. Verified live via PPSSPPHeadless + wsdbg: hits reads 0 before the CPU resumes, 1 after the breakpoint fires once. UnitTest.exe all: 49/49 passed. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- Core/Debugger/Breakpoints.cpp | 4 +++- Core/Debugger/Breakpoints.h | 5 +++++ Core/Debugger/WebSocket/BreakpointSubscriber.cpp | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 2ee645b174..b2e18f5855 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -281,7 +281,7 @@ BreakAction BreakpointManager::ExecBreakPoint(u32 addr) { return BREAK_ACTION_IGNORE; size_t bp = FindBreakpoint(addr, false); if (bp != INVALID_BREAKPOINT) { - const BreakPoint &info = breakPoints_[bp]; + BreakPoint &info = breakPoints_[bp]; if (info.hasCond) { // Evaluate the breakpoint and abort if necessary. @@ -290,6 +290,8 @@ BreakAction BreakpointManager::ExecBreakPoint(u32 addr) { return BREAK_ACTION_IGNORE; } + ++info.numHits; + if (info.result & BREAK_ACTION_LOG) { if (info.logFormat.empty()) { NOTICE_LOG(Log::JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str()); diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index cf4c70a48b..9d3f3ec33f 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -61,6 +61,11 @@ struct BreakPoint { bool hasCond = false; BreakPointCond cond; + // Matches MemCheck's numHits below - added after repeatedly needing to tell "is this + // breakpoint even being reached at all" from "it's reached but the log isn't showing up + // where I'm looking" during the VSH boot investigation (see docs/VSHBootInvestigation.md). + u32 numHits = 0; + bool IsEnabled() const { return (result & BREAK_ACTION_PAUSE) != 0; } diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp index 0580ea7a9c..d1f047007e 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -247,6 +247,9 @@ void WebSocketCPUBreakpointRemove(DebuggerRequest &req) { // - logFormat: null, or string to log when breakpoint trips, may include {expression} parts. // - symbol: null, or string label or symbol at breakpoint address. // - code: string disassembly of breakpoint address. +// - hits: unsigned integer, how many times this breakpoint's address has been reached +// (and any condition passed) since it was added - useful for confirming a breakpoint is +// actually being reached at all, independently of whether log/enabled is set. void WebSocketCPUBreakpointList(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive()) { return req.Fail("CPU not started"); @@ -266,6 +269,7 @@ void WebSocketCPUBreakpointList(DebuggerRequest &req) { json.writeUint("address", bp.addr); json.writeBool("enabled", bp.IsEnabled()); json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0); + json.writeUint("hits", bp.numHits); if (bp.hasCond) json.writeString("condition", bp.cond.expressionString); else