Path: blob/master/Core/Debugger/WebSocket/HLESubscriber.cpp
3187 views
// Copyright (c) 2018- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/StringUtils.h"18#include "Core/Config.h"19#include "Core/Core.h"20#include "Core/System.h"21#include "Core/Debugger/DisassemblyManager.h"22#include "Core/Debugger/SymbolMap.h"23#include "Core/Debugger/WebSocket/HLESubscriber.h"24#include "Core/Debugger/WebSocket/WebSocketUtils.h"25#include "Core/MemMap.h"26#include "Core/MIPS/MIPSAnalyst.h"27#include "Core/MIPS/MIPSDebugInterface.h"28#include "Core/MIPS/MIPSStackWalk.h"29#include "Core/HLE/sceKernelThread.h"30#include "Core/Reporting.h"3132DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {33map["hle.thread.list"] = &WebSocketHLEThreadList;34map["hle.thread.wake"] = &WebSocketHLEThreadWake;35map["hle.thread.stop"] = &WebSocketHLEThreadStop;36map["hle.func.list"] = &WebSocketHLEFuncList;37map["hle.func.add"] = &WebSocketHLEFuncAdd;38map["hle.func.remove"] = &WebSocketHLEFuncRemove;39map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;40map["hle.func.rename"] = &WebSocketHLEFuncRename;41map["hle.func.scan"] = &WebSocketHLEFuncScan;42map["hle.module.list"] = &WebSocketHLEModuleList;43map["hle.backtrace"] = &WebSocketHLEBacktrace;4445return nullptr;46}4748// List all current HLE threads (hle.thread.list)49//50// No parameters.51//52// Response (same event name):53// - threads: array of objects, each with properties:54// - id: unsigned integer unique id of thread.55// - name: name given to thread when created.56// - status: numeric status flags of thread.57// - statuses: array of string status names, e.g. 'running'. Typically only one set.58// - pc: unsigned integer address of next instruction on thread.59// - entry: unsigned integer address thread execution started at.60// - initialStackSize: unsigned integer, size of initial stack.61// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)62// - priority: numeric priority level, lower values are better priority.63// - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting.64// - isCurrent: boolean, true for the currently executing thread.65void WebSocketHLEThreadList(DebuggerRequest &req) {66// Will just return none of the CPU isn't ready yet.67auto threads = GetThreadsInfo();6869JsonWriter &json = req.Respond();70json.pushArray("threads");71for (const auto &th : threads) {72json.pushDict();73json.writeUint("id", th.id);74json.writeString("name", th.name);75json.writeInt("status", th.status);76json.pushArray("statuses");77if (th.status & THREADSTATUS_RUNNING)78json.writeString("running");79if (th.status & THREADSTATUS_READY)80json.writeString("ready");81if (th.status & THREADSTATUS_WAIT)82json.writeString("wait");83if (th.status & THREADSTATUS_SUSPEND)84json.writeString("suspend");85if (th.status & THREADSTATUS_DORMANT)86json.writeString("dormant");87if (th.status & THREADSTATUS_DEAD)88json.writeString("dead");89json.pop();90json.writeUint("pc", th.curPC);91json.writeUint("entry", th.entrypoint);92json.writeUint("initialStackSize", th.initialStack);93json.writeUint("currentStackSize", th.stackSize);94json.writeInt("priority", th.priority);95json.writeInt("waitType", (int)th.waitType);96json.writeBool("isCurrent", th.isCurrent);97json.pop();98}99json.pop();100}101102static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) {103if (PSP_GetBootState() != BootState::Complete) {104req.Fail("CPU not active");105return false;106}107if (!Core_IsStepping()) {108req.Fail("CPU currently running (cpu.stepping first)");109return false;110}111112uint32_t threadID;113if (!req.ParamU32("thread", &threadID))114return false;115116auto threads = GetThreadsInfo();117for (const auto &t : threads) {118if (t.id == threadID) {119*result = t;120return true;121}122}123124req.Fail("Thread could not be found");125return false;126}127128// Force resume a thread (hle.thread.wake)129//130// Parameters:131// - thread: number indicating the thread id to resume.132//133// Response (same event name):134// - thread: id repeated back.135// - status: string 'ready'.136void WebSocketHLEThreadWake(DebuggerRequest &req) {137DebugThreadInfo threadInfo{ -1 };138if (!ThreadInfoForStatus(req, &threadInfo))139return;140141switch (threadInfo.status) {142case THREADSTATUS_SUSPEND:143case THREADSTATUS_WAIT:144case THREADSTATUS_WAITSUSPEND:145if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0)146return req.Fail("Failed to resume thread");147break;148149default:150return req.Fail("Cannot force run thread based on current status");151}152153Reporting::NotifyDebugger();154155JsonWriter &json = req.Respond();156json.writeUint("thread", threadInfo.id);157json.writeString("status", "ready");158}159160// Force stop a thread (hle.thread.stop)161//162// Parameters:163// - thread: number indicating the thread id to stop.164//165// Response (same event name):166// - thread: id repeated back.167// - status: string 'dormant'.168void WebSocketHLEThreadStop(DebuggerRequest &req) {169DebugThreadInfo threadInfo{ -1 };170if (!ThreadInfoForStatus(req, &threadInfo))171return;172173switch (threadInfo.status) {174case THREADSTATUS_SUSPEND:175case THREADSTATUS_WAIT:176case THREADSTATUS_WAITSUSPEND:177case THREADSTATUS_READY:178__KernelStopThread(threadInfo.id, 0, "stopped from debugger");179break;180181default:182return req.Fail("Cannot force run thread based on current status");183}184185// Get it again to verify.186if (!ThreadInfoForStatus(req, &threadInfo))187return;188if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)189return req.Fail("Failed to stop thread");190191Reporting::NotifyDebugger();192193JsonWriter &json = req.Respond();194json.writeUint("thread", threadInfo.id);195json.writeString("status", "dormant");196}197198// List all current known function symbols (hle.func.list)199//200// No parameters.201//202// Response (same event name):203// - functions: array of objects, each with properties:204// - name: current name of function.205// - address: unsigned integer start address of function.206// - size: unsigned integer size in bytes.207void WebSocketHLEFuncList(DebuggerRequest &req) {208if (!g_symbolMap)209return req.Fail("CPU not active");210211auto functions = g_symbolMap->GetAllActiveSymbols(ST_FUNCTION);212213JsonWriter &json = req.Respond();214json.pushArray("functions");215for (auto f : functions) {216json.pushDict();217json.writeString("name", f.name);218json.writeUint("address", f.address);219json.writeUint("size", f.size);220json.pop();221}222json.pop();223}224225// Add a new function symbols (hle.func.add)226//227// Parameters:228// - address: unsigned integer address for the start of the function.229// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,230// defaults to that function's end, otherwise 4 bytes.231// - name: string to name the function, optional and defaults to an auto-generated name.232//233// Response (same event name):234// - address: the start address, repeated back.235// - size: the size of the function, whether autodetected or not.236// - name: name of the new function.237//238// Note: will fail if a function starts at that location already, or if size spans multiple239// existing functions. Remove those functions first if necessary.240void WebSocketHLEFuncAdd(DebuggerRequest &req) {241if (!g_symbolMap)242return req.Fail("CPU not active");243if (!Core_IsStepping())244return req.Fail("CPU currently running (cpu.stepping first)");245246u32 addr;247if (!req.ParamU32("address", &addr))248return;249u32 size = -1;250if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))251return;252if (size == 0)253size = -1;254255std::string name;256if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))257return;258if (name.empty())259name = StringFromFormat("z_un_%08x", addr);260261u32 prevBegin = g_symbolMap->GetFunctionStart(addr);262u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 1);263if (prevBegin == addr) {264return req.Fail("Function already exists at 'address'");265} else if (endBegin != prevBegin) {266return req.Fail("Function already exists between 'address' and 'address' + 'size'");267} else if (prevBegin != -1) {268std::string prevName = g_symbolMap->GetLabelString(prevBegin);269u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);270u32 newPrevSize = addr - prevBegin;271272// The new function will be the remainder, unless otherwise specified.273if (size == -1)274size = prevSize - newPrevSize;275276// Make sure we register the new length for replacements too.277MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize - 1);278g_symbolMap->SetFunctionSize(prevBegin, newPrevSize);279MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str());280} else {281// There was no function there, so hopefully they specified a size.282if (size == -1)283size = 4;284}285286// To ensure we restore replacements.287MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);288g_symbolMap->AddFunction(name.c_str(), addr, size);289g_symbolMap->SortSymbols();290MIPSAnalyst::RegisterFunction(addr, size, name.c_str());291292MIPSAnalyst::UpdateHashMap();293MIPSAnalyst::ApplyHashMap();294295if (g_Config.bFuncReplacements) {296MIPSAnalyst::ReplaceFunctions();297}298299// Clear cache for branch lines and such.300g_disassemblyManager.clear();301302JsonWriter &json = req.Respond();303json.writeUint("address", addr);304json.writeUint("size", size);305json.writeString("name", name);306}307308// Remove a function symbol (hle.func.remove)309//310// Parameters:311// - address: unsigned integer address within function to remove.312//313// Response (same event name):314// - address: the start address of the removed function.315// - size: the size in bytes of the removed function.316//317// Note: will expand any previous function automatically.318void WebSocketHLEFuncRemove(DebuggerRequest &req) {319if (!g_symbolMap)320return req.Fail("CPU not active");321if (!Core_IsStepping())322return req.Fail("CPU currently running (cpu.stepping first)");323324u32 addr;325if (!req.ParamU32("address", &addr))326return;327328u32 funcBegin = g_symbolMap->GetFunctionStart(addr);329if (funcBegin == -1)330return req.Fail("No function found at 'address'");331u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);332333// Expand the previous function.334u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1);335if (prevBegin != -1) {336std::string prevName = g_symbolMap->GetLabelString(prevBegin);337u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize;338g_symbolMap->SetFunctionSize(prevBegin, expandedSize);339MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize - 1);340MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str());341} else {342MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);343}344345g_symbolMap->RemoveFunction(funcBegin, true);346g_symbolMap->SortSymbols();347348MIPSAnalyst::UpdateHashMap();349MIPSAnalyst::ApplyHashMap();350351if (g_Config.bFuncReplacements) {352MIPSAnalyst::ReplaceFunctions();353}354355// Clear cache for branch lines and such.356g_disassemblyManager.clear();357358JsonWriter &json = req.Respond();359json.writeUint("address", funcBegin);360json.writeUint("size", funcSize);361}362363// This function removes function symbols that intersect or lie inside the range364// (Note: this makes no checks whether the range is valid)365// Returns the number of removed functions366static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {367u32 func_address = g_symbolMap->GetFunctionStart(addr);368if (func_address == SymbolMap::INVALID_ADDRESS) {369func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);370}371372u32 counter = 0;373while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {374g_symbolMap->RemoveFunction(func_address, true);375++counter;376func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);377}378379if (counter) {380MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);381382// The following was copied from hle.func.remove:383g_symbolMap->SortSymbols();384385MIPSAnalyst::UpdateHashMap();386MIPSAnalyst::ApplyHashMap();387388if (g_Config.bFuncReplacements) {389MIPSAnalyst::ReplaceFunctions();390}391392// Clear cache for branch lines and such.393g_disassemblyManager.clear();394}395return counter;396}397398// Remove function symbols in range (hle.func.removeRange)399//400// Parameters:401// - address: unsigned integer address for the start of the range.402// - size: unsigned integer size in bytes for removal403//404// Response (same event name):405// - count: number of removed functions406void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {407if (!g_symbolMap)408return req.Fail("CPU not active");409if (!Core_IsStepping())410return req.Fail("CPU currently running (cpu.stepping first)");411412u32 addr;413if (!req.ParamU32("address", &addr))414return;415u32 size;416if (!req.ParamU32("size", &size))417return;418419if (!Memory::IsValidRange(addr, size))420return req.Fail("Address or size outside valid memory");421422u32 count = RemoveFuncSymbolsInRange(addr, size);423424JsonWriter &json = req.Respond();425json.writeUint("count", count);426}427428// Rename a function symbol (hle.func.rename)429//430// Parameters:431// - address: unsigned integer address within function to rename.432// - name: string, new name for the function.433//434// Response (same event name):435// - address: the start address of the renamed function.436// - size: the size in bytes of the renamed function.437// - name: string, new name repeated back.438void WebSocketHLEFuncRename(DebuggerRequest &req) {439if (!g_symbolMap)440return req.Fail("CPU not active");441if (!Core_IsStepping())442return req.Fail("CPU currently running (cpu.stepping first)");443444u32 addr;445if (!req.ParamU32("address", &addr))446return;447std::string name;448if (!req.ParamString("name", &name))449return;450451u32 funcBegin = g_symbolMap->GetFunctionStart(addr);452if (funcBegin == -1)453return req.Fail("No function found at 'address'");454u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);455456g_symbolMap->SetLabelName(name.c_str(), funcBegin);457// To ensure we reapply replacements (in case we check name there.)458MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);459MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str());460MIPSAnalyst::UpdateHashMap();461MIPSAnalyst::ApplyHashMap();462if (g_Config.bFuncReplacements) {463MIPSAnalyst::ReplaceFunctions();464}465466JsonWriter &json = req.Respond();467json.writeUint("address", funcBegin);468json.writeUint("size", funcSize);469json.writeString("name", name);470}471472// Auto-detect functions in a memory range (hle.func.scan)473//474// Parameters:475// - address: unsigned integer address for the start of the range.476// - size: unsigned integer size in bytes for scan.477// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning478//479// Response (same event name) with no extra data.480void WebSocketHLEFuncScan(DebuggerRequest &req) {481if (!g_symbolMap)482return req.Fail("CPU not active");483if (!Core_IsStepping())484return req.Fail("CPU currently running (cpu.stepping first)");485486u32 addr;487if (!req.ParamU32("address", &addr))488return;489u32 size;490if (!req.ParamU32("size", &size))491return;492493bool remove = false;494if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL))495return;496497if (!Memory::IsValidRange(addr, size))498return req.Fail("Address or size outside valid memory");499500if (remove) {501RemoveFuncSymbolsInRange(addr, size);502}503504bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size - 1, true);505MIPSAnalyst::FinalizeScan(insertSymbols);506507req.Respond();508}509510// List all known user modules (hle.module.list)511//512// No parameters.513//514// Response (same event name):515// - modules: array of objects, each with properties:516// - name: name of module when loaded.517// - address: unsigned integer start address.518// - size: unsigned integer size in bytes.519// - isActive: boolean, true if this module is active.520void WebSocketHLEModuleList(DebuggerRequest &req) {521if (!g_symbolMap)522return req.Fail("CPU not active");523524auto modules = g_symbolMap->getAllModules();525526JsonWriter &json = req.Respond();527json.pushArray("modules");528for (auto m : modules) {529json.pushDict();530json.writeString("name", m.name);531json.writeUint("address", m.address);532json.writeUint("size", m.size);533json.writeBool("isActive", m.active);534json.pop();535}536json.pop();537}538539// Walk the stack and list stack frames (hle.backtrace)540//541// Parameters:542// - thread: optional number indicating the thread id to backtrace, default current.543//544// Response (same event name):545// - frames: array of objects, each with properties:546// - entry: unsigned integer address of function start (may be estimated.)547// - pc: unsigned integer next execution address.548// - sp: unsigned integer stack address in this func (beware of alloca().)549// - stackSize: integer size of stack frame.550// - code: string disassembly of pc.551void WebSocketHLEBacktrace(DebuggerRequest &req) {552if (!g_symbolMap)553return req.Fail("CPU not active");554if (!Core_IsStepping())555return req.Fail("CPU currently running (cpu.stepping first)");556557uint32_t threadID = -1;558DebugInterface *cpuDebug = currentDebugMIPS;559if (req.HasParam("thread")) {560if (!req.ParamU32("thread", &threadID))561return;562563cpuDebug = KernelDebugThread((SceUID)threadID);564if (!cpuDebug)565return req.Fail("Thread could not be found");566}567568auto threads = GetThreadsInfo();569uint32_t entry = cpuDebug->GetPC();570uint32_t stackTop = 0;571for (const DebugThreadInfo &th : threads) {572if ((threadID == -1 && th.isCurrent) || th.id == threadID) {573entry = th.entrypoint;574stackTop = th.initialStack;575break;576}577}578579uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);580uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);581auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);582583JsonWriter &json = req.Respond();584json.pushArray("frames");585for (auto f : frames) {586json.pushDict();587json.writeUint("entry", f.entry);588json.writeUint("pc", f.pc);589json.writeUint("sp", f.sp);590json.writeUint("stackSize", f.stackSize);591592DisassemblyLineInfo line;593g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug);594json.writeString("code", line.name + " " + line.params);595596json.pop();597}598json.pop();599}600601602