Debugger: Route BreakpointSubscriber, DisasmSubscriber, and most of

CPUCoreSubscriber through the CPU thread

Continues the pattern started with SteppingSubscriber: route breakpoint,
disassembly, symbol, and register access through Core_RunOnCPUThread()
instead of touching that state directly from the WebSocket handler thread.

Two intentional exceptions, matching the reasoning already used for
cpu.stepInto's "not currently stepping" branch:
- cpu.stepping's Core_Break() call stays unqueued - it's what makes the CPU
  thread start reaching the queue drain point in the first place.
- cpu.status stays unqueued - it's meant to be a cheap, frequently-pollable
  status check, and its "pc" field is already documented as inaccurate unless
  stepping. Matches how SteppingBroadcaster already reads the same state
  directly from the WebSocket thread.

Where a handler's response doesn't depend on anything the queued lambda
computed (plain add/remove endpoints), moved req.Respond() back out after
the Core_RunOnCPUThread() call for readability - the JSON building and
socket write happen later in Finish() regardless of where Respond() is
called, so there's no thread-safety difference either way, just clarity
about what actually needs to run on the CPU thread.

memory.searchDisasm carries the same caveat flagged for memory.search: its
scan range has no size cap, so if the CPU is stepping, a very large range
will now block the CPU thread's own frame pump for the scan's duration
rather than running unqueued on the WebSocket thread as before. Not fixed
here - noted in a comment at the call site.

Replaced remaining `auto` locals in these three files with concrete types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
This commit is contained in:
Henrik RydgårdandClaude Sonnet 5 committed 2026-08-08 11:56:32 +02:00
1 parent 29825c1e61
commit c32df3d931
3 files changed
+401 -307

No files matched your search

+112 -71
View File
@@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/StringUtils.h"
#include "Core/Core.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/SymbolMap.h"
@@ -138,8 +139,12 @@ void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
if (!params.Parse(req))
return;
g_breakpoints.AddBreakPoint(params.address);
params.Apply();
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
g_breakpoints.AddBreakPoint(params.address);
params.Apply();
});
req.Respond();
}
@@ -157,11 +162,19 @@ void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
WebSocketCPUBreakpointParams params;
if (!params.Parse(req))
return;
bool enabled;
if (!g_breakpoints.IsAddressBreakPoint(params.address, &enabled))
return req.Fail("Breakpoint not found");
params.Apply();
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
bool found = false;
Core_RunOnCPUThread([&] {
bool enabled;
found = g_breakpoints.IsAddressBreakPoint(params.address, &enabled);
if (found)
params.Apply();
});
if (!found)
return req.Fail("Breakpoint not found");
req.Respond();
}
@@ -180,7 +193,11 @@ void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
if (!req.ParamU32("address", &address))
return;
g_breakpoints.RemoveBreakPoint(address);
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
g_breakpoints.RemoveBreakPoint(address);
});
req.Respond();
}
@@ -202,38 +219,42 @@ void WebSocketCPUBreakpointList(DebuggerRequest &req) {
return req.Fail("CPU not started");
}
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
auto bps = g_breakpoints.GetBreakpoints();
for (const auto &bp : bps) {
if (bp.temporary)
continue;
// Route the breakpoint/symbol/disassembly reads to the CPU thread instead of poking at them directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
std::vector<BreakPoint> bps = g_breakpoints.GetBreakpoints();
for (const BreakPoint &bp : bps) {
if (bp.temporary)
continue;
json.pushDict();
json.writeUint("address", bp.addr);
json.writeBool("enabled", bp.IsEnabled());
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
if (bp.hasCond)
json.writeString("condition", bp.cond.expressionString);
else
json.writeNull("condition");
if (!bp.logFormat.empty())
json.writeString("logFormat", bp.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
json.pushDict();
json.writeUint("address", bp.addr);
json.writeBool("enabled", bp.IsEnabled());
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
if (bp.hasCond)
json.writeString("condition", bp.cond.expressionString);
else
json.writeNull("condition");
if (!bp.logFormat.empty())
json.writeString("logFormat", bp.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
DisassemblyLineInfo line;
g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS);
json.writeString("code", line.name + " " + line.params);
DisassemblyLineInfo line;
g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS);
json.writeString("code", line.name + " " + line.params);
json.pop();
}
json.pop();
}
json.pop();
});
}
struct WebSocketMemoryBreakpointParams {
@@ -361,8 +382,12 @@ void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
if (!params.Parse(req))
return;
g_breakpoints.AddMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
g_breakpoints.AddMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
});
req.Respond();
}
@@ -386,12 +411,20 @@ void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
if (!params.Parse(req))
return;
MemCheck mc;
if (!g_breakpoints.GetMemCheck(params.address, params.end, &mc))
return req.Fail("Breakpoint not found");
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
bool found = false;
Core_RunOnCPUThread([&] {
MemCheck mc;
found = g_breakpoints.GetMemCheck(params.address, params.end, &mc);
if (found) {
g_breakpoints.ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
}
});
g_breakpoints.ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
if (!found)
return req.Fail("Breakpoint not found");
req.Respond();
}
@@ -414,7 +447,11 @@ void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
if (!req.ParamU32("size", &size))
return;
g_breakpoints.RemoveMemCheck(address, size == 0 ? 0 : address + size);
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
g_breakpoints.RemoveMemCheck(address, size == 0 ? 0 : address + size);
});
req.Respond();
}
@@ -440,34 +477,38 @@ void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
return req.Fail("CPU not started");
}
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
auto mcs = g_breakpoints.GetMemChecks();
for (const auto &mc : mcs) {
json.pushDict();
json.writeUint("address", mc.start);
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
json.writeBool("enabled", mc.IsEnabled());
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
json.writeUint("hits", mc.numHits);
if (mc.hasCondition)
json.writeString("condition", mc.condition.expressionString);
else
json.writeNull("condition");
if (!mc.logFormat.empty())
json.writeString("logFormat", mc.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(mc.start);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
// Route the breakpoint/symbol reads to the CPU thread instead of poking at them directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
std::vector<MemCheck> mcs = g_breakpoints.GetMemChecks();
for (const MemCheck &mc : mcs) {
json.pushDict();
json.writeUint("address", mc.start);
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
json.writeBool("enabled", mc.IsEnabled());
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
json.writeUint("hits", mc.numHits);
if (mc.hasCondition)
json.writeString("condition", mc.condition.expressionString);
else
json.writeNull("condition");
if (!mc.logFormat.empty())
json.writeString("logFormat", mc.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(mc.start);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
json.pop();
}
json.pop();
}
json.pop();
});
}