diff --git a/Core/Core.cpp b/Core/Core.cpp index e8c0000a01..337185e003 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -17,9 +17,13 @@ #include "ppsspp_config.h" +#include #include #include +#include #include +#include +#include #include #include "Common/System/System.h" @@ -67,6 +71,64 @@ struct CPUStepCommand { static CPUStepCommand g_cpuStepCommand; +// Task queue for Core_RunOnCPUThread(), see Core.h for the rationale. Drained from Core_RunLoopUntil() +// below, so at least once per call to it (i.e. about once per host frame) even while the CPU is fully +// running, and continuously (in a tight spin) while it's stepping/paused. +struct CPUThreadTask { + std::function func; + bool done = false; +}; +static std::mutex g_cpuQueueMutex; +static std::condition_variable g_cpuQueueCond; +static std::vector> g_cpuQueue; +static std::once_flag g_cpuThreadIdOnce; +static std::thread::id g_cpuThreadId; +// Published via release/acquire around g_cpuThreadIdOnce, so it's safe to check from other threads +// without taking g_cpuQueueMutex - g_cpuThreadId itself never changes once this becomes true. +static std::atomic g_cpuThreadIdValid{ false }; + +void Core_RunOnCPUThread(std::function func) { + if (g_cpuThreadIdValid.load(std::memory_order_acquire) && std::this_thread::get_id() == g_cpuThreadId) { + // Already on the CPU thread (or called before it's ever run) - just do it now, avoids deadlock. + func(); + return; + } + + auto task = std::make_shared(); + task->func = std::move(func); + + std::unique_lock guard(g_cpuQueueMutex); + g_cpuQueue.push_back(task); + g_cpuQueueCond.wait(guard, [&] { return task->done; }); +} + +// Called from the CPU thread only. +static void Core_ProcessCPUQueue() { + std::call_once(g_cpuThreadIdOnce, [] { + g_cpuThreadId = std::this_thread::get_id(); + g_cpuThreadIdValid.store(true, std::memory_order_release); + }); + + std::vector> tasks; + { + std::lock_guard guard(g_cpuQueueMutex); + if (g_cpuQueue.empty()) + return; + tasks = std::move(g_cpuQueue); + g_cpuQueue.clear(); + } + + for (auto &task : tasks) + task->func(); + + { + std::lock_guard guard(g_cpuQueueMutex); + for (auto &task : tasks) + task->done = true; + } + g_cpuQueueCond.notify_all(); +} + // This is so that external threads can wait for the CPU to become inactive. static std::condition_variable m_InactiveCond; static std::mutex m_hInactiveMutex; @@ -206,6 +268,11 @@ bool Core_GetPowerSaving() { void Core_RunLoopUntil(u64 globalticks) { while (true) { + // Drain any functions queued up by Core_RunOnCPUThread() from other threads. Doing this at the + // top of this loop means it's reached at least once per call (i.e. about once per host frame) + // even while the CPU is fully running, and continuously (in a tight spin) while it's stepping/paused. + Core_ProcessCPUQueue(); + switch (coreState) { case CORE_POWERDOWN: case CORE_RUNTIME_ERROR: @@ -274,8 +341,7 @@ bool Core_RequestCPUStep(CPUStepType type, int stepSize) { } // Handles more advanced step types (used by the debugger). -// stepSize is to support stepping through compound instructions like fused lui+ladd (li). -// Yes, our disassembler does support those. +// stepSize is always in instructions (4 bytes each), never bytes. // Doesn't return the new address, as that's just mips->getPC(). // Internal use. static void Core_PerformCPUStep(MIPSDebugInterface *cpu, CPUStepType stepType, int stepSize) { @@ -283,10 +349,9 @@ static void Core_PerformCPUStep(MIPSDebugInterface *cpu, CPUStepType stepType, i case CPUStepType::Into: { u32 currentPc = cpu->GetPC(); - u32 newAddress = currentPc + stepSize; // If the current PC is on a breakpoint, the user still wants the step to happen. g_breakpoints.SetSkipFirst(currentPc); - for (int i = 0; i < (int)(newAddress - currentPc) / 4; i++) { + for (int i = 0; i < stepSize; i++) { currentMIPS->SingleStep(); } break; @@ -294,7 +359,7 @@ static void Core_PerformCPUStep(MIPSDebugInterface *cpu, CPUStepType stepType, i case CPUStepType::Over: { u32 currentPc = cpu->GetPC(); - u32 breakpointAddress = currentPc + stepSize; + u32 breakpointAddress = currentPc + stepSize * 4; g_breakpoints.SetSkipFirst(currentPc); MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(cpu, cpu->GetPC()); diff --git a/Core/Core.h b/Core/Core.h index 1272e1ce2d..493701c127 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include @@ -84,6 +85,7 @@ BreakReason Core_BreakReason(); // This should be called externally. // Can fail if another step type was requested this frame. +// stepSize is always in instructions (4 bytes each), never bytes - see Core_PerformCPUStep in Core.cpp. bool Core_RequestCPUStep(CPUStepType stepType, int stepSize); bool Core_NextFrame(); @@ -148,6 +150,20 @@ bool Core_GetPowerSaving(); void Core_RunLoopUntil(u64 globalticks); +// Runs a function on the CPU thread - the thread that calls Core_RunLoopUntil (and thus, indirectly, +// NativeFrame). Useful for code running on unrelated threads (like the WebSocket debugger) that needs to +// safely touch state that's otherwise only ever touched from that thread (breakpoints, stepping, etc.), +// instead of poking at it directly from wherever the call happens to come from. +// +// Safe to call from any thread, including the CPU thread itself (in which case func just runs immediately). +// Blocks the calling thread until func has actually run, so don't call this from the CPU thread with +// something that would itself try to wait on the CPU thread - that'll deadlock. +// +// Drained at the top of every Core_RunLoopUntil() iteration, so it's reached continuously (in a tight +// spin) while the CPU is stepping/paused, and at least once per call (i.e. about once per host frame) +// even while it's fully running. +void Core_RunOnCPUThread(std::function func); + extern volatile CoreState coreState; extern volatile bool coreStatePending; diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 39e03307ab..39266230fc 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -595,26 +595,6 @@ void DisassemblyFunction::load() { generateBranchLines(); - // gather all branch targets - std::set branchTargets; - { - std::lock_guard guard(lock_); - for (size_t i = 0; i < lines.size(); i++) - { - switch (lines[i].type) - { - case LINE_DOWN: - branchTargets.insert(lines[i].second); - break; - case LINE_UP: - branchTargets.insert(lines[i].first); - break; - default: - break; - } - } - } - DebugInterface *cpu = g_disassemblyManager.getCpu(); u32 funcPos = address; u32 funcEnd = address+size; @@ -655,7 +635,6 @@ void DisassemblyFunction::load() } MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos); - u32 opAddress = funcPos; funcPos += 4; // skip branches and their delay slots @@ -665,70 +644,6 @@ void DisassemblyFunction::load() continue; } - // lui - if (MIPS_GET_OP(opInfo.encodedOpcode) == 0x0F && funcPos < funcEnd && funcPos != nextData) - { - MIPSOpcode next = Memory::Read_Instruction(funcPos); - MIPSInfo nextInfo = MIPSGetInfo(next); - - u32 immediate = ((opInfo.encodedOpcode & 0xFFFF) << 16) + (s16)(next.encoding & 0xFFFF); - int rt = MIPS_GET_RT(opInfo.encodedOpcode); - - int nextRs = MIPS_GET_RS(next.encoding); - int nextRt = MIPS_GET_RT(next.encoding); - - // both rs and rt of the second op have to match rt of the first, - // otherwise there may be hidden consequences if the macro is displayed. - // also, don't create a macro if something branches into the middle of it - if (nextRs == rt && nextRt == rt && branchTargets.find(funcPos) == branchTargets.end()) - { - DisassemblyMacro* macro = NULL; - switch (MIPS_GET_OP(next.encoding)) - { - case 0x09: // addiu - macro = new DisassemblyMacro(opAddress); - macro->setMacroLi(immediate,rt); - funcPos += 4; - break; - case 0x20: // lb - case 0x21: // lh - case 0x23: // lw - case 0x24: // lbu - case 0x25: // lhu - case 0x28: // sb - case 0x29: // sh - case 0x2B: // sw - macro = new DisassemblyMacro(opAddress); - - int dataSize = MIPSGetMemoryAccessSize(next); - if (dataSize == 0) { - delete macro; - return; - } - - macro->setMacroMemory(MIPSGetName(next),immediate,rt,dataSize); - funcPos += 4; - break; - } - - if (macro != NULL) - { - if (opcodeSequenceStart != opAddress) - addOpcodeSequence(opcodeSequenceStart,opAddress); - - std::lock_guard guard(lock_); - entries[opAddress] = macro; - for (int i = 0; i < macro->getNumLines(); i++) - { - lineAddresses.push_back(macro->getLineAddress(i)); - } - - opcodeSequenceStart = funcPos; - continue; - } - } - } - // just a normal opcode } @@ -801,76 +716,6 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vectorGetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) { - snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str()); - } else { - snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate); - } - - dest.params = buffer; - - dest.info.hasRelevantAddress = true; - dest.info.relevantAddress = immediate; - break; - case MACRO_MEMORYIMM: - dest.name = name; - - addressSymbol = g_symbolMap->GetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) { - snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str()); - } else { - snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate); - } - - dest.params = buffer; - - dest.info.isDataAccess = true; - dest.info.dataAddress = immediate; - dest.info.dataSize = dataSize; - - dest.info.hasRelevantAddress = true; - dest.info.relevantAddress = immediate; - break; - default: - return false; - } - - dest.totalSize = getTotalSize(); - return true; -} - DisassemblyData::DisassemblyData(u32 _address, u32 _size, DataType _type): address(_address), size(_size), type(_type) { _dbg_assert_(PSP_GetBootState() == BootState::Complete); diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index ecfde48f81..7c8fc8dc17 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -32,7 +32,7 @@ typedef u64 HashType; typedef u32 HashType; #endif -enum DisassemblyLineType { DISTYPE_OPCODE, DISTYPE_MACRO, DISTYPE_DATA, DISTYPE_OTHER }; +enum DisassemblyLineType { DISTYPE_OPCODE, DISTYPE_DATA, DISTYPE_OTHER }; struct DisassemblyLineInfo { @@ -117,33 +117,6 @@ private: }; -class DisassemblyMacro: public DisassemblyEntry -{ -public: - DisassemblyMacro(u32 _address): address(_address) { } - - void setMacroLi(u32 _immediate, u8 _rt); - void setMacroMemory(std::string_view _name, u32 _immediate, u8 _rt, int _dataSize); - - void recheck() override { }; - int getNumLines() override { return 1; }; - int getLineNum(u32 address, bool findStart) override { return 0; }; - u32 getLineAddress(int line) override { return address; }; - u32 getTotalSize() override { return numOpcodes * 4; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; -private: - enum MacroType { MACRO_LI, MACRO_MEMORYIMM }; - - MacroType type; - std::string name; - u32 immediate; - u32 address; - u32 numOpcodes; - u8 rt; - int dataSize; -}; - - class DisassemblyData: public DisassemblyEntry { public: diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 6e12abe5db..c3778c20a2 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -128,11 +128,13 @@ static void SetupDebuggerLock() { } void HandleDebuggerRequest(const http::ServerRequest &request) { - net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); - if (!ws) - return; - SetCurrentThreadName("WebSocketDebugger"); + + net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); + if (!ws) { + return; + } + UpdateConnected(1); SetupDebuggerLock(); @@ -188,7 +190,10 @@ void HandleDebuggerRequest(const http::ServerRequest &request) { ws->Send(DebuggerErrorEvent("Bad message: binary WebSocket frames are not supported", LogLevel::LERROR)); }); - while (ws->Process(highActivity ? 1.0f / 1000.0f : 1.0f / 60.0f)) { + // Don't out-line the highActivity check, it needs to recompute on every lap. + constexpr float lowActivityPollTimeStep = 1.0f / 60.0f; + constexpr float highActivityPollTimeStep = 1.0f / 1000.0f; + while (ws->Process(highActivity ? highActivityPollTimeStep : lowActivityPollTimeStep)) { std::lock_guard guard(lifecycleLock); // These send events that aren't just responses to requests @@ -212,6 +217,7 @@ void HandleDebuggerRequest(const http::ServerRequest &request) { if (stopRequested) { ws->Close(net::WebSocketClose::GOING_AWAY); } + if (highActivity > 0) { highActivity--; } diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp index 7eee0695bc..9f76c2306b 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -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 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 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(); + }); } diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 57d79459b3..708a2458a0 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -72,6 +72,9 @@ void WebSocketCPUStepping(DebuggerRequest &req) { return req.Fail("CPU not started"); } if (!Core_IsStepping() && Core_IsActive()) { + // Core_Break() is explicitly free-threaded (see Core.cpp), so no need to bounce this to the CPU + // thread - and we can't anyway, since queuing to it only makes sense once the CPU actually *is* + // stepping, which this call is what triggers in the first place. Core_Break(BreakReason::DebugStep, 0); } } @@ -89,11 +92,15 @@ void WebSocketCPUResume(DebuggerRequest &req) { return req.Fail("CPU not stepping"); } - g_breakpoints.SetSkipFirst(currentMIPS->pc); - if (currentMIPS->inDelaySlot) { - Core_RequestCPUStep(CPUStepType::Into, 1); - } - Core_Resume(); + // Route the actual breakpoint/stepping 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.SetSkipFirst(currentMIPS->pc); + if (currentMIPS->inDelaySlot) { + Core_RequestCPUStep(CPUStepType::Into, 1); + } + Core_Resume(); + }); } // Request the current CPU status (cpu.status) @@ -105,6 +112,10 @@ void WebSocketCPUResume(DebuggerRequest &req) { // - paused: boolean, CPU paused or not started yet. // - pc: number value of PC register (inaccurate unless stepping.) // - ticks: number of CPU cycles into emulation. +// Deliberately not routed through Core_RunOnCPUThread(): this is meant to be a cheap, frequently-pollable +// status check, and the "pc" field is already documented as inaccurate unless stepping - queuing it would +// add up to a frame of latency to every poll for no real accuracy benefit. Matches how SteppingBroadcaster +// already reads this same state directly from the WebSocket thread. void WebSocketCPUStatus(DebuggerRequest &req) { JsonWriter &json = req.Respond(); @@ -131,55 +142,59 @@ void WebSocketCPUStatus(DebuggerRequest &req) { // - uintValues: array of unsigned integer values for the registers. // - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetAllRegs(DebuggerRequest &req) { - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; + // Route the actual register reads to the CPU thread instead of poking at them directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - JsonWriter &json = req.Respond(); + JsonWriter &json = req.Respond(); - json.pushArray("categories"); - for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) { - json.pushDict(); - json.writeInt("id", c); - json.writeString("name", MIPSDebugInterface::GetCategoryName(c)); + json.pushArray("categories"); + for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) { + json.pushDict(); + json.writeInt("id", c); + json.writeString("name", MIPSDebugInterface::GetCategoryName(c)); - int total = MIPSDebugInterface::GetNumRegsInCategory(c); + int total = MIPSDebugInterface::GetNumRegsInCategory(c); - json.pushArray("registerNames"); - for (int r = 0; r < total; ++r) - json.writeString(MIPSDebugInterface::GetRegName(c, r)); - if (c == 0) { - json.writeString("pc"); - json.writeString("hi"); - json.writeString("lo"); + json.pushArray("registerNames"); + for (int r = 0; r < total; ++r) + json.writeString(MIPSDebugInterface::GetRegName(c, r)); + if (c == 0) { + json.writeString("pc"); + json.writeString("hi"); + json.writeString("lo"); + } + json.pop(); + + json.pushArray("uintValues"); + // Writing as floating point to avoid negatives. Actually double, so safe. + for (int r = 0; r < total; ++r) + json.writeUint(cpuDebug->GetRegValue(c, r)); + if (c == 0) { + json.writeUint(cpuDebug->GetPC()); + json.writeUint(cpuDebug->GetHi()); + json.writeUint(cpuDebug->GetLo()); + } + json.pop(); + + json.pushArray("floatValues"); + // Note: String so it can have Infinity and NaN. + for (int r = 0; r < total; ++r) + json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r))); + if (c == 0) { + json.writeString(RegValueAsFloat(cpuDebug->GetPC())); + json.writeString(RegValueAsFloat(cpuDebug->GetHi())); + json.writeString(RegValueAsFloat(cpuDebug->GetLo())); + } + json.pop(); + + json.pop(); } json.pop(); - - json.pushArray("uintValues"); - // Writing as floating point to avoid negatives. Actually double, so safe. - for (int r = 0; r < total; ++r) - json.writeUint(cpuDebug->GetRegValue(c, r)); - if (c == 0) { - json.writeUint(cpuDebug->GetPC()); - json.writeUint(cpuDebug->GetHi()); - json.writeUint(cpuDebug->GetLo()); - } - json.pop(); - - json.pushArray("floatValues"); - // Note: String so it can have Infinity and NaN. - for (int r = 0; r < total; ++r) - json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r))); - if (c == 0) { - json.writeString(RegValueAsFloat(cpuDebug->GetPC())); - json.writeString(RegValueAsFloat(cpuDebug->GetHi())); - json.writeString(RegValueAsFloat(cpuDebug->GetLo())); - } - json.pop(); - - json.pop(); - } - json.pop(); + }); } enum class DebuggerRegType { @@ -271,37 +286,41 @@ static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) // - uintValue: value in register. // - floatValue: string showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetReg(DebuggerRequest &req) { - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; + // Route the actual register read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - int cat, reg; - uint32_t val = 0; - switch (ValidateCatReg(req, &cat, ®)) { - case DebuggerRegType::NORMAL: - val = cpuDebug->GetRegValue(cat, reg); - break; + int cat, reg; + uint32_t val = 0; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + val = cpuDebug->GetRegValue(cat, reg); + break; - case DebuggerRegType::PC: - val = cpuDebug->GetPC(); - break; - case DebuggerRegType::HI: - val = cpuDebug->GetHi(); - break; - case DebuggerRegType::LO: - val = cpuDebug->GetLo(); - break; + case DebuggerRegType::PC: + val = cpuDebug->GetPC(); + break; + case DebuggerRegType::HI: + val = cpuDebug->GetHi(); + break; + case DebuggerRegType::LO: + val = cpuDebug->GetLo(); + break; - case DebuggerRegType::INVALID: - // Error response already sent. - return; - } + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } - JsonWriter &json = req.Respond(); - json.writeInt("category", cat); - json.writeInt("register", reg); - json.writeUint("uintValue", val); - json.writeString("floatValue", RegValueAsFloat(val)); + JsonWriter &json = req.Respond(); + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); + }); } // Update the value of a single register (cpu.setReg) @@ -334,50 +353,55 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { return req.Fail("CPU currently running (cpu.stepping first)"); } - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; + // Route the actual register write to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - uint32_t val; - if (!req.ParamU32("value", &val, true)) { - // Already sent error. - return; - } - - int cat, reg; - switch (ValidateCatReg(req, &cat, ®)) { - case DebuggerRegType::NORMAL: - if (cat == 0 && reg == 0 && val != 0) { - return req.Fail("Cannot change reg zero"); + uint32_t val; + if (!req.ParamU32("value", &val, true)) { + // Already sent error. + return; } - cpuDebug->SetRegValue(cat, reg, val); - // In case part of it was ignored (e.g. flags reg.) - val = cpuDebug->GetRegValue(cat, reg); - break; - case DebuggerRegType::PC: - cpuDebug->SetPC(val); - break; - case DebuggerRegType::HI: - cpuDebug->SetHi(val); - break; - case DebuggerRegType::LO: - cpuDebug->SetLo(val); - break; + int cat, reg; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + if (cat == 0 && reg == 0 && val != 0) { + req.Fail("Cannot change reg zero"); + return; + } + cpuDebug->SetRegValue(cat, reg, val); + // In case part of it was ignored (e.g. flags reg.) + val = cpuDebug->GetRegValue(cat, reg); + break; - case DebuggerRegType::INVALID: - // Error response already sent. - return; - } + case DebuggerRegType::PC: + cpuDebug->SetPC(val); + break; + case DebuggerRegType::HI: + cpuDebug->SetHi(val); + break; + case DebuggerRegType::LO: + cpuDebug->SetLo(val); + break; - Reporting::NotifyDebugger(); + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } - JsonWriter &json = req.Respond(); - // Repeat it back just to avoid confusion on how it parsed. - json.writeInt("category", cat); - json.writeInt("register", reg); - json.writeUint("uintValue", val); - json.writeString("floatValue", RegValueAsFloat(val)); + Reporting::NotifyDebugger(); + + JsonWriter &json = req.Respond(); + // Repeat it back just to avoid confusion on how it parsed. + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); + }); } // Evaluate an expression (cpu.evaluate) @@ -394,26 +418,32 @@ void WebSocketCPUEvaluate(DebuggerRequest &req) { return req.Fail("CPU not started"); } - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; + // Route the actual register/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([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - std::string exp; - if (!req.ParamString("expression", &exp)) { - // Already sent error. - return; - } + std::string exp; + if (!req.ParamString("expression", &exp)) { + // Already sent error. + return; + } - u32 val; - PostfixExpression postfix; - if (!initExpression(cpuDebug, exp.c_str(), postfix)) { - return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); - } - if (!parseExpression(cpuDebug, postfix, val)) { - return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); - } + u32 val; + PostfixExpression postfix; + if (!initExpression(cpuDebug, exp.c_str(), postfix)) { + req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + return; + } + if (!parseExpression(cpuDebug, postfix, val)) { + req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); + return; + } - JsonWriter &json = req.Respond(); - json.writeUint("uintValue", val); - json.writeString("floatValue", RegValueAsFloat(val)); + JsonWriter &json = req.Respond(); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); + }); } diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 2758183b8a..356cbab6ed 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -21,6 +21,7 @@ #include "Common/Data/Encoding/Utf8.h" #include "Common/StringUtils.h" +#include "Core/Core.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/DisassemblyManager.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" @@ -51,7 +52,7 @@ protected: }; DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { - auto p = new WebSocketDisasmState(); + WebSocketDisasmState *p = new WebSocketDisasmState(); map["memory.base"] = [p](DebuggerRequest &req) { p->Base(req); }; map["memory.disasm"] = [p](DebuggerRequest &req) { p->Disasm(req); }; map["memory.searchDisasm"] = [p](DebuggerRequest &req) { p->SearchDisasm(req); }; @@ -78,8 +79,6 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.pushDict(); if (l.type == DISTYPE_OPCODE) json.writeString("type", "opcode"); - else if (l.type == DISTYPE_MACRO) - json.writeString("type", "macro"); else if (l.type == DISTYPE_DATA) json.writeString("type", "data"); else if (l.type == DISTYPE_OTHER) @@ -145,7 +144,7 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.pushDict("breakpoint"); json.writeBool("enabled", enabled); json.writeUint("address", addr + breakpointOffset); - auto cond = g_breakpoints.GetBreakPointCondition(addr + breakpointOffset); + BreakPointCond *cond = g_breakpoints.GetBreakPointCondition(addr + breakpointOffset); if (cond) json.writeString("condition", cond->expressionString); else @@ -297,83 +296,88 @@ void WebSocketDisasmState::Base(DebuggerRequest &req) { void WebSocketDisasmState::Disasm(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) return req.Fail("CPU not started"); - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; // In case of client errors, we limit the range to something that won't make us crash. static const uint32_t MAX_RANGE = 10000; - uint32_t start, end; - if (!req.ParamU32("address", &start)) - return; - uint32_t count = 0; - if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL)) - return; - if (count != 0) { - count = std::min(count, MAX_RANGE); - // Let's assume everything is two instructions. - g_disassemblyManager.analyze(start - 4, count * 8 + 8); - start = g_disassemblyManager.getStartAddress(start); - if (start == -1) - req.ParamU32("address", &start); - end = g_disassemblyManager.getNthNextAddress(start, count); - } else if (req.ParamU32("end", &end)) { - end = std::max(start, end); - if (end - start > MAX_RANGE * 4) - end = start + MAX_RANGE * 4; - // Let's assume everything is two instructions at most. - g_disassemblyManager.analyze(start - 4, end - start + 8); - start = g_disassemblyManager.getStartAddress(start); - if (start == -1) - req.ParamU32("address", &start); - // Correct end and calculate count based on it. - // This accounts for macros as one line, although two instructions. - u32 stop = end; - u32 next = start; - count = 0; - if (stop < start) { - for (next = start; next > stop; next = g_disassemblyManager.getNthNextAddress(next, 1)) { + // Route the disassembly manager/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([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + uint32_t start, end; + if (!req.ParamU32("address", &start)) + return; + uint32_t count = 0; + if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL)) + return; + if (count != 0) { + count = std::min(count, MAX_RANGE); + // Let's assume everything is two instructions. + g_disassemblyManager.analyze(start - 4, count * 8 + 8); + start = g_disassemblyManager.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + end = g_disassemblyManager.getNthNextAddress(start, count); + } else if (req.ParamU32("end", &end)) { + end = std::max(start, end); + if (end - start > MAX_RANGE * 4) + end = start + MAX_RANGE * 4; + // Let's assume everything is two instructions at most. + g_disassemblyManager.analyze(start - 4, end - start + 8); + start = g_disassemblyManager.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + // Correct end and calculate count based on it. + // This accounts for macros as one line, although two instructions. + u32 stop = end; + u32 next = start; + count = 0; + if (stop < start) { + for (next = start; next > stop; next = g_disassemblyManager.getNthNextAddress(next, 1)) { + count++; + } + } + for (end = next; end < stop && end >= next; end = g_disassemblyManager.getNthNextAddress(end, 1)) { count++; } + } else { + // Error message already sent. + return; } - for (end = next; end < stop && end >= next; end = g_disassemblyManager.getNthNextAddress(end, 1)) { - count++; + + bool displaySymbols = true; + if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) + return; + + JsonWriter &json = req.Respond(); + json.pushDict("range"); + json.writeUint("start", start); + json.writeUint("end", end); + json.pop(); + + json.pushArray("lines"); + DisassemblyLineInfo line; + uint32_t addr = start; + for (uint32_t i = 0; i < count; ++i) { + g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); + WriteDisasmLine(json, line); + addr += line.totalSize; + + // These are pretty long, so let's grease the wheels a bit. + if (i % 50 == 0) + req.Flush(); } - } else { - // Error message already sent. - return; - } + json.pop(); - bool displaySymbols = true; - if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) - return; - - JsonWriter &json = req.Respond(); - json.pushDict("range"); - json.writeUint("start", start); - json.writeUint("end", end); - json.pop(); - - json.pushArray("lines"); - DisassemblyLineInfo line; - uint32_t addr = start; - for (uint32_t i = 0; i < count; ++i) { - g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); - WriteDisasmLine(json, line); - addr += line.totalSize; - - // These are pretty long, so let's grease the wheels a bit. - if (i % 50 == 0) - req.Flush(); - } - json.pop(); - - json.pushArray("branchGuides"); - auto branchGuides = g_disassemblyManager.getBranchLines(start, end - start); - for (auto bl : branchGuides) - WriteBranchGuide(json, bl); - json.pop(); + json.pushArray("branchGuides"); + std::vector branchGuides = g_disassemblyManager.getBranchLines(start, end - start); + for (const BranchLine &bl : branchGuides) + WriteBranchGuide(json, bl); + json.pop(); + }); } // Search disassembly for some text (memory.searchDisasm) @@ -390,9 +394,6 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) return req.Fail("CPU not started"); - auto cpuDebug = CPUFromRequest(req); - if (!cpuDebug) - return; uint32_t start; if (!req.ParamU32("address", &start)) @@ -421,41 +422,51 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { std::transform(match.begin(), match.end(), match.begin(), ::tolower); - DisassemblyLineInfo line; - bool found = false; - uint32_t addr = start; - do { - g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); - const std::string addressSymbol = g_symbolMap->GetLabelString(addr); + // Route the disassembly manager/symbol reads to the CPU thread instead of poking at them directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + // Note: for a very large [start, end) range, this scan itself can take a while - unlike memory.search + // there's no size cap here, so a huge range will block the CPU thread's own frame pump for its duration. + Core_RunOnCPUThread([&] { + DebugInterface *cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - std::string mergeForSearch; - // Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case. - mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size()); + DisassemblyLineInfo line; + bool found = false; + uint32_t addr = start; + do { + g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); + const std::string addressSymbol = g_symbolMap->GetLabelString(addr); - sprintf(&mergeForSearch[0], "%08x ", addr); - auto inserter = mergeForSearch.begin() + 9; - if (!addressSymbol.empty()) { - inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower); - *inserter++ = ':'; + std::string mergeForSearch; + // Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case. + mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size()); + + sprintf(&mergeForSearch[0], "%08x ", addr); + std::string::iterator inserter = mergeForSearch.begin() + 9; + if (!addressSymbol.empty()) { + inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower); + *inserter++ = ':'; + *inserter++ = ' '; + } + inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower); *inserter++ = ' '; - } - inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower); - *inserter++ = ' '; - inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower); + inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower); - if (mergeForSearch.find(match) != mergeForSearch.npos) { - found = true; - break; - } + if (mergeForSearch.find(match) != mergeForSearch.npos) { + found = true; + break; + } - addr = RoundMemAddressUp(addr + line.totalSize); - } while (addr != end); + addr = RoundMemAddressUp(addr + line.totalSize); + } while (addr != end); - JsonWriter &json = req.Respond(); - if (found) - json.writeUint("address", addr); - else - json.writeNull("address"); + JsonWriter &json = req.Respond(); + if (found) + json.writeUint("address", addr); + else + json.writeNull("address"); + }); } // Assemble an instruction (memory.assemble) @@ -478,12 +489,22 @@ void WebSocketDisasmState::Assemble(DebuggerRequest &req) { if (!req.ParamString("code", &code)) return; + // Route the actual code assembly to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. std::string error; - if (!MipsAssembleOpcode(code, currentDebugMIPS, address, &error)) { + uint32_t encoding = 0; + bool ok = false; + Core_RunOnCPUThread([&] { + ok = MipsAssembleOpcode(code, currentDebugMIPS, address, &error); + if (ok) + encoding = Memory::Read_Instruction(address).encoding; + }); + + if (!ok) { return req.Fail(StringFromFormat("Could not assemble: %s", error.c_str())); } JsonWriter &json = req.Respond(); Reporting::NotifyDebugger(); - json.writeUint("encoding", Memory::Read_Instruction(address).encoding); + json.writeUint("encoding", encoding); } 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); + }); } diff --git a/Core/Debugger/WebSocket/MemorySubscriber.cpp b/Core/Debugger/WebSocket/MemorySubscriber.cpp index 0ad7f9f8e4..265cbb9926 100644 --- a/Core/Debugger/WebSocket/MemorySubscriber.cpp +++ b/Core/Debugger/WebSocket/MemorySubscriber.cpp @@ -56,23 +56,18 @@ struct AutoDisabledReplacements { std::map replacements; std::vector emuhacks; bool saved = false; - bool wasStepping = false; }; -// Important: Only use keepReplacements when reading, not writing. -static AutoDisabledReplacements LockMemoryAndCPU(uint32_t addr, bool keepReplacements) { +// Call this from within a Core_RunOnCPUThread() callback - see Core_RunOnCPUThread() in Core.h. +// No longer needs to pause a running CPU to do this safely: we're already running on the CPU thread +// by the time this is called, so nothing else can be concurrently executing MIPS code or touching the +// JIT's emuhack ops on this thread while we hold onto them below. +// +// Important: Only use keepReplacements=false when reading, not writing. +static AutoDisabledReplacements LockMemory(bool keepReplacements) { AutoDisabledReplacements result; - CoreState state = coreState; - if (Core_IsStepping()) { - result.wasStepping = true; - } else { - while (state != CoreState::CORE_RUNNING_CPU) { - state = coreState; - } - Core_Break(BreakReason::MemoryAccess, addr); - Core_WaitInactive(); - } - + // This still guards against a *different* thread (not the CPU thread) tearing down or + // reinitializing the memory system underneath us, e.g. during shutdown. result.lock = new Memory::MemoryInitedLock(); if (!keepReplacements) { result.saved = true; @@ -92,8 +87,6 @@ AutoDisabledReplacements::AutoDisabledReplacements(AutoDisabledReplacements &&ot emuhacks = std::move(other.emuhacks); saved = other.saved; other.saved = false; - wasStepping = other.wasStepping; - other.wasStepping = true; } AutoDisabledReplacements::~AutoDisabledReplacements() { @@ -103,8 +96,6 @@ AutoDisabledReplacements::~AutoDisabledReplacements() { MIPSComp::jit->RestoreSavedEmuHackOps(emuhacks); RestoreSavedReplacements(replacements); } - if (!wasStepping) - Core_Resume(); delete lock; } @@ -121,17 +112,23 @@ void WebSocketMemoryReadU8(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U8(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U8(addr)); + }); } // Read two bytes from memory (memory.read_u16) @@ -147,17 +144,23 @@ void WebSocketMemoryReadU16(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U16(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U16(addr)); + }); } // Read four bytes from memory (memory.read_u32) @@ -173,17 +176,23 @@ void WebSocketMemoryReadU32(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U32(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U32(addr)); + }); } // Read bytes from memory (memory.read) @@ -206,29 +215,40 @@ void WebSocketMemoryRead(DebuggerRequest &req) { if (!req.ParamBool("replacements", &replacements, DebuggerParamType::OPTIONAL)) return; - auto memLock = LockMemoryAndCPU(addr, replacements); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + // Note: for a very large 'size', base64-encoding it is real CPU work that will now happen on the + // CPU thread itself, blocking its own frame pump for the duration - same caveat as memory.search. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(replacements); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) - return req.Fail("Invalid address"); - else if (!Memory::IsValidRange(addr, size)) - return req.Fail("Invalid size"); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } else if (!Memory::IsValidRange(addr, size)) { + req.Fail("Invalid size"); + return; + } - JsonWriter &json = req.Respond(); - // Start a value without any actual data yet... - json.writeRaw("base64", ""); - req.Flush(); + JsonWriter &json = req.Respond(); + // Start a value without any actual data yet... + json.writeRaw("base64", ""); + req.Flush(); - // Now we'll write it directly to the stream. - req.ws->AddFragment(false, "\""); - // 65535 is an "even" number of base64 characters. - static const size_t CHUNK_SIZE = 65535; - for (size_t i = 0; i < size; i += CHUNK_SIZE) { - size_t left = std::min(size - i, CHUNK_SIZE); - req.ws->AddFragment(false, Base64Encode(Memory::GetPointerUnchecked(addr) + i, left)); - } - req.ws->AddFragment(false, "\""); + // Now we'll write it directly to the stream. + req.ws->AddFragment(false, "\""); + // 65535 is an "even" number of base64 characters. + static const size_t CHUNK_SIZE = 65535; + for (size_t i = 0; i < size; i += CHUNK_SIZE) { + size_t left = std::min(size - i, CHUNK_SIZE); + req.ws->AddFragment(false, Base64Encode(Memory::GetPointerUnchecked(addr) + i, left)); + } + req.ws->AddFragment(false, "\""); + }); } // Read a NUL terminated string from memory (memory.readString) @@ -247,30 +267,40 @@ void WebSocketMemoryReadString(DebuggerRequest &req) { if (!req.ParamU32("address", &addr)) return; - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory read to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - std::string type = "utf-8"; - if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL)) - return; - if (type != "utf-8" && type != "base64") - return req.Fail("Invalid type, must be either utf-8 or base64"); + std::string type = "utf-8"; + if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL)) + return; + if (type != "utf-8" && type != "base64") { + req.Fail("Invalid type, must be either utf-8 or base64"); + return; + } - if (!Memory::IsValidAddress(addr)) - return req.Fail("Invalid address"); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } - // Let's try to avoid crashing and get a safe length. - const uint8_t *p = Memory::GetPointerUnchecked(addr); - size_t longest = Memory::ClampValidSizeAt(addr, Memory::g_MemorySize); - size_t len = strnlen((const char *)p, longest); + // Let's try to avoid crashing and get a safe length. + const uint8_t *p = Memory::GetPointerUnchecked(addr); + size_t longest = Memory::ClampValidSizeAt(addr, Memory::g_MemorySize); + size_t len = strnlen((const char *)p, longest); - JsonWriter &json = req.Respond(); - if (type == "utf-8") { - json.writeString("value", std::string((const char *)p, len)); - } else if (type == "base64") { - json.writeString("base64", Base64Encode(p, len)); - } + JsonWriter &json = req.Respond(); + if (type == "utf-8") { + json.writeString("value", std::string((const char *)p, len)); + } else if (type == "base64") { + json.writeString("base64", Base64Encode(p, len)); + } + }); } // Write a byte to memory (memory.write_u8) @@ -290,20 +320,26 @@ void WebSocketMemoryWriteU8(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory write to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } - currentMIPS->InvalidateICache(addr, 1); - Memory::Write_U8(val, addr); - Reporting::NotifyDebugger(); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } + currentMIPS->InvalidateICache(addr, 1); + Memory::Write_U8(val, addr); + Reporting::NotifyDebugger(); - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U8(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U8(addr)); + }); } // Write two bytes to memory (memory.write_u16) @@ -323,20 +359,26 @@ void WebSocketMemoryWriteU16(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory write to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } - currentMIPS->InvalidateICache(addr, 2); - Memory::Write_U16(val, addr); - Reporting::NotifyDebugger(); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } + currentMIPS->InvalidateICache(addr, 2); + Memory::Write_U16(val, addr); + Reporting::NotifyDebugger(); - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U16(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U16(addr)); + }); } // Write four bytes to memory (memory.write_u32) @@ -356,20 +398,26 @@ void WebSocketMemoryWriteU32(DebuggerRequest &req) { return; } - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory write to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - if (!Memory::IsValidAddress(addr)) { - req.Fail("Invalid address"); - return; - } - currentMIPS->InvalidateICache(addr, 4); - Memory::Write_U32(val, addr); - Reporting::NotifyDebugger(); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } + currentMIPS->InvalidateICache(addr, 4); + Memory::Write_U32(val, addr); + Reporting::NotifyDebugger(); - JsonWriter &json = req.Respond(); - json.writeUint("value", Memory::Read_U32(addr)); + JsonWriter &json = req.Respond(); + json.writeUint("value", Memory::Read_U32(addr)); + }); } // Write bytes to memory (memory.write) @@ -387,22 +435,31 @@ void WebSocketMemoryWrite(DebuggerRequest &req) { if (!req.ParamString("base64", &encoded)) return; - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); + // Route the actual memory write to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } - std::vector value = Base64Decode(&encoded[0], encoded.size()); - uint32_t size = (uint32_t)value.size(); + std::vector value = Base64Decode(&encoded[0], encoded.size()); + uint32_t size = (uint32_t)value.size(); - if (!Memory::IsValidAddress(addr)) - return req.Fail("Invalid address"); - else if (value.size() != (size_t)size || !Memory::IsValidRange(addr, size)) - return req.Fail("Invalid size"); + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } else if (value.size() != (size_t)size || !Memory::IsValidRange(addr, size)) { + req.Fail("Invalid size"); + return; + } - currentMIPS->InvalidateICache(addr, size); - Memory::MemcpyUnchecked(addr, &value[0], size); - Reporting::NotifyDebugger(); - req.Respond(); + currentMIPS->InvalidateICache(addr, size); + Memory::MemcpyUnchecked(addr, &value[0], size); + Reporting::NotifyDebugger(); + req.Respond(); + }); } // Search memory for a value or byte pattern (memory.search) @@ -441,105 +498,125 @@ void WebSocketMemorySearch(DebuggerRequest &req) { if (!req.ParamString("type", &type)) return; - auto memLock = LockMemoryAndCPU(addr, true); - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) - return req.Fail("CPU not started"); - if (!Memory::IsValidAddress(addr)) - return req.Fail("Invalid address"); - else if (!Memory::IsValidRange(addr, size)) - return req.Fail("Invalid size"); - - uint32_t align = 1; - uint32_t needleSize = 0; - uint32_t needleValue = 0; - std::vector needleBytes; - std::vector maskBytes; - - if (type == "u8" || type == "u16" || type == "u32") { - needleSize = type == "u8" ? 1 : type == "u16" ? 2 : 4; - align = needleSize; - if (!req.ParamU32("value", &needleValue, false)) + // Route the actual memory scan to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + // Note: 'size' has no cap beyond valid memory range, so a very large scan will now block the CPU + // thread's own frame pump for its duration, rather than running unqueued on this thread as before. + Core_RunOnCPUThread([&] { + AutoDisabledReplacements memLock = LockMemory(true); + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + req.Fail("CPU not started"); + return; + } + if (!Memory::IsValidAddress(addr)) { + req.Fail("Invalid address"); + return; + } else if (!Memory::IsValidRange(addr, size)) { + req.Fail("Invalid size"); return; - } else if (type == "float") { - needleSize = 4; - align = 4; - // allowFloatBits: accepts a string like "1.5" and gives us its raw bit pattern. - if (!req.ParamU32("value", &needleValue, true)) - return; - } else if (type == "bytes") { - std::string encoded; - if (!req.ParamString("base64", &encoded)) - return; - needleBytes = Base64Decode(&encoded[0], encoded.size()); - if (needleBytes.empty()) - return req.Fail("'base64' must decode to at least one byte"); - needleSize = (uint32_t)needleBytes.size(); - align = 1; - - if (req.HasParam("maskBase64")) { - std::string maskEncoded; - if (!req.ParamString("maskBase64", &maskEncoded)) - return; - maskBytes = Base64Decode(&maskEncoded[0], maskEncoded.size()); - if (maskBytes.size() != needleBytes.size()) - return req.Fail("'maskBase64' must decode to the same length as 'base64'"); } - } else { - return req.Fail("Invalid 'type', must be u8, u16, u32, float, or bytes"); - } - if (needleSize > size) - return req.Fail("'size' is smaller than the pattern/value being searched for"); + uint32_t align = 1; + uint32_t needleSize = 0; + uint32_t needleValue = 0; + std::vector needleBytes; + std::vector maskBytes; - if (!req.ParamU32("align", &align, false, DebuggerParamType::OPTIONAL)) - return; - if (align == 0) - return req.Fail("'align' must not be zero"); + if (type == "u8" || type == "u16" || type == "u32") { + needleSize = type == "u8" ? 1 : type == "u16" ? 2 : 4; + align = needleSize; + if (!req.ParamU32("value", &needleValue, false)) + return; + } else if (type == "float") { + needleSize = 4; + align = 4; + // allowFloatBits: accepts a string like "1.5" and gives us its raw bit pattern. + if (!req.ParamU32("value", &needleValue, true)) + return; + } else if (type == "bytes") { + std::string encoded; + if (!req.ParamString("base64", &encoded)) + return; + needleBytes = Base64Decode(&encoded[0], encoded.size()); + if (needleBytes.empty()) { + req.Fail("'base64' must decode to at least one byte"); + return; + } + needleSize = (uint32_t)needleBytes.size(); + align = 1; - uint32_t maxResults = 1000; - if (!req.ParamU32("maxResults", &maxResults, false, DebuggerParamType::OPTIONAL)) - return; - if (maxResults == 0) - maxResults = 1000; - else if (maxResults > 100000) - maxResults = 100000; - - const uint8_t *base = Memory::GetPointerUnchecked(addr); - std::vector matches; - bool truncated = false; - for (uint32_t offset = 0; offset + needleSize <= size; offset += align) { - bool match; - if (!needleBytes.empty()) { - match = true; - for (uint32_t i = 0; i < needleSize; ++i) { - uint8_t mask = maskBytes.empty() ? 0xFF : maskBytes[i]; - if ((base[offset + i] & mask) != (needleBytes[i] & mask)) { - match = false; - break; + if (req.HasParam("maskBase64")) { + std::string maskEncoded; + if (!req.ParamString("maskBase64", &maskEncoded)) + return; + maskBytes = Base64Decode(&maskEncoded[0], maskEncoded.size()); + if (maskBytes.size() != needleBytes.size()) { + req.Fail("'maskBase64' must decode to the same length as 'base64'"); + return; } } } else { - uint32_t actual = base[offset]; - if (needleSize >= 2) - actual |= base[offset + 1] << 8; - if (needleSize >= 4) - actual |= (base[offset + 2] << 16) | (base[offset + 3] << 24); - match = actual == needleValue; + req.Fail("Invalid 'type', must be u8, u16, u32, float, or bytes"); + return; } - if (match) { - if (matches.size() >= maxResults) { - truncated = true; - break; + if (needleSize > size) { + req.Fail("'size' is smaller than the pattern/value being searched for"); + return; + } + + if (!req.ParamU32("align", &align, false, DebuggerParamType::OPTIONAL)) + return; + if (align == 0) { + req.Fail("'align' must not be zero"); + return; + } + + uint32_t maxResults = 1000; + if (!req.ParamU32("maxResults", &maxResults, false, DebuggerParamType::OPTIONAL)) + return; + if (maxResults == 0) + maxResults = 1000; + else if (maxResults > 100000) + maxResults = 100000; + + const uint8_t *base = Memory::GetPointerUnchecked(addr); + std::vector matches; + bool truncated = false; + for (uint32_t offset = 0; offset + needleSize <= size; offset += align) { + bool match; + if (!needleBytes.empty()) { + match = true; + for (uint32_t i = 0; i < needleSize; ++i) { + uint8_t mask = maskBytes.empty() ? 0xFF : maskBytes[i]; + if ((base[offset + i] & mask) != (needleBytes[i] & mask)) { + match = false; + break; + } + } + } else { + uint32_t actual = base[offset]; + if (needleSize >= 2) + actual |= base[offset + 1] << 8; + if (needleSize >= 4) + actual |= (base[offset + 2] << 16) | (base[offset + 3] << 24); + match = actual == needleValue; } - matches.push_back(addr + offset); - } - } - JsonWriter &json = req.Respond(); - json.pushArray("matches"); - for (uint32_t m : matches) - json.writeUint(m); - json.pop(); - json.writeBool("truncated", truncated); + if (match) { + if (matches.size() >= maxResults) { + truncated = true; + break; + } + matches.push_back(addr + offset); + } + } + + JsonWriter &json = req.Respond(); + json.pushArray("matches"); + for (uint32_t m : matches) + json.writeUint(m); + json.pop(); + json.writeBool("truncated", truncated); + }); } diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 04c2824455..fff6197f8f 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -44,13 +44,12 @@ struct WebSocketSteppingState : public DebuggerSubscriber { protected: uint32_t GetNextAddress(DebugInterface *cpuDebug); - int GetNextInstructionCount(DebugInterface *cpuDebug); void PrepareResume(); void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID); }; DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) { - auto p = new WebSocketSteppingState(); + WebSocketSteppingState *p = new WebSocketSteppingState(); map["cpu.stepInto"] = [p](DebuggerRequest &req) { p->Into(req); }; map["cpu.stepOver"] = [p](DebuggerRequest &req) { p->Over(req); }; map["cpu.stepOut"] = [p](DebuggerRequest &req) { p->Out(req); }; @@ -90,33 +89,39 @@ void WebSocketSteppingState::Into(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive()) return req.Fail("CPU not started"); if (!Core_IsStepping()) { + // Core_Break() is explicitly free-threaded (see Core.cpp), so no need to bounce this to the CPU + // thread - and we can't anyway, since queuing to it only makes sense once the CPU actually *is* + // stepping, which this call is what triggers in the first place. Core_Break(BreakReason::DebugStepInto, 0); return; } - uint32_t threadID; - auto cpuDebug = CPUFromRequest(req, &threadID); - if (!cpuDebug) - return; + // Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + uint32_t threadID; + DebugInterface *cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; - if (cpuDebug == currentDebugMIPS) { - // If the current PC is on a breakpoint, the user doesn't want to do nothing. - g_breakpoints.SetSkipFirst(currentMIPS->pc); + if (cpuDebug == currentDebugMIPS) { + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + g_breakpoints.SetSkipFirst(currentMIPS->pc); - int c = GetNextInstructionCount(cpuDebug); - Core_RequestCPUStep(CPUStepType::Into, c); - } else { - uint32_t breakpointAddress = cpuDebug->GetPC(); - PrepareResume(); - // Could have advanced to the breakpoint already in PrepareResume(). - // Note: we need to get cpuDebug again anyway (in case we ran some HLE above.) - cpuDebug = CPUFromRequest(req); - if (cpuDebug != currentDebugMIPS) { - g_breakpoints.AddBreakPoint(breakpointAddress, true); - AddThreadCondition(breakpointAddress, threadID); - Core_Resume(); + Core_RequestCPUStep(CPUStepType::Into, 1); + } else { + uint32_t breakpointAddress = cpuDebug->GetPC(); + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + // Note: we need to get cpuDebug again anyway (in case we ran some HLE above.) + cpuDebug = CPUFromRequest(req); + if (cpuDebug != currentDebugMIPS) { + g_breakpoints.AddBreakPoint(breakpointAddress, true); + AddThreadCondition(breakpointAddress, threadID); + Core_Resume(); + } } - } + }); } // Step over the next instruction (cpu.stepOver) @@ -135,41 +140,45 @@ void WebSocketSteppingState::Over(DebuggerRequest &req) { if (!Core_IsStepping()) return req.Fail("CPU currently running (cpu.stepping first)"); - uint32_t threadID; - auto cpuDebug = CPUFromRequest(req, &threadID); - if (!cpuDebug) - return; + // Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + uint32_t threadID; + DebugInterface *cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; - MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); - uint32_t breakpointAddress = GetNextAddress(cpuDebug); - if (info.isBranch) { - if (info.isConditional && !info.isLinkedBranch) { - if (info.conditionMet) { - breakpointAddress = info.branchTarget; + MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); + uint32_t breakpointAddress = GetNextAddress(cpuDebug); + if (info.isBranch) { + if (info.isConditional && !info.isLinkedBranch) { + if (info.conditionMet) { + breakpointAddress = info.branchTarget; + } else { + // Skip over the delay slot. + breakpointAddress += 4; + } } else { - // Skip over the delay slot. - breakpointAddress += 4; - } - } else { - if (info.isLinkedBranch) { - // jal or jalr - a function call. Skip the delay slot. - breakpointAddress += 4; - } else { - // j - for absolute branches, set the breakpoint at the branch target. - breakpointAddress = info.branchTarget; + if (info.isLinkedBranch) { + // jal or jalr - a function call. Skip the delay slot. + breakpointAddress += 4; + } else { + // j - for absolute branches, set the breakpoint at the branch target. + breakpointAddress = info.branchTarget; + } } } - } - PrepareResume(); - // Could have advanced to the breakpoint already in PrepareResume(). - cpuDebug = CPUFromRequest(req); - if (cpuDebug->GetPC() != breakpointAddress) { - g_breakpoints.AddBreakPoint(breakpointAddress, true); - if (cpuDebug != currentDebugMIPS) - AddThreadCondition(breakpointAddress, threadID); - Core_Resume(); - } + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { + g_breakpoints.AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); + Core_Resume(); + } + }); } // Step out of a function based on a stack walk (cpu.stepOut) @@ -186,39 +195,43 @@ void WebSocketSteppingState::Out(DebuggerRequest &req) { if (!Core_IsStepping()) return req.Fail("CPU currently running (cpu.stepping first)"); - uint32_t threadID; - auto cpuDebug = CPUFromRequest(req, &threadID); - if (!cpuDebug) - return; + // Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + uint32_t threadID; + DebugInterface *cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; - 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; + 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; + } } - } - 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); - if (frames.size() < 2) { - return req.Fail("Could not find function call to step out into"); - } + 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); + if (frames.size() < 2) { + return req.Fail("Could not find function call to step out into"); + } - uint32_t breakpointAddress = frames[1].pc; - PrepareResume(); - // Could have advanced to the breakpoint already in PrepareResume(). - cpuDebug = CPUFromRequest(req); - if (cpuDebug->GetPC() != breakpointAddress) { - g_breakpoints.AddBreakPoint(breakpointAddress, true); - if (cpuDebug != currentDebugMIPS) - AddThreadCondition(breakpointAddress, threadID); - Core_Resume(); - } + uint32_t breakpointAddress = frames[1].pc; + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { + g_breakpoints.AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); + Core_Resume(); + } + }); } // Run until a certain address (cpu.runUntil) @@ -238,13 +251,17 @@ void WebSocketSteppingState::RunUntil(DebuggerRequest &req) { return; } - bool wasAtAddress = currentMIPS->pc == address; - PrepareResume(); - // We may have arrived already if PauseResume() stepped out of a delay slot. - if (currentMIPS->pc != address || wasAtAddress) { - g_breakpoints.AddBreakPoint(address, true); - Core_Resume(); - } + // Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + bool wasAtAddress = currentMIPS->pc == address; + PrepareResume(); + // We may have arrived already if PauseResume() stepped out of a delay slot. + if (currentMIPS->pc != address || wasAtAddress) { + g_breakpoints.AddBreakPoint(address, true); + Core_Resume(); + } + }); } // Jump after the next HLE call (cpu.nextHLE) @@ -257,9 +274,13 @@ void WebSocketSteppingState::HLE(DebuggerRequest &req) { return req.Fail("CPU not started"); } - PrepareResume(); - hleDebugBreak(); - Core_Resume(); + // Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly + // from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h. + Core_RunOnCPUThread([&] { + PrepareResume(); + hleDebugBreak(); + Core_Resume(); + }); } uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { @@ -267,10 +288,6 @@ uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { return g_disassemblyManager.getNthNextAddress(current, 1); } -int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) { - return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4; -} - void WebSocketSteppingState::PrepareResume() { if (currentMIPS->inDelaySlot) { // Delay slot instructions are never joined, so we pass 1. diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 2668f858ee..8dd2eec8d8 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -38,8 +38,8 @@ #include "Core/Util/RecentFiles.h" #include "Core/Util/PathUtil.h" #include "Core/Config.h" -#include "Core/Debugger/WebSocket.h" #include "Core/WebServer.h" +#include "Core/Debugger/WebSocket.h" enum class ServerStatus { STOPPED, @@ -760,6 +760,17 @@ void WebServerSetUploadPath(const Path &path) { g_uploadPath = path; } +WebServerFlags GetServerFlags() { + return serverFlags; +} + +void OpenWebDebugger() { + if (!WebServerRunning(WebServerFlags::DEBUGGER)) { + StartWebServer(WebServerFlags::DEBUGGER); + } + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "http://localhost:" + std::to_string(g_Config.iRemoteISOPort) + "/debugger/index.html"); +} + static void WebServerThread() { SetCurrentThreadName("HTTPServer"); @@ -787,6 +798,14 @@ static void WebServerThread() { double lastRegister = time_now_d(); INFO_LOG(Log::HTTP, "Entering web server loop. Listening on port %d", g_Config.iRemoteISOPort); + + if (serverFlags & WebServerFlags::DEBUGGER) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, "Debugger web server running on port " + std::to_string(g_Config.iRemoteISOPort), 5.0f, "debugger"); + g_OSD.SetClickCallback("debugger", []() { + OpenWebDebugger(); + }); + } + while (RetrieveStatus() == ServerStatus::RUNNING) { constexpr double webServerSliceSeconds = 0.2f; http->RunSlice(webServerSliceSeconds); diff --git a/Core/WebServer.h b/Core/WebServer.h index 9fd1f4f807..3737119bd3 100644 --- a/Core/WebServer.h +++ b/Core/WebServer.h @@ -43,6 +43,9 @@ bool RemoteISOFileSupported(const std::string &filename); void WebServerSetUploadPath(const Path &path); int WebServerPort(); +// Will start the webserver if not running. +void OpenWebDebugger(); + struct UploadProgress { s64 totalBytes = 0; s64 uploadedBytes = 0; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 548912c655..0076ffb577 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -14,6 +14,7 @@ #include "Core/Config.h" #include "Core/System.h" #include "Core/SaveState.h" +#include "Core/WebServer.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/RetroAchievements.h" #include "Core/Core.h" @@ -2392,6 +2393,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: }); } ImGui::Separator(); + if (ImGui::MenuItem("Open web debugger")) { + OpenWebDebugger(); + } + ImGui::Separator(); if (ImGui::MenuItem("Exit")) { System_ExitApp(); } diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 1fd8ca1a7f..a72461953a 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -465,7 +465,7 @@ void ImDisasmView::FollowBranch() { DisassemblyLineInfo line; g_disassemblyManager.getLine(curAddress_, true, line, debugger_); - if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { + if (line.type == DISTYPE_OPCODE) { if (line.info.isBranch) { jumpStack_.push_back(curAddress_); gotoAddr(line.info.branchTarget); @@ -890,7 +890,7 @@ void ImDisasmView::updateStatusBarText() { g_disassemblyManager.getLine(curAddress_, true, line, debugger_); text[0] = 0; - if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { + if (line.type == DISTYPE_OPCODE) { if (line.info.hasRelevantAddress && IsLikelyStringAt(line.info.relevantAddress)) { snprintf(text, sizeof(text), "[%08X] = \"%s\"", line.info.relevantAddress, Memory::GetCharPointer(line.info.relevantAddress)); } @@ -1162,13 +1162,6 @@ void ImDisasmView::scrollStepping(u32 newPc) { } } -u32 ImDisasmView::getInstructionSizeAt(u32 address) { - u32 start = g_disassemblyManager.getStartAddress(address); - u32 next = g_disassemblyManager.getNthNextAddress(start, 1); - return next - address; -} - - void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState) { disasmView_.setDebugger(mipsDebug); @@ -1181,12 +1174,10 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro if (ImGui::IsWindowFocused()) { // Process stepping keyboard shortcuts. if (ImGui::IsKeyPressed(ImGuiKey_F10)) { - u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC()); - Core_RequestCPUStep(CPUStepType::Over, stepSize); + Core_RequestCPUStep(CPUStepType::Over, 1); } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC()); - Core_RequestCPUStep(CPUStepType::Into, stepSize); + Core_RequestCPUStep(CPUStepType::Into, 1); } } @@ -1219,8 +1210,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro ImGui::SameLine(); if (ImGui::RepeatButtonShift("Into")) { - u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC()); - Core_RequestCPUStep(CPUStepType::Into, stepSize); + Core_RequestCPUStep(CPUStepType::Into, 1); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("F11"); @@ -1228,8 +1218,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro ImGui::SameLine(); if (ImGui::SmallButton("Over")) { - u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC()); - Core_RequestCPUStep(CPUStepType::Over, stepSize); + Core_RequestCPUStep(CPUStepType::Over, 1); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("F10"); diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index f70f7291f4..573288e858 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -58,7 +58,6 @@ public: } void scrollStepping(u32 newPc); - u32 getInstructionSizeAt(u32 address); // not const because it might have to analyze. void gotoAddr(unsigned int addr) { if (positionLocked_ != 0) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 8f328112c6..6d78ed67f8 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -602,7 +602,7 @@ void CtrlDisAsmView::followBranch() DisassemblyLineInfo line; g_disassemblyManager.getLine(curAddress, true, line, debugger); - if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) + if (line.type == DISTYPE_OPCODE) { if (line.info.isBranch) { @@ -1116,7 +1116,7 @@ void CtrlDisAsmView::updateStatusBarText() g_disassemblyManager.getLine(curAddress,true,line, debugger); text[0] = 0; - if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) + if (line.type == DISTYPE_OPCODE) { if (line.info.hasRelevantAddress && IsLikelyStringAt(line.info.relevantAddress)) { snprintf(text, sizeof(text), "[%08X] = \"%s\"", line.info.relevantAddress, Memory::GetCharPointer(line.info.relevantAddress)); @@ -1350,9 +1350,3 @@ void CtrlDisAsmView::scrollStepping(u32 newPc) } } -u32 CtrlDisAsmView::getInstructionSizeAt(u32 address) -{ - u32 start = g_disassemblyManager.getStartAddress(address); - u32 next = g_disassemblyManager.getNthNextAddress(start,1); - return next - address; -} diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index 2f661ec659..727fd55573 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -119,7 +119,6 @@ public: } void scrollStepping(u32 newPc); - u32 getInstructionSizeAt(u32 address); void gotoAddr(unsigned int addr) { diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index ef577edf23..e0c294e772 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -206,8 +206,7 @@ void CDisasm::step(CPUStepType stepType) { ptr->setDontRedraw(true); lastTicks_ = CoreTiming::GetTicks(); - u32 stepSize = ptr->getInstructionSizeAt(cpu->GetPC()); - Core_RequestCPUStep(stepType, stepSize); + Core_RequestCPUStep(stepType, 1); } void CDisasm::runToLine() { diff --git a/headless/Headless.cpp b/headless/Headless.cpp index a449d6b7b7..af52010547 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -92,6 +92,7 @@ bool System_AudioRecordingState() { return false; } void NativeFrame(GraphicsContext *graphicsContext) { } void NativeResized() { } +void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {} std::string System_GetProperty(SystemProperty prop) { return ""; } std::vector System_GetPropertyStringVec(SystemProperty prop) { return std::vector(); } int64_t System_GetPropertyInt(SystemProperty prop) {