331 lines
11 KiB
C++
331 lines
11 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/HLE/sceKernelThread.h"
|
|
|
|
void *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
|
|
map["hle.thread.list"] = &WebSocketHLEThreadList;
|
|
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;
|
|
|
|
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();
|
|
}
|
|
|
|
// 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();
|
|
}
|