Files
ppsspp/Core/Debugger/WebSocket/HLESubscriber.cpp
T
Henrik RydgårdandClaude Opus 5 d4500d9353 hle.data.add: apply the requested name, and report the real one
AddLabel() won't overwrite an existing label. That's deliberate and right for
bulk import - a real ELF symbol name shouldn't lose to the analyzer's later
z_un_* - but wrong when someone is explicitly naming an address, so a second
hle.data.add at the same address silently kept the old name. The response echoed
the requested name back either way, so there was no sign anything had been
ignored.

Force the requested name in with SetLabelName() now, except when a function
starts at that address and owns the label - renaming that function isn't what
"label this data" should mean, and it would undo the care hle.data.remove takes
not to destroy it. Either way the response now reports the name the symbol
actually ended up with rather than the one that was asked for.

Also, in the ImDebugger memcheck edit form: the Enabled checkbox didn't mark the
memcheck as changed, and the condition combo marked it changed on every frame
the popup was open rather than when a condition was actually picked (Selectable
returns true only on click, BeginCombo stays true while open).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 00:28:26 +02:00

927 lines
31 KiB
C++

// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/StringUtils.h"
#include "Common/Math/math_util.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSStackWalk.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/Reporting.h"
// General note: Function addresses will be snapped appropriately to full instructions
// if they are not divisible by four. Addresses downwards, sizes upwards.
// It's recommended to use correctly aligned addresses instead.
DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
map["hle.thread.list"] = &WebSocketHLEThreadList;
map["hle.thread.wake"] = &WebSocketHLEThreadWake;
map["hle.thread.stop"] = &WebSocketHLEThreadStop;
map["hle.func.list"] = &WebSocketHLEFuncList;
map["hle.func.add"] = &WebSocketHLEFuncAdd;
map["hle.func.remove"] = &WebSocketHLEFuncRemove;
map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;
map["hle.func.rename"] = &WebSocketHLEFuncRename;
map["hle.func.scan"] = &WebSocketHLEFuncScan;
map["hle.module.list"] = &WebSocketHLEModuleList;
map["hle.backtrace"] = &WebSocketHLEBacktrace;
map["hle.data.list"] = &WebSocketHLEDataList;
map["hle.data.add"] = &WebSocketHLEDataAdd;
map["hle.data.remove"] = &WebSocketHLEDataRemove;
map["hle.data.rename"] = &WebSocketHLEDataRename;
return nullptr;
}
static const char *DataTypeToString(DataType type) {
switch (type) {
case DATATYPE_BYTE: return "byte";
case DATATYPE_HALFWORD: return "halfword";
case DATATYPE_WORD: return "word";
case DATATYPE_ASCII: return "ascii";
default: return "unknown";
}
}
static bool DataTypeFromString(const std::string &s, DataType *out) {
if (s == "byte") {
*out = DATATYPE_BYTE;
} else if (s == "halfword") {
*out = DATATYPE_HALFWORD;
} else if (s == "word") {
*out = DATATYPE_WORD;
} else if (s == "ascii") {
*out = DATATYPE_ASCII;
} else {
return false;
}
return true;
}
// List all current HLE threads (hle.thread.list)
//
// No parameters.
//
// Response (same event name):
// - threads: array of objects, each with properties:
// - id: unsigned integer unique id of thread.
// - name: name given to thread when created.
// - status: numeric status flags of thread.
// - statuses: array of string status names, e.g. 'running'. Typically only one set.
// - pc: unsigned integer address of next instruction on thread.
// - entry: unsigned integer address thread execution started at.
// - initialStack: unsigned integer, address of the base of the thread's stack.
// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)
// - priority: numeric priority level, lower values are better priority.
// - 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) {
// 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 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);
// Named after the SceKernelThreadInfo field it comes from - it's the stack base
// address, not a size, despite what this used to be called.
json.writeUint("initialStack", 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) {
if (PSP_GetBootState() != BootState::Complete) {
req.Fail("CPU not active");
return false;
}
if (!Core_IsStepping()) {
req.Fail("CPU currently running (cpu.stepping first)");
return false;
}
uint32_t threadID;
if (!req.ParamU32("thread", &threadID))
return false;
std::vector<DebugThreadInfo> threads = GetThreadsInfo();
for (const DebugThreadInfo &t : threads) {
if (t.id == threadID) {
*result = t;
return true;
}
}
req.Fail("Thread could not be found");
return false;
}
// Force resume a thread (hle.thread.wake)
//
// Parameters:
// - thread: number indicating the thread id to resume.
//
// Response (same event name):
// - thread: id repeated back.
// - status: string 'ready'.
void WebSocketHLEThreadWake(DebuggerRequest &req) {
// 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) {
req.Fail("Failed to resume thread");
return;
}
break;
default:
req.Fail("Cannot force run thread based on current status");
return;
}
Reporting::NotifyDebugger();
JsonWriter &json = req.Respond();
json.writeUint("thread", threadInfo.id);
json.writeString("status", "ready");
});
}
// Force stop a thread (hle.thread.stop)
//
// Parameters:
// - thread: number indicating the thread id to stop.
//
// Response (same event name):
// - thread: id repeated back.
// - status: string 'dormant'.
void WebSocketHLEThreadStop(DebuggerRequest &req) {
// 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;
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) {
req.Fail("Failed to stop thread");
return;
}
Reporting::NotifyDebugger();
JsonWriter &json = req.Respond();
json.writeUint("thread", threadInfo.id);
json.writeString("status", "dormant");
});
}
// List all current known function symbols (hle.func.list)
//
// No parameters.
//
// Response (same event name):
// - functions: array of objects, each with properties:
// - name: current name of function.
// - address: unsigned integer start address of function.
// - size: unsigned integer size in bytes.
void WebSocketHLEFuncList(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
// 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 (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();
});
}
// Add a new function symbols (hle.func.add)
//
// Parameters:
// - address: unsigned integer address for the start of the function.
// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,
// defaults to that function's end, otherwise 4 bytes.
// - name: string to name the function, optional and defaults to an auto-generated name.
//
// Response (same event name):
// - address: the start address, repeated back.
// - size: the size of the function, whether autodetected or not.
// - name: name of the new function.
//
// Note: will fail if a function starts at that location already, or if size spans multiple
// existing functions. Remove those functions first if necessary.
void WebSocketHLEFuncAdd(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
u32 size = -1;
if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))
return;
if (size == 0)
size = -1;
addr = RoundDownToMultipleOf(addr, 4);
size = RoundUpToMultipleOf(size, 4);
std::string name;
if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))
return;
if (name.empty())
name = StringFromFormat("z_un_%08x", addr);
// 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;
// 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());
MIPSAnalyst::UpdateHashMap();
MIPSAnalyst::ApplyHashMap();
if (g_Config.bFuncReplacements) {
MIPSAnalyst::ReplaceFunctions();
}
// 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);
});
}
// Remove a function symbol (hle.func.remove)
//
// Parameters:
// - address: unsigned integer address within function to remove.
//
// Response (same event name):
// - address: the start address of the removed function.
// - size: the size in bytes of the removed function.
//
// Note: will expand any previous function automatically.
void WebSocketHLEFuncRemove(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
addr = RoundDownToMultipleOf(addr, 4);
// 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);
}
g_symbolMap->RemoveFunction(funcBegin, true);
g_symbolMap->SortSymbols();
MIPSAnalyst::UpdateHashMap();
MIPSAnalyst::ApplyHashMap();
if (g_Config.bFuncReplacements) {
MIPSAnalyst::ReplaceFunctions();
}
// Clear cache for branch lines and such.
g_disassemblyManager.clear();
JsonWriter &json = req.Respond();
json.writeUint("address", funcBegin);
json.writeUint("size", funcSize);
});
}
// This function removes function symbols that intersect or lie inside the range
// (Note: this makes no checks whether the range is valid)
// Returns the number of removed functions
static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {
u32 func_address = g_symbolMap->GetFunctionStart(addr);
if (func_address == SymbolMap::INVALID_ADDRESS) {
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
}
u32 counter = 0;
while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {
g_symbolMap->RemoveFunction(func_address, true);
++counter;
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
}
if (counter) {
MIPSAnalyst::ForgetFunctions(addr, addr + size);
// The following was copied from hle.func.remove:
g_symbolMap->SortSymbols();
MIPSAnalyst::UpdateHashMap();
MIPSAnalyst::ApplyHashMap();
if (g_Config.bFuncReplacements) {
MIPSAnalyst::ReplaceFunctions();
}
// Clear cache for branch lines and such.
g_disassemblyManager.clear();
}
return counter;
}
// Remove function symbols in range (hle.func.removeRange)
//
// Parameters:
// - address: unsigned integer address for the start of the range.
// - size: unsigned integer size in bytes for removal
//
// Response (same event name):
// - count: number of removed functions
void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
u32 size;
if (!req.ParamU32("size", &size))
return;
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");
// 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);
});
}
// Rename a function symbol (hle.func.rename)
//
// Parameters:
// - address: unsigned integer address within function to rename.
// - name: string, new name for the function.
//
// Response (same event name):
// - address: the start address of the renamed function.
// - size: the size in bytes of the renamed function.
// - name: string, new name repeated back.
void WebSocketHLEFuncRename(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
std::string name;
if (!req.ParamString("name", &name))
return;
addr = RoundDownToMultipleOf(addr, 4);
// 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();
}
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)
//
// Parameters:
// - address: unsigned integer address for the start of the range.
// - size: unsigned integer size in bytes for scan.
// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning
//
// Response (same event name) with no extra data.
void WebSocketHLEFuncScan(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
u32 size;
if (!req.ParamU32("size", &size))
return;
addr = RoundDownToMultipleOf(addr, 4);
size = RoundUpToMultipleOf(size, 4);
bool remove = false;
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");
// 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([&] {
if (remove) {
RemoveFuncSymbolsInRange(addr, size);
}
bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true);
MIPSAnalyst::FinalizeScan(insertSymbols);
req.Respond();
});
}
// List all known user modules (hle.module.list)
//
// No parameters.
//
// Response (same event name):
// - modules: array of objects, each with properties:
// - name: name of module when loaded.
// - address: unsigned integer start address.
// - size: unsigned integer size in bytes.
// - isActive: boolean, true if this module is active.
void WebSocketHLEModuleList(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
// 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 (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();
});
}
// Walk the stack and list stack frames (hle.backtrace)
//
// Parameters:
// - thread: optional number indicating the thread id to backtrace, default current.
//
// Response (same event name):
// - frames: array of objects, each with properties:
// - entry: unsigned integer address of function start (may be estimated.)
// - pc: unsigned integer next execution address.
// - sp: unsigned integer stack address in this func (beware of alloca().)
// - stackSize: integer size of stack frame.
// - code: string disassembly of pc.
void WebSocketHLEBacktrace(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
// 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) {
req.Fail("Thread could not be found");
return;
}
}
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);
std::vector<MIPSStackWalk::StackFrame> frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
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();
});
}
// List all current known data symbols (hle.data.list)
//
// No parameters.
//
// Response (same event name):
// - data: array of objects, each with properties:
// - name: current name of the data symbol.
// - address: unsigned integer start address.
// - size: unsigned integer size in bytes.
// - type: string, one of 'byte', 'halfword', 'word', 'ascii', or 'unknown'.
void WebSocketHLEDataList(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
// 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 (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();
});
}
// Add a new data symbol (hle.data.add)
//
// Useful for labeling structures, tables, or buffers found while reverse engineering
// (e.g. after locating something with memory.search.)
//
// Parameters:
// - address: unsigned integer address for the start of the data.
// - size: unsigned integer size in bytes.
// - type: string, one of 'byte', 'halfword', 'word', or 'ascii'.
// - name: string to name the data, optional and defaults to an auto-generated name.
//
// Response (same event name):
// - address: the start address, repeated back.
// - size: the size, repeated back.
// - type: the type, repeated back.
// - name: name the data symbol actually ended up with. Normally the requested one, but a
// function starting at this address keeps its own name (labels are shared between the two),
// so check this rather than assuming the request was applied verbatim.
void WebSocketHLEDataAdd(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
u32 size;
if (!req.ParamU32("size", &size))
return;
if (size == 0)
return req.Fail("'size' must not be zero");
std::string typeStr;
if (!req.ParamString("type", &typeStr))
return;
DataType type;
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");
std::string name;
if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))
return;
if (name.empty())
name = StringFromFormat("data_%08x", addr);
// 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([&] {
// GetModuleIndex() returns -1 for an address that isn't inside any loaded module, and
// symbols added under that index never make it into the "active" maps - so they'd silently
// vanish (invisible to hle.data.list, and not findable by remove/rename). Module index 0
// means "no module, absolute address", which is exactly what we want for a label the user
// put on the heap, the stack, or scratchpad after e.g. a memory.search.
int moduleIndex = g_symbolMap->GetModuleIndex(addr);
if (moduleIndex < 0)
moduleIndex = 0;
// Labels are intentionally a single namespace shared by function and data symbols, and AddLabel() won't
// overwrite an existing one - a real ELF symbol name shouldn't lose to the analyzer's later z_un_*.
// However it's wrong here, where someone is explicitly naming this address.
// So force the requested name in afterwards, unless a function starts here and owns the label:
// silently renaming that function isn't what "label this data" should do, and it would undo the same care taken in hle.data.remove.
const bool functionOwnsLabel = g_symbolMap->GetFunctionStart(addr) == addr;
g_symbolMap->AddData(addr, size, type, moduleIndex);
g_symbolMap->AddLabel(name.c_str(), addr, moduleIndex);
if (!functionOwnsLabel)
g_symbolMap->SetLabelName(name.c_str(), addr);
g_symbolMap->SortSymbols();
// Clear cache so the disassembly view picks up the new annotation.
g_disassemblyManager.clear();
// Report the name that actually ended up in the map, not the one that was asked for.
const std::string actualName = g_symbolMap->GetLabelString(addr);
JsonWriter &json = req.Respond();
json.writeUint("address", addr);
json.writeUint("size", size);
json.writeString("type", typeStr);
json.writeString("name", actualName.empty() ? name : actualName);
});
}
// Remove a data symbol (hle.data.remove)
//
// Parameters:
// - address: unsigned integer address within the data symbol to remove.
//
// Response (same event name):
// - address: the start address of the removed data symbol.
// - size: the size in bytes of the removed data symbol.
void WebSocketHLEDataRemove(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
// 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);
// Labels are shared between data and function symbols, so dropping the label along with the
// data would also wipe the name of a function starting at the same address (leaving it
// showing up in hle.func.list with an empty name). Only take the label with us if no
// function is using it too.
const bool functionOwnsLabel = g_symbolMap->GetFunctionStart(dataBegin) == dataBegin;
g_symbolMap->RemoveData(dataBegin, !functionOwnsLabel);
g_symbolMap->SortSymbols();
g_disassemblyManager.clear();
JsonWriter &json = req.Respond();
json.writeUint("address", dataBegin);
json.writeUint("size", dataSize);
});
}
// Rename a data symbol (hle.data.rename)
//
// Parameters:
// - address: unsigned integer address within the data symbol to rename.
// - name: string, new name for the data symbol.
//
// Response (same event name):
// - address: the start address of the renamed data symbol.
// - size: the size in bytes of the renamed data symbol.
// - name: string, new name repeated back.
void WebSocketHLEDataRename(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
u32 addr;
if (!req.ParamU32("address", &addr))
return;
std::string name;
if (!req.ParamString("name", &name))
return;
// 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);
JsonWriter &json = req.Respond();
json.writeUint("address", dataBegin);
json.writeUint("size", dataSize);
json.writeString("name", name);
});
}