CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Debugger/WebSocket/HLESubscriber.cpp
Views: 1401
// 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/Debugger/DisassemblyManager.h"21#include "Core/Debugger/SymbolMap.h"22#include "Core/Debugger/WebSocket/HLESubscriber.h"23#include "Core/Debugger/WebSocket/WebSocketUtils.h"24#include "Core/MemMap.h"25#include "Core/MIPS/MIPSAnalyst.h"26#include "Core/MIPS/MIPSDebugInterface.h"27#include "Core/MIPS/MIPSStackWalk.h"28#include "Core/HLE/sceKernelThread.h"29#include "Core/Reporting.h"3031DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {32map["hle.thread.list"] = &WebSocketHLEThreadList;33map["hle.thread.wake"] = &WebSocketHLEThreadWake;34map["hle.thread.stop"] = &WebSocketHLEThreadStop;35map["hle.func.list"] = &WebSocketHLEFuncList;36map["hle.func.add"] = &WebSocketHLEFuncAdd;37map["hle.func.remove"] = &WebSocketHLEFuncRemove;38map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;39map["hle.func.rename"] = &WebSocketHLEFuncRename;40map["hle.func.scan"] = &WebSocketHLEFuncScan;41map["hle.module.list"] = &WebSocketHLEModuleList;42map["hle.backtrace"] = &WebSocketHLEBacktrace;4344return nullptr;45}4647// List all current HLE threads (hle.thread.list)48//49// No parameters.50//51// Response (same event name):52// - threads: array of objects, each with properties:53// - id: unsigned integer unique id of thread.54// - name: name given to thread when created.55// - status: numeric status flags of thread.56// - statuses: array of string status names, e.g. 'running'. Typically only one set.57// - pc: unsigned integer address of next instruction on thread.58// - entry: unsigned integer address thread execution started at.59// - initialStackSize: unsigned integer, size of initial stack.60// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)61// - priority: numeric priority level, lower values are better priority.62// - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting.63// - isCurrent: boolean, true for the currently executing thread.64void WebSocketHLEThreadList(DebuggerRequest &req) {65// Will just return none of the CPU isn't ready yet.66auto threads = GetThreadsInfo();6768JsonWriter &json = req.Respond();69json.pushArray("threads");70for (const auto &th : threads) {71json.pushDict();72json.writeUint("id", th.id);73json.writeString("name", th.name);74json.writeInt("status", th.status);75json.pushArray("statuses");76if (th.status & THREADSTATUS_RUNNING)77json.writeString("running");78if (th.status & THREADSTATUS_READY)79json.writeString("ready");80if (th.status & THREADSTATUS_WAIT)81json.writeString("wait");82if (th.status & THREADSTATUS_SUSPEND)83json.writeString("suspend");84if (th.status & THREADSTATUS_DORMANT)85json.writeString("dormant");86if (th.status & THREADSTATUS_DEAD)87json.writeString("dead");88json.pop();89json.writeUint("pc", th.curPC);90json.writeUint("entry", th.entrypoint);91json.writeUint("initialStackSize", th.initialStack);92json.writeUint("currentStackSize", th.stackSize);93json.writeInt("priority", th.priority);94json.writeInt("waitType", (int)th.waitType);95json.writeBool("isCurrent", th.isCurrent);96json.pop();97}98json.pop();99}100101static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) {102if (!PSP_IsInited()) {103req.Fail("CPU not active");104return false;105}106if (!Core_IsStepping()) {107req.Fail("CPU currently running (cpu.stepping first)");108return false;109}110111uint32_t threadID;112if (!req.ParamU32("thread", &threadID))113return false;114115auto threads = GetThreadsInfo();116for (const auto &t : threads) {117if (t.id == threadID) {118*result = t;119return true;120}121}122123req.Fail("Thread could not be found");124return false;125}126127// Force resume a thread (hle.thread.wake)128//129// Parameters:130// - thread: number indicating the thread id to resume.131//132// Response (same event name):133// - thread: id repeated back.134// - status: string 'ready'.135void WebSocketHLEThreadWake(DebuggerRequest &req) {136DebugThreadInfo threadInfo{ -1 };137if (!ThreadInfoForStatus(req, &threadInfo))138return;139140switch (threadInfo.status) {141case THREADSTATUS_SUSPEND:142case THREADSTATUS_WAIT:143case THREADSTATUS_WAITSUSPEND:144if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0)145return req.Fail("Failed to resume thread");146break;147148default:149return req.Fail("Cannot force run thread based on current status");150}151152Reporting::NotifyDebugger();153154JsonWriter &json = req.Respond();155json.writeUint("thread", threadInfo.id);156json.writeString("status", "ready");157}158159// Force stop a thread (hle.thread.stop)160//161// Parameters:162// - thread: number indicating the thread id to stop.163//164// Response (same event name):165// - thread: id repeated back.166// - status: string 'dormant'.167void WebSocketHLEThreadStop(DebuggerRequest &req) {168DebugThreadInfo threadInfo{ -1 };169if (!ThreadInfoForStatus(req, &threadInfo))170return;171172switch (threadInfo.status) {173case THREADSTATUS_SUSPEND:174case THREADSTATUS_WAIT:175case THREADSTATUS_WAITSUSPEND:176case THREADSTATUS_READY:177__KernelStopThread(threadInfo.id, 0, "stopped from debugger");178break;179180default:181return req.Fail("Cannot force run thread based on current status");182}183184// Get it again to verify.185if (!ThreadInfoForStatus(req, &threadInfo))186return;187if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)188return req.Fail("Failed to stop thread");189190Reporting::NotifyDebugger();191192JsonWriter &json = req.Respond();193json.writeUint("thread", threadInfo.id);194json.writeString("status", "dormant");195}196197// List all current known function symbols (hle.func.list)198//199// No parameters.200//201// Response (same event name):202// - functions: array of objects, each with properties:203// - name: current name of function.204// - address: unsigned integer start address of function.205// - size: unsigned integer size in bytes.206void WebSocketHLEFuncList(DebuggerRequest &req) {207if (!g_symbolMap)208return req.Fail("CPU not active");209210auto functions = g_symbolMap->GetAllSymbols(ST_FUNCTION);211212JsonWriter &json = req.Respond();213json.pushArray("functions");214for (auto f : functions) {215json.pushDict();216json.writeString("name", f.name);217json.writeUint("address", f.address);218json.writeUint("size", f.size);219json.pop();220}221json.pop();222}223224// Add a new function symbols (hle.func.add)225//226// Parameters:227// - address: unsigned integer address for the start of the function.228// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,229// defaults to that function's end, otherwise 4 bytes.230// - name: string to name the function, optional and defaults to an auto-generated name.231//232// Response (same event name):233// - address: the start address, repeated back.234// - size: the size of the function, whether autodetected or not.235// - name: name of the new function.236//237// Note: will fail if a function starts at that location already, or if size spans multiple238// existing functions. Remove those functions first if necessary.239void WebSocketHLEFuncAdd(DebuggerRequest &req) {240if (!g_symbolMap)241return req.Fail("CPU not active");242if (!Core_IsStepping())243return req.Fail("CPU currently running (cpu.stepping first)");244245u32 addr;246if (!req.ParamU32("address", &addr))247return;248u32 size = -1;249if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))250return;251if (size == 0)252size = -1;253254std::string name;255if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))256return;257if (name.empty())258name = StringFromFormat("z_un_%08x", addr);259260u32 prevBegin = g_symbolMap->GetFunctionStart(addr);261u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 1);262if (prevBegin == addr) {263return req.Fail("Function already exists at 'address'");264} else if (endBegin != prevBegin) {265return req.Fail("Function already exists between 'address' and 'address' + 'size'");266} else if (prevBegin != -1) {267std::string prevName = g_symbolMap->GetLabelString(prevBegin);268u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);269u32 newPrevSize = addr - prevBegin;270271// The new function will be the remainder, unless otherwise specified.272if (size == -1)273size = prevSize - newPrevSize;274275// Make sure we register the new length for replacements too.276MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize - 1);277g_symbolMap->SetFunctionSize(prevBegin, newPrevSize);278MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str());279} else {280// There was no function there, so hopefully they specified a size.281if (size == -1)282size = 4;283}284285// To ensure we restore replacements.286MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);287g_symbolMap->AddFunction(name.c_str(), addr, size);288g_symbolMap->SortSymbols();289MIPSAnalyst::RegisterFunction(addr, size, name.c_str());290291MIPSAnalyst::UpdateHashMap();292MIPSAnalyst::ApplyHashMap();293294if (g_Config.bFuncReplacements) {295MIPSAnalyst::ReplaceFunctions();296}297298// Clear cache for branch lines and such.299DisassemblyManager manager;300manager.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.356DisassemblyManager manager;357manager.clear();358359JsonWriter &json = req.Respond();360json.writeUint("address", funcBegin);361json.writeUint("size", funcSize);362}363364// This function removes function symbols that intersect or lie inside the range365// (Note: this makes no checks whether the range is valid)366// Returns the number of removed functions367static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {368u32 func_address = g_symbolMap->GetFunctionStart(addr);369if (func_address == SymbolMap::INVALID_ADDRESS) {370func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);371}372373u32 counter = 0;374while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {375g_symbolMap->RemoveFunction(func_address, true);376++counter;377func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);378}379380if (counter) {381MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);382383// The following was copied from hle.func.remove:384g_symbolMap->SortSymbols();385386MIPSAnalyst::UpdateHashMap();387MIPSAnalyst::ApplyHashMap();388389if (g_Config.bFuncReplacements) {390MIPSAnalyst::ReplaceFunctions();391}392393// Clear cache for branch lines and such.394DisassemblyManager manager;395manager.clear();396}397return counter;398}399400// Remove function symbols in range (hle.func.removeRange)401//402// Parameters:403// - address: unsigned integer address for the start of the range.404// - size: unsigned integer size in bytes for removal405//406// Response (same event name):407// - count: number of removed functions408void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {409if (!g_symbolMap)410return req.Fail("CPU not active");411if (!Core_IsStepping())412return req.Fail("CPU currently running (cpu.stepping first)");413414u32 addr;415if (!req.ParamU32("address", &addr))416return;417u32 size;418if (!req.ParamU32("size", &size))419return;420421if (!Memory::IsValidRange(addr, size))422return req.Fail("Address or size outside valid memory");423424u32 count = RemoveFuncSymbolsInRange(addr, size);425426JsonWriter &json = req.Respond();427json.writeUint("count", count);428}429430// Rename a function symbol (hle.func.rename)431//432// Parameters:433// - address: unsigned integer address within function to rename.434// - name: string, new name for the function.435//436// Response (same event name):437// - address: the start address of the renamed function.438// - size: the size in bytes of the renamed function.439// - name: string, new name repeated back.440void WebSocketHLEFuncRename(DebuggerRequest &req) {441if (!g_symbolMap)442return req.Fail("CPU not active");443if (!Core_IsStepping())444return req.Fail("CPU currently running (cpu.stepping first)");445446u32 addr;447if (!req.ParamU32("address", &addr))448return;449std::string name;450if (!req.ParamString("name", &name))451return;452453u32 funcBegin = g_symbolMap->GetFunctionStart(addr);454if (funcBegin == -1)455return req.Fail("No function found at 'address'");456u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);457458g_symbolMap->SetLabelName(name.c_str(), funcBegin);459// To ensure we reapply replacements (in case we check name there.)460MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);461MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str());462MIPSAnalyst::UpdateHashMap();463MIPSAnalyst::ApplyHashMap();464if (g_Config.bFuncReplacements) {465MIPSAnalyst::ReplaceFunctions();466}467468JsonWriter &json = req.Respond();469json.writeUint("address", funcBegin);470json.writeUint("size", funcSize);471json.writeString("name", name);472}473474// Auto-detect functions in a memory range (hle.func.scan)475//476// Parameters:477// - address: unsigned integer address for the start of the range.478// - size: unsigned integer size in bytes for scan.479// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning480//481// Response (same event name) with no extra data.482void WebSocketHLEFuncScan(DebuggerRequest &req) {483if (!g_symbolMap)484return req.Fail("CPU not active");485if (!Core_IsStepping())486return req.Fail("CPU currently running (cpu.stepping first)");487488u32 addr;489if (!req.ParamU32("address", &addr))490return;491u32 size;492if (!req.ParamU32("size", &size))493return;494495bool remove = false;496if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL))497return;498499if (!Memory::IsValidRange(addr, size))500return req.Fail("Address or size outside valid memory");501502if (remove) {503RemoveFuncSymbolsInRange(addr, size);504}505506bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size - 1, true);507MIPSAnalyst::FinalizeScan(insertSymbols);508509req.Respond();510}511512// List all known user modules (hle.module.list)513//514// No parameters.515//516// Response (same event name):517// - modules: array of objects, each with properties:518// - name: name of module when loaded.519// - address: unsigned integer start address.520// - size: unsigned integer size in bytes.521// - isActive: boolean, true if this module is active.522void WebSocketHLEModuleList(DebuggerRequest &req) {523if (!g_symbolMap)524return req.Fail("CPU not active");525526auto modules = g_symbolMap->getAllModules();527528JsonWriter &json = req.Respond();529json.pushArray("modules");530for (auto m : modules) {531json.pushDict();532json.writeString("name", m.name);533json.writeUint("address", m.address);534json.writeUint("size", m.size);535json.writeBool("isActive", m.active);536json.pop();537}538json.pop();539}540541// Walk the stack and list stack frames (hle.backtrace)542//543// Parameters:544// - thread: optional number indicating the thread id to backtrace, default current.545//546// Response (same event name):547// - frames: array of objects, each with properties:548// - entry: unsigned integer address of function start (may be estimated.)549// - pc: unsigned integer next execution address.550// - sp: unsigned integer stack address in this func (beware of alloca().)551// - stackSize: integer size of stack frame.552// - code: string disassembly of pc.553void WebSocketHLEBacktrace(DebuggerRequest &req) {554if (!g_symbolMap)555return req.Fail("CPU not active");556if (!Core_IsStepping())557return req.Fail("CPU currently running (cpu.stepping first)");558559uint32_t threadID = -1;560DebugInterface *cpuDebug = currentDebugMIPS;561if (req.HasParam("thread")) {562if (!req.ParamU32("thread", &threadID))563return;564565cpuDebug = KernelDebugThread((SceUID)threadID);566if (!cpuDebug)567return req.Fail("Thread could not be found");568}569570auto threads = GetThreadsInfo();571uint32_t entry = cpuDebug->GetPC();572uint32_t stackTop = 0;573for (const DebugThreadInfo &th : threads) {574if ((threadID == -1 && th.isCurrent) || th.id == threadID) {575entry = th.entrypoint;576stackTop = th.initialStack;577break;578}579}580581uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);582uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);583auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);584585JsonWriter &json = req.Respond();586json.pushArray("frames");587for (auto f : frames) {588json.pushDict();589json.writeUint("entry", f.entry);590json.writeUint("pc", f.pc);591json.writeUint("sp", f.sp);592json.writeUint("stackSize", f.stackSize);593594DisassemblyManager manager;595DisassemblyLineInfo line;596manager.getLine(manager.getStartAddress(f.pc), true, line, cpuDebug);597json.writeString("code", line.name + " " + line.params);598599json.pop();600}601json.pop();602}603604605