Path: blob/master/Core/Debugger/WebSocket/HLESubscriber.cpp
5669 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 "Common/Math/math_util.h"19#include "Core/Config.h"20#include "Core/Core.h"21#include "Core/System.h"22#include "Core/Debugger/DisassemblyManager.h"23#include "Core/Debugger/SymbolMap.h"24#include "Core/Debugger/WebSocket/HLESubscriber.h"25#include "Core/Debugger/WebSocket/WebSocketUtils.h"26#include "Core/MemMap.h"27#include "Core/MIPS/MIPSAnalyst.h"28#include "Core/MIPS/MIPSDebugInterface.h"29#include "Core/MIPS/MIPSStackWalk.h"30#include "Core/HLE/sceKernelThread.h"31#include "Core/Reporting.h"3233// General note: Function addresses will be snapped appropriately to full instructions34// if they are not divisible by four. Addresses downwards, sizes upwards.35// It's recommended to use correctly aligned addresses instead.3637DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {38map["hle.thread.list"] = &WebSocketHLEThreadList;39map["hle.thread.wake"] = &WebSocketHLEThreadWake;40map["hle.thread.stop"] = &WebSocketHLEThreadStop;41map["hle.func.list"] = &WebSocketHLEFuncList;42map["hle.func.add"] = &WebSocketHLEFuncAdd;43map["hle.func.remove"] = &WebSocketHLEFuncRemove;44map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;45map["hle.func.rename"] = &WebSocketHLEFuncRename;46map["hle.func.scan"] = &WebSocketHLEFuncScan;47map["hle.module.list"] = &WebSocketHLEModuleList;48map["hle.backtrace"] = &WebSocketHLEBacktrace;4950return nullptr;51}5253// List all current HLE threads (hle.thread.list)54//55// No parameters.56//57// Response (same event name):58// - threads: array of objects, each with properties:59// - id: unsigned integer unique id of thread.60// - name: name given to thread when created.61// - status: numeric status flags of thread.62// - statuses: array of string status names, e.g. 'running'. Typically only one set.63// - pc: unsigned integer address of next instruction on thread.64// - entry: unsigned integer address thread execution started at.65// - initialStackSize: unsigned integer, size of initial stack.66// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)67// - priority: numeric priority level, lower values are better priority.68// - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting.69// - isCurrent: boolean, true for the currently executing thread.70void WebSocketHLEThreadList(DebuggerRequest &req) {71// Will just return none of the CPU isn't ready yet.72auto threads = GetThreadsInfo();7374JsonWriter &json = req.Respond();75json.pushArray("threads");76for (const auto &th : threads) {77json.pushDict();78json.writeUint("id", th.id);79json.writeString("name", th.name);80json.writeInt("status", th.status);81json.pushArray("statuses");82if (th.status & THREADSTATUS_RUNNING)83json.writeString("running");84if (th.status & THREADSTATUS_READY)85json.writeString("ready");86if (th.status & THREADSTATUS_WAIT)87json.writeString("wait");88if (th.status & THREADSTATUS_SUSPEND)89json.writeString("suspend");90if (th.status & THREADSTATUS_DORMANT)91json.writeString("dormant");92if (th.status & THREADSTATUS_DEAD)93json.writeString("dead");94json.pop();95json.writeUint("pc", th.curPC);96json.writeUint("entry", th.entrypoint);97json.writeUint("initialStackSize", th.initialStack);98json.writeUint("currentStackSize", th.stackSize);99json.writeInt("priority", th.priority);100json.writeInt("waitType", (int)th.waitType);101json.writeBool("isCurrent", th.isCurrent);102json.pop();103}104json.pop();105}106107static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) {108if (PSP_GetBootState() != BootState::Complete) {109req.Fail("CPU not active");110return false;111}112if (!Core_IsStepping()) {113req.Fail("CPU currently running (cpu.stepping first)");114return false;115}116117uint32_t threadID;118if (!req.ParamU32("thread", &threadID))119return false;120121auto threads = GetThreadsInfo();122for (const auto &t : threads) {123if (t.id == threadID) {124*result = t;125return true;126}127}128129req.Fail("Thread could not be found");130return false;131}132133// Force resume a thread (hle.thread.wake)134//135// Parameters:136// - thread: number indicating the thread id to resume.137//138// Response (same event name):139// - thread: id repeated back.140// - status: string 'ready'.141void WebSocketHLEThreadWake(DebuggerRequest &req) {142DebugThreadInfo threadInfo{ -1 };143if (!ThreadInfoForStatus(req, &threadInfo))144return;145146switch (threadInfo.status) {147case THREADSTATUS_SUSPEND:148case THREADSTATUS_WAIT:149case THREADSTATUS_WAITSUSPEND:150if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0)151return req.Fail("Failed to resume thread");152break;153154default:155return req.Fail("Cannot force run thread based on current status");156}157158Reporting::NotifyDebugger();159160JsonWriter &json = req.Respond();161json.writeUint("thread", threadInfo.id);162json.writeString("status", "ready");163}164165// Force stop a thread (hle.thread.stop)166//167// Parameters:168// - thread: number indicating the thread id to stop.169//170// Response (same event name):171// - thread: id repeated back.172// - status: string 'dormant'.173void WebSocketHLEThreadStop(DebuggerRequest &req) {174DebugThreadInfo threadInfo{ -1 };175if (!ThreadInfoForStatus(req, &threadInfo))176return;177178switch (threadInfo.status) {179case THREADSTATUS_SUSPEND:180case THREADSTATUS_WAIT:181case THREADSTATUS_WAITSUSPEND:182case THREADSTATUS_READY:183__KernelStopThread(threadInfo.id, 0, "stopped from debugger");184break;185186default:187return req.Fail("Cannot force stop thread based on current status");188}189190// Get it again to verify.191if (!ThreadInfoForStatus(req, &threadInfo))192return;193if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)194return req.Fail("Failed to stop thread");195196Reporting::NotifyDebugger();197198JsonWriter &json = req.Respond();199json.writeUint("thread", threadInfo.id);200json.writeString("status", "dormant");201}202203// List all current known function symbols (hle.func.list)204//205// No parameters.206//207// Response (same event name):208// - functions: array of objects, each with properties:209// - name: current name of function.210// - address: unsigned integer start address of function.211// - size: unsigned integer size in bytes.212void WebSocketHLEFuncList(DebuggerRequest &req) {213if (!g_symbolMap)214return req.Fail("CPU not active");215216auto functions = g_symbolMap->GetAllActiveSymbols(ST_FUNCTION);217218JsonWriter &json = req.Respond();219json.pushArray("functions");220for (auto f : functions) {221json.pushDict();222json.writeString("name", f.name);223json.writeUint("address", f.address);224json.writeUint("size", f.size);225json.pop();226}227json.pop();228}229230// Add a new function symbols (hle.func.add)231//232// Parameters:233// - address: unsigned integer address for the start of the function.234// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,235// defaults to that function's end, otherwise 4 bytes.236// - name: string to name the function, optional and defaults to an auto-generated name.237//238// Response (same event name):239// - address: the start address, repeated back.240// - size: the size of the function, whether autodetected or not.241// - name: name of the new function.242//243// Note: will fail if a function starts at that location already, or if size spans multiple244// existing functions. Remove those functions first if necessary.245void WebSocketHLEFuncAdd(DebuggerRequest &req) {246if (!g_symbolMap)247return req.Fail("CPU not active");248if (!Core_IsStepping())249return req.Fail("CPU currently running (cpu.stepping first)");250251u32 addr;252if (!req.ParamU32("address", &addr))253return;254255u32 size = -1;256if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))257return;258if (size == 0)259size = -1;260261addr = RoundDownToMultipleOf(addr, 4);262size = RoundUpToMultipleOf(size, 4);263264std::string name;265if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))266return;267if (name.empty())268name = StringFromFormat("z_un_%08x", addr);269270u32 prevBegin = g_symbolMap->GetFunctionStart(addr);271u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 4);272if (prevBegin == addr) {273return req.Fail("Function already exists at 'address'");274} else if (endBegin != prevBegin) {275return req.Fail("Function already exists between 'address' and 'address' + 'size'");276} else if (prevBegin != -1) {277std::string prevName = g_symbolMap->GetLabelString(prevBegin);278u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);279u32 newPrevSize = addr - prevBegin;280281// The new function will be the remainder, unless otherwise specified.282if (size == -1)283size = prevSize - newPrevSize;284285// Make sure we register the new length for replacements too.286MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize);287g_symbolMap->SetFunctionSize(prevBegin, newPrevSize);288MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str());289} else {290// There was no function there, so hopefully they specified a size.291if (size == -1)292size = 4;293}294295// To ensure we restore replacements.296MIPSAnalyst::ForgetFunctions(addr, addr + size);297g_symbolMap->AddFunction(name.c_str(), addr, size);298g_symbolMap->SortSymbols();299MIPSAnalyst::RegisterFunction(addr, size, name.c_str());300301MIPSAnalyst::UpdateHashMap();302MIPSAnalyst::ApplyHashMap();303304if (g_Config.bFuncReplacements) {305MIPSAnalyst::ReplaceFunctions();306}307308// Clear cache for branch lines and such.309g_disassemblyManager.clear();310311JsonWriter &json = req.Respond();312json.writeUint("address", addr);313json.writeUint("size", size);314json.writeString("name", name);315}316317// Remove a function symbol (hle.func.remove)318//319// Parameters:320// - address: unsigned integer address within function to remove.321//322// Response (same event name):323// - address: the start address of the removed function.324// - size: the size in bytes of the removed function.325//326// Note: will expand any previous function automatically.327void WebSocketHLEFuncRemove(DebuggerRequest &req) {328if (!g_symbolMap)329return req.Fail("CPU not active");330if (!Core_IsStepping())331return req.Fail("CPU currently running (cpu.stepping first)");332333u32 addr;334if (!req.ParamU32("address", &addr))335return;336337addr = RoundDownToMultipleOf(addr, 4);338339u32 funcBegin = g_symbolMap->GetFunctionStart(addr);340if (funcBegin == -1)341return req.Fail("No function found at 'address'");342u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);343344// Expand the previous function.345u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1);346if (prevBegin != -1) {347std::string prevName = g_symbolMap->GetLabelString(prevBegin);348u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize;349g_symbolMap->SetFunctionSize(prevBegin, expandedSize);350MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize);351MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str());352} else {353MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize);354}355356g_symbolMap->RemoveFunction(funcBegin, true);357g_symbolMap->SortSymbols();358359MIPSAnalyst::UpdateHashMap();360MIPSAnalyst::ApplyHashMap();361362if (g_Config.bFuncReplacements) {363MIPSAnalyst::ReplaceFunctions();364}365366// Clear cache for branch lines and such.367g_disassemblyManager.clear();368369JsonWriter &json = req.Respond();370json.writeUint("address", funcBegin);371json.writeUint("size", funcSize);372}373374// This function removes function symbols that intersect or lie inside the range375// (Note: this makes no checks whether the range is valid)376// Returns the number of removed functions377static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {378u32 func_address = g_symbolMap->GetFunctionStart(addr);379if (func_address == SymbolMap::INVALID_ADDRESS) {380func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);381}382383u32 counter = 0;384while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {385g_symbolMap->RemoveFunction(func_address, true);386++counter;387func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);388}389390if (counter) {391MIPSAnalyst::ForgetFunctions(addr, addr + size);392393// The following was copied from hle.func.remove:394g_symbolMap->SortSymbols();395396MIPSAnalyst::UpdateHashMap();397MIPSAnalyst::ApplyHashMap();398399if (g_Config.bFuncReplacements) {400MIPSAnalyst::ReplaceFunctions();401}402403// Clear cache for branch lines and such.404g_disassemblyManager.clear();405}406return counter;407}408409// Remove function symbols in range (hle.func.removeRange)410//411// Parameters:412// - address: unsigned integer address for the start of the range.413// - size: unsigned integer size in bytes for removal414//415// Response (same event name):416// - count: number of removed functions417void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {418if (!g_symbolMap)419return req.Fail("CPU not active");420if (!Core_IsStepping())421return req.Fail("CPU currently running (cpu.stepping first)");422423u32 addr;424if (!req.ParamU32("address", &addr))425return;426427u32 size;428if (!req.ParamU32("size", &size))429return;430431addr = RoundDownToMultipleOf(addr, 4);432size = RoundUpToMultipleOf(size, 4);433434if (!Memory::IsValidRange(addr, size))435return req.Fail("Address or size outside valid memory");436437u32 count = RemoveFuncSymbolsInRange(addr, size);438439JsonWriter &json = req.Respond();440json.writeUint("count", count);441}442443// Rename a function symbol (hle.func.rename)444//445// Parameters:446// - address: unsigned integer address within function to rename.447// - name: string, new name for the function.448//449// Response (same event name):450// - address: the start address of the renamed function.451// - size: the size in bytes of the renamed function.452// - name: string, new name repeated back.453void WebSocketHLEFuncRename(DebuggerRequest &req) {454if (!g_symbolMap)455return req.Fail("CPU not active");456if (!Core_IsStepping())457return req.Fail("CPU currently running (cpu.stepping first)");458459u32 addr;460if (!req.ParamU32("address", &addr))461return;462std::string name;463if (!req.ParamString("name", &name))464return;465466addr = RoundDownToMultipleOf(addr, 4);467468u32 funcBegin = g_symbolMap->GetFunctionStart(addr);469if (funcBegin == -1)470return req.Fail("No function found at 'address'");471u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);472473g_symbolMap->SetLabelName(name.c_str(), funcBegin);474// To ensure we reapply replacements (in case we check name there.)475MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize);476MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str());477MIPSAnalyst::UpdateHashMap();478MIPSAnalyst::ApplyHashMap();479if (g_Config.bFuncReplacements) {480MIPSAnalyst::ReplaceFunctions();481}482483JsonWriter &json = req.Respond();484json.writeUint("address", funcBegin);485json.writeUint("size", funcSize);486json.writeString("name", name);487}488489// Auto-detect functions in a memory range (hle.func.scan)490//491// Parameters:492// - address: unsigned integer address for the start of the range.493// - size: unsigned integer size in bytes for scan.494// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning495//496// Response (same event name) with no extra data.497void WebSocketHLEFuncScan(DebuggerRequest &req) {498if (!g_symbolMap)499return req.Fail("CPU not active");500if (!Core_IsStepping())501return req.Fail("CPU currently running (cpu.stepping first)");502503u32 addr;504if (!req.ParamU32("address", &addr))505return;506507508u32 size;509if (!req.ParamU32("size", &size))510return;511512addr = RoundDownToMultipleOf(addr, 4);513size = RoundUpToMultipleOf(size, 4);514515bool remove = false;516if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL))517return;518519if (!Memory::IsValidRange(addr, size))520return req.Fail("Address or size outside valid memory");521522if (remove) {523RemoveFuncSymbolsInRange(addr, size);524}525526bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true);527MIPSAnalyst::FinalizeScan(insertSymbols);528529req.Respond();530}531532// List all known user modules (hle.module.list)533//534// No parameters.535//536// Response (same event name):537// - modules: array of objects, each with properties:538// - name: name of module when loaded.539// - address: unsigned integer start address.540// - size: unsigned integer size in bytes.541// - isActive: boolean, true if this module is active.542void WebSocketHLEModuleList(DebuggerRequest &req) {543if (!g_symbolMap)544return req.Fail("CPU not active");545546auto modules = g_symbolMap->getAllModules();547548JsonWriter &json = req.Respond();549json.pushArray("modules");550for (auto m : modules) {551json.pushDict();552json.writeString("name", m.name);553json.writeUint("address", m.address);554json.writeUint("size", m.size);555json.writeBool("isActive", m.active);556json.pop();557}558json.pop();559}560561// Walk the stack and list stack frames (hle.backtrace)562//563// Parameters:564// - thread: optional number indicating the thread id to backtrace, default current.565//566// Response (same event name):567// - frames: array of objects, each with properties:568// - entry: unsigned integer address of function start (may be estimated.)569// - pc: unsigned integer next execution address.570// - sp: unsigned integer stack address in this func (beware of alloca().)571// - stackSize: integer size of stack frame.572// - code: string disassembly of pc.573void WebSocketHLEBacktrace(DebuggerRequest &req) {574if (!g_symbolMap)575return req.Fail("CPU not active");576if (!Core_IsStepping())577return req.Fail("CPU currently running (cpu.stepping first)");578579uint32_t threadID = -1;580DebugInterface *cpuDebug = currentDebugMIPS;581if (req.HasParam("thread")) {582if (!req.ParamU32("thread", &threadID))583return;584585cpuDebug = KernelDebugThread((SceUID)threadID);586if (!cpuDebug)587return req.Fail("Thread could not be found");588}589590auto threads = GetThreadsInfo();591uint32_t entry = cpuDebug->GetPC();592uint32_t stackTop = 0;593for (const DebugThreadInfo &th : threads) {594if ((threadID == -1 && th.isCurrent) || th.id == threadID) {595entry = th.entrypoint;596stackTop = th.initialStack;597break;598}599}600601uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);602uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);603auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);604605JsonWriter &json = req.Respond();606json.pushArray("frames");607for (auto f : frames) {608json.pushDict();609json.writeUint("entry", f.entry);610json.writeUint("pc", f.pc);611json.writeUint("sp", f.sp);612json.writeUint("stackSize", f.stackSize);613614DisassemblyLineInfo line;615g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug);616json.writeString("code", line.name + " " + line.params);617618json.pop();619}620json.pop();621}622623624