From 212ed7cdda790138ddb4e37eacbcd22fd822b76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 8 Aug 2026 12:40:18 +0200 Subject: [PATCH] Debugger: Route HLESubscriber through the CPU thread Same treatment as the other subscribers: thread list/wake/stop, function and data symbol list/add/remove/rename/scan, and backtrace now route their kernel thread, symbol map, and disassembly manager access through Core_RunOnCPUThread() instead of touching that state directly from the WebSocket handler thread. Memory::IsValidRange() checks that only depend on the request's own address/size params (not on anything CPU-thread-owned) stay outside the queued callback and fail fast, rather than making a pointless round trip through the CPU thread for a request already known to be invalid. hle.func.scan carries the same unbounded-range caveat already noted for memory.search: no cap on 'size' beyond valid memory range. Replaced remaining `auto` locals with concrete types. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH --- Core/Debugger/WebSocket/HLESubscriber.cpp | 607 +++++++++++++--------- 1 file changed, 347 insertions(+), 260 deletions(-) diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp index dec209244e..7f35b09f2f 100644 --- a/Core/Debugger/WebSocket/HLESubscriber.cpp +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -98,40 +98,44 @@ static bool DataTypeFromString(const std::string &s, DataType *out) { // - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting. // - isCurrent: boolean, true for the currently executing thread. void WebSocketHLEThreadList(DebuggerRequest &req) { - // Will just return none of the CPU isn't ready yet. - auto threads = GetThreadsInfo(); + // Route the actual kernel thread reads to the CPU thread instead of poking at them directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + // Will just return none of the CPU isn't ready yet. + std::vector threads = GetThreadsInfo(); - JsonWriter &json = req.Respond(); - json.pushArray("threads"); - for (const auto &th : threads) { - json.pushDict(); - json.writeUint("id", th.id); - json.writeString("name", th.name); - json.writeInt("status", th.status); - json.pushArray("statuses"); - if (th.status & THREADSTATUS_RUNNING) - json.writeString("running"); - if (th.status & THREADSTATUS_READY) - json.writeString("ready"); - if (th.status & THREADSTATUS_WAIT) - json.writeString("wait"); - if (th.status & THREADSTATUS_SUSPEND) - json.writeString("suspend"); - if (th.status & THREADSTATUS_DORMANT) - json.writeString("dormant"); - if (th.status & THREADSTATUS_DEAD) - json.writeString("dead"); + JsonWriter &json = req.Respond(); + json.pushArray("threads"); + for (const DebugThreadInfo &th : threads) { + json.pushDict(); + json.writeUint("id", th.id); + json.writeString("name", th.name); + json.writeInt("status", th.status); + json.pushArray("statuses"); + if (th.status & THREADSTATUS_RUNNING) + json.writeString("running"); + if (th.status & THREADSTATUS_READY) + json.writeString("ready"); + if (th.status & THREADSTATUS_WAIT) + json.writeString("wait"); + if (th.status & THREADSTATUS_SUSPEND) + json.writeString("suspend"); + if (th.status & THREADSTATUS_DORMANT) + json.writeString("dormant"); + if (th.status & THREADSTATUS_DEAD) + json.writeString("dead"); + json.pop(); + json.writeUint("pc", th.curPC); + json.writeUint("entry", th.entrypoint); + json.writeUint("initialStackSize", th.initialStack); + json.writeUint("currentStackSize", th.stackSize); + json.writeInt("priority", th.priority); + json.writeInt("waitType", (int)th.waitType); + json.writeBool("isCurrent", th.isCurrent); + json.pop(); + } json.pop(); - json.writeUint("pc", th.curPC); - json.writeUint("entry", th.entrypoint); - json.writeUint("initialStackSize", th.initialStack); - json.writeUint("currentStackSize", th.stackSize); - json.writeInt("priority", th.priority); - json.writeInt("waitType", (int)th.waitType); - json.writeBool("isCurrent", th.isCurrent); - json.pop(); - } - json.pop(); + }); } static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) { @@ -148,8 +152,8 @@ static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) { if (!req.ParamU32("thread", &threadID)) return false; - auto threads = GetThreadsInfo(); - for (const auto &t : threads) { + std::vector threads = GetThreadsInfo(); + for (const DebugThreadInfo &t : threads) { if (t.id == threadID) { *result = t; return true; @@ -169,27 +173,34 @@ static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) { // - thread: id repeated back. // - status: string 'ready'. void WebSocketHLEThreadWake(DebuggerRequest &req) { - DebugThreadInfo threadInfo{ -1 }; - if (!ThreadInfoForStatus(req, &threadInfo)) - return; + // Route the actual kernel thread manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + DebugThreadInfo threadInfo{ -1 }; + if (!ThreadInfoForStatus(req, &threadInfo)) + return; - switch (threadInfo.status) { - case THREADSTATUS_SUSPEND: - case THREADSTATUS_WAIT: - case THREADSTATUS_WAITSUSPEND: - if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0) - return req.Fail("Failed to resume thread"); - break; + switch (threadInfo.status) { + case THREADSTATUS_SUSPEND: + case THREADSTATUS_WAIT: + case THREADSTATUS_WAITSUSPEND: + if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0) { + req.Fail("Failed to resume thread"); + return; + } + break; - default: - return req.Fail("Cannot force run thread based on current status"); - } + default: + req.Fail("Cannot force run thread based on current status"); + return; + } - Reporting::NotifyDebugger(); + Reporting::NotifyDebugger(); - JsonWriter &json = req.Respond(); - json.writeUint("thread", threadInfo.id); - json.writeString("status", "ready"); + JsonWriter &json = req.Respond(); + json.writeUint("thread", threadInfo.id); + json.writeString("status", "ready"); + }); } // Force stop a thread (hle.thread.stop) @@ -201,33 +212,40 @@ void WebSocketHLEThreadWake(DebuggerRequest &req) { // - thread: id repeated back. // - status: string 'dormant'. void WebSocketHLEThreadStop(DebuggerRequest &req) { - DebugThreadInfo threadInfo{ -1 }; - if (!ThreadInfoForStatus(req, &threadInfo)) - return; + // Route the actual kernel thread manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + DebugThreadInfo threadInfo{ -1 }; + if (!ThreadInfoForStatus(req, &threadInfo)) + return; - switch (threadInfo.status) { - case THREADSTATUS_SUSPEND: - case THREADSTATUS_WAIT: - case THREADSTATUS_WAITSUSPEND: - case THREADSTATUS_READY: - __KernelStopThread(threadInfo.id, 0, "stopped from debugger"); - break; + switch (threadInfo.status) { + case THREADSTATUS_SUSPEND: + case THREADSTATUS_WAIT: + case THREADSTATUS_WAITSUSPEND: + case THREADSTATUS_READY: + __KernelStopThread(threadInfo.id, 0, "stopped from debugger"); + break; - default: - return req.Fail("Cannot force stop thread based on current status"); - } + default: + req.Fail("Cannot force stop thread based on current status"); + return; + } - // Get it again to verify. - if (!ThreadInfoForStatus(req, &threadInfo)) - return; - if ((threadInfo.status & THREADSTATUS_DORMANT) == 0) - return req.Fail("Failed to stop thread"); + // Get it again to verify. + if (!ThreadInfoForStatus(req, &threadInfo)) + return; + if ((threadInfo.status & THREADSTATUS_DORMANT) == 0) { + req.Fail("Failed to stop thread"); + return; + } - Reporting::NotifyDebugger(); + Reporting::NotifyDebugger(); - JsonWriter &json = req.Respond(); - json.writeUint("thread", threadInfo.id); - json.writeString("status", "dormant"); + JsonWriter &json = req.Respond(); + json.writeUint("thread", threadInfo.id); + json.writeString("status", "dormant"); + }); } // List all current known function symbols (hle.func.list) @@ -243,18 +261,22 @@ void WebSocketHLEFuncList(DebuggerRequest &req) { if (!g_symbolMap) return req.Fail("CPU not active"); - auto functions = g_symbolMap->GetAllActiveSymbols(ST_FUNCTION); + // Route the actual 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([&] { + std::vector functions = g_symbolMap->GetAllActiveSymbols(ST_FUNCTION); - JsonWriter &json = req.Respond(); - json.pushArray("functions"); - for (auto f : functions) { - json.pushDict(); - json.writeString("name", f.name); - json.writeUint("address", f.address); - json.writeUint("size", f.size); + JsonWriter &json = req.Respond(); + json.pushArray("functions"); + for (const SymbolEntry &f : functions) { + json.pushDict(); + json.writeString("name", f.name); + json.writeUint("address", f.address); + json.writeUint("size", f.size); + json.pop(); + } json.pop(); - } - json.pop(); + }); } // Add a new function symbols (hle.func.add) @@ -297,51 +319,57 @@ void WebSocketHLEFuncAdd(DebuggerRequest &req) { if (name.empty()) name = StringFromFormat("z_un_%08x", addr); - u32 prevBegin = g_symbolMap->GetFunctionStart(addr); - u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 4); - if (prevBegin == addr) { - return req.Fail("Function already exists at 'address'"); - } else if (endBegin != prevBegin) { - return req.Fail("Function already exists between 'address' and 'address' + 'size'"); - } else if (prevBegin != -1) { - std::string prevName = g_symbolMap->GetLabelString(prevBegin); - u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin); - u32 newPrevSize = addr - prevBegin; + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 prevBegin = g_symbolMap->GetFunctionStart(addr); + u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 4); + if (prevBegin == addr) { + req.Fail("Function already exists at 'address'"); + return; + } else if (endBegin != prevBegin) { + req.Fail("Function already exists between 'address' and 'address' + 'size'"); + return; + } else if (prevBegin != -1) { + std::string prevName = g_symbolMap->GetLabelString(prevBegin); + u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin); + u32 newPrevSize = addr - prevBegin; - // The new function will be the remainder, unless otherwise specified. - if (size == -1) - size = prevSize - newPrevSize; + // The new function will be the remainder, unless otherwise specified. + if (size == -1) + size = prevSize - newPrevSize; - // Make sure we register the new length for replacements too. - MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize); - g_symbolMap->SetFunctionSize(prevBegin, newPrevSize); - MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str()); - } else { - // There was no function there, so hopefully they specified a size. - if (size == -1) - size = 4; - } + // Make sure we register the new length for replacements too. + MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize); + g_symbolMap->SetFunctionSize(prevBegin, newPrevSize); + MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str()); + } else { + // There was no function there, so hopefully they specified a size. + if (size == -1) + size = 4; + } - // To ensure we restore replacements. - MIPSAnalyst::ForgetFunctions(addr, addr + size); - g_symbolMap->AddFunction(name.c_str(), addr, size); - g_symbolMap->SortSymbols(); - MIPSAnalyst::RegisterFunction(addr, size, name.c_str()); + // To ensure we restore replacements. + MIPSAnalyst::ForgetFunctions(addr, addr + size); + g_symbolMap->AddFunction(name.c_str(), addr, size); + g_symbolMap->SortSymbols(); + MIPSAnalyst::RegisterFunction(addr, size, name.c_str()); - MIPSAnalyst::UpdateHashMap(); - MIPSAnalyst::ApplyHashMap(); + MIPSAnalyst::UpdateHashMap(); + MIPSAnalyst::ApplyHashMap(); - if (g_Config.bFuncReplacements) { - MIPSAnalyst::ReplaceFunctions(); - } + if (g_Config.bFuncReplacements) { + MIPSAnalyst::ReplaceFunctions(); + } - // Clear cache for branch lines and such. - g_disassemblyManager.clear(); + // Clear cache for branch lines and such. + g_disassemblyManager.clear(); - JsonWriter &json = req.Respond(); - json.writeUint("address", addr); - json.writeUint("size", size); - json.writeString("name", name); + JsonWriter &json = req.Respond(); + json.writeUint("address", addr); + json.writeUint("size", size); + json.writeString("name", name); + }); } // Remove a function symbol (hle.func.remove) @@ -366,39 +394,45 @@ void WebSocketHLEFuncRemove(DebuggerRequest &req) { addr = RoundDownToMultipleOf(addr, 4); - u32 funcBegin = g_symbolMap->GetFunctionStart(addr); - if (funcBegin == -1) - return req.Fail("No function found at 'address'"); - u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin); + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 funcBegin = g_symbolMap->GetFunctionStart(addr); + if (funcBegin == -1) { + req.Fail("No function found at 'address'"); + return; + } + u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin); - // Expand the previous function. - u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1); - if (prevBegin != -1) { - std::string prevName = g_symbolMap->GetLabelString(prevBegin); - u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize; - g_symbolMap->SetFunctionSize(prevBegin, expandedSize); - MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize); - MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str()); - } else { - MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize); - } + // Expand the previous function. + u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1); + if (prevBegin != -1) { + std::string prevName = g_symbolMap->GetLabelString(prevBegin); + u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize; + g_symbolMap->SetFunctionSize(prevBegin, expandedSize); + MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize); + MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str()); + } else { + MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize); + } - g_symbolMap->RemoveFunction(funcBegin, true); - g_symbolMap->SortSymbols(); + g_symbolMap->RemoveFunction(funcBegin, true); + g_symbolMap->SortSymbols(); - MIPSAnalyst::UpdateHashMap(); - MIPSAnalyst::ApplyHashMap(); + MIPSAnalyst::UpdateHashMap(); + MIPSAnalyst::ApplyHashMap(); - if (g_Config.bFuncReplacements) { - MIPSAnalyst::ReplaceFunctions(); - } + if (g_Config.bFuncReplacements) { + MIPSAnalyst::ReplaceFunctions(); + } - // Clear cache for branch lines and such. - g_disassemblyManager.clear(); + // Clear cache for branch lines and such. + g_disassemblyManager.clear(); - JsonWriter &json = req.Respond(); - json.writeUint("address", funcBegin); - json.writeUint("size", funcSize); + JsonWriter &json = req.Respond(); + json.writeUint("address", funcBegin); + json.writeUint("size", funcSize); + }); } // This function removes function symbols that intersect or lie inside the range @@ -461,13 +495,19 @@ void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) { addr = RoundDownToMultipleOf(addr, 4); size = RoundUpToMultipleOf(size, 4); + // This only depends on addr/size, not on anything CPU-thread-owned, so fail fast here rather than + // making a round trip through the queue for a request we already know is invalid. if (!Memory::IsValidRange(addr, size)) return req.Fail("Address or size outside valid memory"); - u32 count = RemoveFuncSymbolsInRange(addr, size); + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 count = RemoveFuncSymbolsInRange(addr, size); - JsonWriter &json = req.Respond(); - json.writeUint("count", count); + JsonWriter &json = req.Respond(); + json.writeUint("count", count); + }); } // Rename a function symbol (hle.func.rename) @@ -495,25 +535,31 @@ void WebSocketHLEFuncRename(DebuggerRequest &req) { addr = RoundDownToMultipleOf(addr, 4); - u32 funcBegin = g_symbolMap->GetFunctionStart(addr); - if (funcBegin == -1) - return req.Fail("No function found at 'address'"); - u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin); + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 funcBegin = g_symbolMap->GetFunctionStart(addr); + if (funcBegin == -1) { + req.Fail("No function found at 'address'"); + return; + } + u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin); - g_symbolMap->SetLabelName(name.c_str(), funcBegin); - // To ensure we reapply replacements (in case we check name there.) - MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize); - MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str()); - MIPSAnalyst::UpdateHashMap(); - MIPSAnalyst::ApplyHashMap(); - if (g_Config.bFuncReplacements) { - MIPSAnalyst::ReplaceFunctions(); - } + g_symbolMap->SetLabelName(name.c_str(), funcBegin); + // To ensure we reapply replacements (in case we check name there.) + MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize); + MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str()); + MIPSAnalyst::UpdateHashMap(); + MIPSAnalyst::ApplyHashMap(); + if (g_Config.bFuncReplacements) { + MIPSAnalyst::ReplaceFunctions(); + } - JsonWriter &json = req.Respond(); - json.writeUint("address", funcBegin); - json.writeUint("size", funcSize); - json.writeString("name", name); + JsonWriter &json = req.Respond(); + json.writeUint("address", funcBegin); + json.writeUint("size", funcSize); + json.writeString("name", name); + }); } // Auto-detect functions in a memory range (hle.func.scan) @@ -546,17 +592,26 @@ void WebSocketHLEFuncScan(DebuggerRequest &req) { if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL)) return; + // This only depends on addr/size, not on anything CPU-thread-owned, so fail fast here rather than + // making a round trip through the queue for a request we already know is invalid. if (!Memory::IsValidRange(addr, size)) return req.Fail("Address or size outside valid memory"); - if (remove) { - RemoveFuncSymbolsInRange(addr, size); - } + // Route the actual symbol scan/manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + // Note: like memory.search, 'size' has no cap beyond valid memory range, so a very large scan + // will block the CPU thread's own frame pump for its duration. + Core_RunOnCPUThread([&] { - bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true); - MIPSAnalyst::FinalizeScan(insertSymbols); + if (remove) { + RemoveFuncSymbolsInRange(addr, size); + } - req.Respond(); + bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true); + MIPSAnalyst::FinalizeScan(insertSymbols); + + req.Respond(); + }); } // List all known user modules (hle.module.list) @@ -573,19 +628,23 @@ void WebSocketHLEModuleList(DebuggerRequest &req) { if (!g_symbolMap) return req.Fail("CPU not active"); - auto modules = g_symbolMap->getAllModules(); + // Route the actual 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([&] { + std::vector modules = g_symbolMap->getAllModules(); - JsonWriter &json = req.Respond(); - json.pushArray("modules"); - for (auto m : modules) { - json.pushDict(); - json.writeString("name", m.name); - json.writeUint("address", m.address); - json.writeUint("size", m.size); - json.writeBool("isActive", m.active); + JsonWriter &json = req.Respond(); + json.pushArray("modules"); + for (const LoadedModuleInfo &m : modules) { + json.pushDict(); + json.writeString("name", m.name); + json.writeUint("address", m.address); + json.writeUint("size", m.size); + json.writeBool("isActive", m.active); + json.pop(); + } json.pop(); - } - json.pop(); + }); } // Walk the stack and list stack frames (hle.backtrace) @@ -606,48 +665,54 @@ void WebSocketHLEBacktrace(DebuggerRequest &req) { if (!Core_IsStepping()) return req.Fail("CPU currently running (cpu.stepping first)"); - uint32_t threadID = -1; - DebugInterface *cpuDebug = currentDebugMIPS; - if (req.HasParam("thread")) { - if (!req.ParamU32("thread", &threadID)) - return; + // Route the actual CPU/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([&] { + uint32_t threadID = -1; + DebugInterface *cpuDebug = currentDebugMIPS; + if (req.HasParam("thread")) { + if (!req.ParamU32("thread", &threadID)) + return; - cpuDebug = KernelDebugThread((SceUID)threadID); - if (!cpuDebug) - return req.Fail("Thread could not be found"); - } - - auto threads = GetThreadsInfo(); - uint32_t entry = cpuDebug->GetPC(); - uint32_t stackTop = 0; - for (const DebugThreadInfo &th : threads) { - if ((threadID == -1 && th.isCurrent) || th.id == threadID) { - entry = th.entrypoint; - stackTop = th.initialStack; - break; + cpuDebug = KernelDebugThread((SceUID)threadID); + if (!cpuDebug) { + req.Fail("Thread could not be found"); + return; + } } - } - uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); - uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); - auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop); + std::vector threads = GetThreadsInfo(); + uint32_t entry = cpuDebug->GetPC(); + uint32_t stackTop = 0; + for (const DebugThreadInfo &th : threads) { + if ((threadID == -1 && th.isCurrent) || th.id == threadID) { + entry = th.entrypoint; + stackTop = th.initialStack; + break; + } + } - JsonWriter &json = req.Respond(); - json.pushArray("frames"); - for (auto f : frames) { - json.pushDict(); - json.writeUint("entry", f.entry); - json.writeUint("pc", f.pc); - json.writeUint("sp", f.sp); - json.writeUint("stackSize", f.stackSize); + uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); + uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); + std::vector frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop); - DisassemblyLineInfo line; - g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug); - json.writeString("code", line.name + " " + line.params); + JsonWriter &json = req.Respond(); + json.pushArray("frames"); + for (const MIPSStackWalk::StackFrame &f : frames) { + json.pushDict(); + json.writeUint("entry", f.entry); + json.writeUint("pc", f.pc); + json.writeUint("sp", f.sp); + json.writeUint("stackSize", f.stackSize); + DisassemblyLineInfo line; + g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug); + json.writeString("code", line.name + " " + line.params); + + json.pop(); + } json.pop(); - } - json.pop(); + }); } // List all current known data symbols (hle.data.list) @@ -664,19 +729,23 @@ void WebSocketHLEDataList(DebuggerRequest &req) { if (!g_symbolMap) return req.Fail("CPU not active"); - auto entries = g_symbolMap->GetAllActiveSymbols(ST_DATA); + // Route the actual 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([&] { + std::vector entries = g_symbolMap->GetAllActiveSymbols(ST_DATA); - JsonWriter &json = req.Respond(); - json.pushArray("data"); - for (auto &d : entries) { - json.pushDict(); - json.writeString("name", d.name); - json.writeUint("address", d.address); - json.writeUint("size", d.size); - json.writeString("type", DataTypeToString(g_symbolMap->GetDataType(d.address))); + JsonWriter &json = req.Respond(); + json.pushArray("data"); + for (const SymbolEntry &d : entries) { + json.pushDict(); + json.writeString("name", d.name); + json.writeUint("address", d.address); + json.writeUint("size", d.size); + json.writeString("type", DataTypeToString(g_symbolMap->GetDataType(d.address))); + json.pop(); + } json.pop(); - } - json.pop(); + }); } // Add a new data symbol (hle.data.add) @@ -717,6 +786,8 @@ void WebSocketHLEDataAdd(DebuggerRequest &req) { if (!DataTypeFromString(typeStr, &type)) return req.Fail("Invalid 'type', must be byte, halfword, word, or ascii"); + // This only depends on addr/size, not on anything CPU-thread-owned, so fail fast here rather than + // making a round trip through the queue for a request we already know is invalid. if (!Memory::IsValidRange(addr, size)) return req.Fail("Address or size outside valid memory"); @@ -726,18 +797,22 @@ void WebSocketHLEDataAdd(DebuggerRequest &req) { if (name.empty()) name = StringFromFormat("data_%08x", addr); - g_symbolMap->AddData(addr, size, type); - g_symbolMap->AddLabel(name.c_str(), addr); - g_symbolMap->SortSymbols(); + // Route the actual symbol 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_symbolMap->AddData(addr, size, type); + g_symbolMap->AddLabel(name.c_str(), addr); + g_symbolMap->SortSymbols(); - // Clear cache so the disassembly view picks up the new annotation. - g_disassemblyManager.clear(); + // Clear cache so the disassembly view picks up the new annotation. + g_disassemblyManager.clear(); - JsonWriter &json = req.Respond(); - json.writeUint("address", addr); - json.writeUint("size", size); - json.writeString("type", typeStr); - json.writeString("name", name); + JsonWriter &json = req.Respond(); + json.writeUint("address", addr); + json.writeUint("size", size); + json.writeString("type", typeStr); + json.writeString("name", name); + }); } // Remove a data symbol (hle.data.remove) @@ -758,18 +833,24 @@ void WebSocketHLEDataRemove(DebuggerRequest &req) { if (!req.ParamU32("address", &addr)) return; - u32 dataBegin = g_symbolMap->GetDataStart(addr); - if (dataBegin == -1) - return req.Fail("No data symbol found at 'address'"); - u32 dataSize = g_symbolMap->GetDataSize(dataBegin); + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 dataBegin = g_symbolMap->GetDataStart(addr); + if (dataBegin == -1) { + req.Fail("No data symbol found at 'address'"); + return; + } + u32 dataSize = g_symbolMap->GetDataSize(dataBegin); - g_symbolMap->RemoveData(dataBegin, true); - g_symbolMap->SortSymbols(); - g_disassemblyManager.clear(); + g_symbolMap->RemoveData(dataBegin, true); + g_symbolMap->SortSymbols(); + g_disassemblyManager.clear(); - JsonWriter &json = req.Respond(); - json.writeUint("address", dataBegin); - json.writeUint("size", dataSize); + JsonWriter &json = req.Respond(); + json.writeUint("address", dataBegin); + json.writeUint("size", dataSize); + }); } // Rename a data symbol (hle.data.rename) @@ -795,15 +876,21 @@ void WebSocketHLEDataRename(DebuggerRequest &req) { if (!req.ParamString("name", &name)) return; - u32 dataBegin = g_symbolMap->GetDataStart(addr); - if (dataBegin == -1) - return req.Fail("No data symbol found at 'address'"); - u32 dataSize = g_symbolMap->GetDataSize(dataBegin); + // Route the actual symbol manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + u32 dataBegin = g_symbolMap->GetDataStart(addr); + if (dataBegin == -1) { + req.Fail("No data symbol found at 'address'"); + return; + } + u32 dataSize = g_symbolMap->GetDataSize(dataBegin); - g_symbolMap->SetLabelName(name.c_str(), dataBegin); + g_symbolMap->SetLabelName(name.c_str(), dataBegin); - JsonWriter &json = req.Respond(); - json.writeUint("address", dataBegin); - json.writeUint("size", dataSize); - json.writeString("name", name); + JsonWriter &json = req.Respond(); + json.writeUint("address", dataBegin); + json.writeUint("size", dataSize); + json.writeString("name", name); + }); }