mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
491 lines
15 KiB
C++
491 lines
15 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 "base/stringutil.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/Core.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/MIPS/MIPSAnalyst.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/MIPS/MIPSStackWalk.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
|
|
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.rename"] = &WebSocketHLEFuncRename;
|
|
map["hle.module.list"] = &WebSocketHLEModuleList;
|
|
map["hle.backtrace"] = &WebSocketHLEBacktrace;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// 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.
|
|
// - initialStackSize: unsigned integer, size of initial 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) {
|
|
// Will just return none of the CPU isn't ready yet.
|
|
auto threads = GetThreadsInfo();
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.pushArray("threads");
|
|
for (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");
|
|
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) {
|
|
if (!PSP_IsInited()) {
|
|
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;
|
|
|
|
auto threads = GetThreadsInfo();
|
|
for (auto 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) {
|
|
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;
|
|
|
|
default:
|
|
return req.Fail("Cannot force run thread based on current status");
|
|
}
|
|
|
|
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) {
|
|
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:
|
|
return req.Fail("Cannot force run thread based on current status");
|
|
}
|
|
|
|
// Get it again to verify.
|
|
if (!ThreadInfoForStatus(req, &threadInfo))
|
|
return;
|
|
if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)
|
|
return req.Fail("Failed to stop thread");
|
|
|
|
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");
|
|
|
|
auto functions = g_symbolMap->GetAllSymbols(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);
|
|
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;
|
|
|
|
std::string name;
|
|
if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))
|
|
return;
|
|
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 - 1);
|
|
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;
|
|
|
|
// 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 - 1);
|
|
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 - 1);
|
|
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.
|
|
DisassemblyManager manager;
|
|
manager.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;
|
|
|
|
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
|
|
if (funcBegin == -1)
|
|
return req.Fail("No function found at 'address'");
|
|
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 - 1);
|
|
MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str());
|
|
} else {
|
|
MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);
|
|
}
|
|
|
|
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.
|
|
DisassemblyManager manager;
|
|
manager.clear();
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.writeUint("address", funcBegin);
|
|
json.writeUint("size", funcSize);
|
|
}
|
|
|
|
// 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 removed function.
|
|
// - size: the size in bytes of the removed 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;
|
|
|
|
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
|
|
if (funcBegin == -1)
|
|
return req.Fail("No function found at 'address'");
|
|
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 - 1);
|
|
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);
|
|
}
|
|
|
|
// 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");
|
|
|
|
auto 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);
|
|
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)");
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
DisassemblyManager manager;
|
|
DisassemblyLineInfo line;
|
|
manager.getLine(manager.getStartAddress(f.pc), true, line, cpuDebug);
|
|
json.writeString("code", line.name + " " + line.params);
|
|
|
|
json.pop();
|
|
}
|
|
json.pop();
|
|
}
|