mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #22051 from hrydgard/web-debugger-access
Refactor the websocket debug interface to be thread-safe
This commit is contained in:
20 files changed
+1296
-1110
No files matched your search
+70
-5
@@ -17,9 +17,13 @@
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <condition_variable>
|
||||
|
||||
#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<void()> func;
|
||||
bool done = false;
|
||||
};
|
||||
static std::mutex g_cpuQueueMutex;
|
||||
static std::condition_variable g_cpuQueueCond;
|
||||
static std::vector<std::shared_ptr<CPUThreadTask>> 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<bool> g_cpuThreadIdValid{ false };
|
||||
|
||||
void Core_RunOnCPUThread(std::function<void()> 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<CPUThreadTask>();
|
||||
task->func = std::move(func);
|
||||
|
||||
std::unique_lock<std::mutex> 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<std::shared_ptr<CPUThreadTask>> tasks;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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());
|
||||
|
||||
+16
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -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<void()> func);
|
||||
|
||||
extern volatile CoreState coreState;
|
||||
extern volatile bool coreStatePending;
|
||||
|
||||
|
||||
@@ -595,26 +595,6 @@ void DisassemblyFunction::load()
|
||||
{
|
||||
generateBranchLines();
|
||||
|
||||
// gather all branch targets
|
||||
std::set<u32> branchTargets;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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::vector<BranchLi
|
||||
}
|
||||
|
||||
|
||||
void DisassemblyMacro::setMacroLi(u32 _immediate, u8 _rt)
|
||||
{
|
||||
type = MACRO_LI;
|
||||
name = "li";
|
||||
immediate = _immediate;
|
||||
rt = _rt;
|
||||
numOpcodes = 2;
|
||||
}
|
||||
|
||||
void DisassemblyMacro::setMacroMemory(std::string_view _name, u32 _immediate, u8 _rt, int _dataSize)
|
||||
{
|
||||
type = MACRO_MEMORYIMM;
|
||||
name = _name;
|
||||
immediate = _immediate;
|
||||
rt = _rt;
|
||||
dataSize = _dataSize;
|
||||
numOpcodes = 2;
|
||||
}
|
||||
|
||||
bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
|
||||
{
|
||||
char buffer[64];
|
||||
dest.type = DISTYPE_MACRO;
|
||||
dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);
|
||||
|
||||
std::string addressSymbol;
|
||||
switch (type)
|
||||
{
|
||||
case MACRO_LI:
|
||||
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.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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<std::mutex> 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--;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/Breakpoints.h"
|
||||
#include "Core/Debugger/DisassemblyManager.h"
|
||||
#include "Core/Debugger/SymbolMap.h"
|
||||
@@ -138,8 +139,12 @@ void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
|
||||
if (!params.Parse(req))
|
||||
return;
|
||||
|
||||
g_breakpoints.AddBreakPoint(params.address);
|
||||
params.Apply();
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
g_breakpoints.AddBreakPoint(params.address);
|
||||
params.Apply();
|
||||
});
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -157,11 +162,19 @@ void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
|
||||
WebSocketCPUBreakpointParams params;
|
||||
if (!params.Parse(req))
|
||||
return;
|
||||
bool enabled;
|
||||
if (!g_breakpoints.IsAddressBreakPoint(params.address, &enabled))
|
||||
return req.Fail("Breakpoint not found");
|
||||
|
||||
params.Apply();
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
bool found = false;
|
||||
Core_RunOnCPUThread([&] {
|
||||
bool enabled;
|
||||
found = g_breakpoints.IsAddressBreakPoint(params.address, &enabled);
|
||||
if (found)
|
||||
params.Apply();
|
||||
});
|
||||
|
||||
if (!found)
|
||||
return req.Fail("Breakpoint not found");
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -180,7 +193,11 @@ void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
|
||||
if (!req.ParamU32("address", &address))
|
||||
return;
|
||||
|
||||
g_breakpoints.RemoveBreakPoint(address);
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
g_breakpoints.RemoveBreakPoint(address);
|
||||
});
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -202,38 +219,42 @@ void WebSocketCPUBreakpointList(DebuggerRequest &req) {
|
||||
return req.Fail("CPU not started");
|
||||
}
|
||||
|
||||
JsonWriter &json = req.Respond();
|
||||
json.pushArray("breakpoints");
|
||||
auto bps = g_breakpoints.GetBreakpoints();
|
||||
for (const auto &bp : bps) {
|
||||
if (bp.temporary)
|
||||
continue;
|
||||
// Route the breakpoint/symbol/disassembly reads to the CPU thread instead of poking at them directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
JsonWriter &json = req.Respond();
|
||||
json.pushArray("breakpoints");
|
||||
std::vector<BreakPoint> bps = g_breakpoints.GetBreakpoints();
|
||||
for (const BreakPoint &bp : bps) {
|
||||
if (bp.temporary)
|
||||
continue;
|
||||
|
||||
json.pushDict();
|
||||
json.writeUint("address", bp.addr);
|
||||
json.writeBool("enabled", bp.IsEnabled());
|
||||
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
|
||||
if (bp.hasCond)
|
||||
json.writeString("condition", bp.cond.expressionString);
|
||||
else
|
||||
json.writeNull("condition");
|
||||
if (!bp.logFormat.empty())
|
||||
json.writeString("logFormat", bp.logFormat);
|
||||
else
|
||||
json.writeNull("logFormat");
|
||||
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
|
||||
if (symbol.empty())
|
||||
json.writeNull("symbol");
|
||||
else
|
||||
json.writeString("symbol", symbol);
|
||||
json.pushDict();
|
||||
json.writeUint("address", bp.addr);
|
||||
json.writeBool("enabled", bp.IsEnabled());
|
||||
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
|
||||
if (bp.hasCond)
|
||||
json.writeString("condition", bp.cond.expressionString);
|
||||
else
|
||||
json.writeNull("condition");
|
||||
if (!bp.logFormat.empty())
|
||||
json.writeString("logFormat", bp.logFormat);
|
||||
else
|
||||
json.writeNull("logFormat");
|
||||
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
|
||||
if (symbol.empty())
|
||||
json.writeNull("symbol");
|
||||
else
|
||||
json.writeString("symbol", symbol);
|
||||
|
||||
DisassemblyLineInfo line;
|
||||
g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS);
|
||||
json.writeString("code", line.name + " " + line.params);
|
||||
DisassemblyLineInfo line;
|
||||
g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS);
|
||||
json.writeString("code", line.name + " " + line.params);
|
||||
|
||||
json.pop();
|
||||
}
|
||||
json.pop();
|
||||
}
|
||||
json.pop();
|
||||
});
|
||||
}
|
||||
|
||||
struct WebSocketMemoryBreakpointParams {
|
||||
@@ -361,8 +382,12 @@ void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
|
||||
if (!params.Parse(req))
|
||||
return;
|
||||
|
||||
g_breakpoints.AddMemCheck(params.address, params.end, params.cond, params.Result(true));
|
||||
params.Apply();
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
g_breakpoints.AddMemCheck(params.address, params.end, params.cond, params.Result(true));
|
||||
params.Apply();
|
||||
});
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -386,12 +411,20 @@ void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
|
||||
if (!params.Parse(req))
|
||||
return;
|
||||
|
||||
MemCheck mc;
|
||||
if (!g_breakpoints.GetMemCheck(params.address, params.end, &mc))
|
||||
return req.Fail("Breakpoint not found");
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
bool found = false;
|
||||
Core_RunOnCPUThread([&] {
|
||||
MemCheck mc;
|
||||
found = g_breakpoints.GetMemCheck(params.address, params.end, &mc);
|
||||
if (found) {
|
||||
g_breakpoints.ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
|
||||
params.Apply();
|
||||
}
|
||||
});
|
||||
|
||||
g_breakpoints.ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
|
||||
params.Apply();
|
||||
if (!found)
|
||||
return req.Fail("Breakpoint not found");
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -414,7 +447,11 @@ void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
|
||||
if (!req.ParamU32("size", &size))
|
||||
return;
|
||||
|
||||
g_breakpoints.RemoveMemCheck(address, size == 0 ? 0 : address + size);
|
||||
// Route the actual breakpoint manipulation to the CPU thread instead of poking at it directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
g_breakpoints.RemoveMemCheck(address, size == 0 ? 0 : address + size);
|
||||
});
|
||||
req.Respond();
|
||||
}
|
||||
|
||||
@@ -440,34 +477,38 @@ void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
|
||||
return req.Fail("CPU not started");
|
||||
}
|
||||
|
||||
JsonWriter &json = req.Respond();
|
||||
json.pushArray("breakpoints");
|
||||
auto mcs = g_breakpoints.GetMemChecks();
|
||||
for (const auto &mc : mcs) {
|
||||
json.pushDict();
|
||||
json.writeUint("address", mc.start);
|
||||
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
|
||||
json.writeBool("enabled", mc.IsEnabled());
|
||||
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
|
||||
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
|
||||
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
|
||||
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
|
||||
json.writeUint("hits", mc.numHits);
|
||||
if (mc.hasCondition)
|
||||
json.writeString("condition", mc.condition.expressionString);
|
||||
else
|
||||
json.writeNull("condition");
|
||||
if (!mc.logFormat.empty())
|
||||
json.writeString("logFormat", mc.logFormat);
|
||||
else
|
||||
json.writeNull("logFormat");
|
||||
std::string symbol = g_symbolMap->GetLabelString(mc.start);
|
||||
if (symbol.empty())
|
||||
json.writeNull("symbol");
|
||||
else
|
||||
json.writeString("symbol", symbol);
|
||||
// Route the breakpoint/symbol reads to the CPU thread instead of poking at them directly
|
||||
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
||||
Core_RunOnCPUThread([&] {
|
||||
JsonWriter &json = req.Respond();
|
||||
json.pushArray("breakpoints");
|
||||
std::vector<MemCheck> mcs = g_breakpoints.GetMemChecks();
|
||||
for (const MemCheck &mc : mcs) {
|
||||
json.pushDict();
|
||||
json.writeUint("address", mc.start);
|
||||
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
|
||||
json.writeBool("enabled", mc.IsEnabled());
|
||||
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
|
||||
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
|
||||
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
|
||||
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
|
||||
json.writeUint("hits", mc.numHits);
|
||||
if (mc.hasCondition)
|
||||
json.writeString("condition", mc.condition.expressionString);
|
||||
else
|
||||
json.writeNull("condition");
|
||||
if (!mc.logFormat.empty())
|
||||
json.writeString("logFormat", mc.logFormat);
|
||||
else
|
||||
json.writeNull("logFormat");
|
||||
std::string symbol = g_symbolMap->GetLabelString(mc.start);
|
||||
if (symbol.empty())
|
||||
json.writeNull("symbol");
|
||||
else
|
||||
json.writeString("symbol", symbol);
|
||||
|
||||
json.pop();
|
||||
}
|
||||
json.pop();
|
||||
}
|
||||
json.pop();
|
||||
});
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
@@ -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<BranchLine> 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);
|
||||
}
|
||||
@@ -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<DebugThreadInfo> 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<DebugThreadInfo> 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<SymbolEntry> 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<LoadedModuleInfo> 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<DebugThreadInfo> 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<MIPSStackWalk::StackFrame> 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<SymbolEntry> 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);
|
||||
});
|
||||
}
|
||||
@@ -56,23 +56,18 @@ struct AutoDisabledReplacements {
|
||||
std::map<u32, u32> replacements;
|
||||
std::vector<u32> 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<uint8_t> value = Base64Decode(&encoded[0], encoded.size());
|
||||
uint32_t size = (uint32_t)value.size();
|
||||
std::vector<uint8_t> 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<uint8_t> needleBytes;
|
||||
std::vector<uint8_t> 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<uint8_t> needleBytes;
|
||||
std::vector<uint8_t> 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<uint32_t> 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<uint32_t> 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);
|
||||
});
|
||||
}
|
||||
@@ -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<DebugThreadInfo> 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<MIPSStackWalk::StackFrame> 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.
|
||||
|
||||
+20
-1
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -119,7 +119,6 @@ public:
|
||||
}
|
||||
|
||||
void scrollStepping(u32 newPc);
|
||||
u32 getInstructionSizeAt(u32 address);
|
||||
|
||||
void gotoAddr(unsigned int addr)
|
||||
{
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<std::string> System_GetPropertyStringVec(SystemProperty prop) { return std::vector<std::string>(); }
|
||||
int64_t System_GetPropertyInt(SystemProperty prop) {
|
||||
|
||||
Reference in new issue
Block a user