Path: blob/master/Core/Debugger/WebSocket/MemoryInfoSubscriber.cpp
5651 views
// Copyright (c) 2021- 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 <algorithm>18#include "Core/MIPS/MIPS.h"19#include "Core/MIPS/MIPSDebugInterface.h"20#include "Core/Debugger/MemBlockInfo.h"21#include "Core/Debugger/WebSocket/MemoryInfoSubscriber.h"22#include "Core/Debugger/WebSocket/WebSocketUtils.h"23#include "Core/MemMap.h"2425class WebSocketMemoryInfoState : public DebuggerSubscriber {26public:27WebSocketMemoryInfoState() {28}29~WebSocketMemoryInfoState() {30UpdateOverride(false);31}3233void Mapping(DebuggerRequest &req);34void Config(DebuggerRequest &req);35void Set(DebuggerRequest &req);36void List(DebuggerRequest &req);37void Search(DebuggerRequest &req);3839protected:40void UpdateOverride(bool flag);4142bool detailOverride_ = false;43};4445DebuggerSubscriber *WebSocketMemoryInfoInit(DebuggerEventHandlerMap &map) {46auto p = new WebSocketMemoryInfoState();47map["memory.mapping"] = [p](DebuggerRequest &req) { p->Mapping(req); };48map["memory.info.config"] = [p](DebuggerRequest &req) { p->Config(req); };49map["memory.info.set"] = [p](DebuggerRequest &req) { p->Set(req); };50map["memory.info.list"] = [p](DebuggerRequest &req) { p->List(req); };51map["memory.info.search"] = [p](DebuggerRequest &req) { p->Search(req); };5253return p;54}5556void WebSocketMemoryInfoState::UpdateOverride(bool flag) {57if (detailOverride_ && !flag)58MemBlockReleaseDetailed();59if (!detailOverride_ && flag)60MemBlockOverrideDetailed();61detailOverride_ = flag;62}6364// List memory map data (memory.mapping)65//66// No parameters.67//68// Response (same event name):69// - ranges: array of objects:70// - type: one of "ram", "vram", "sram".71// - subtype: "primary" or "mirror".72// - name: string, friendly name.73// - address: number, start address of range.74// - size: number, in bytes.75void WebSocketMemoryInfoState::Mapping(DebuggerRequest &req) {76struct MemRange {77const char *type;78const char *subtype;79const char *name;80const uint32_t address;81const uint32_t size;82};83constexpr uint32_t kernelMemorySize = PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase();84constexpr uint32_t volatileMemorySize = PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart();85static constexpr MemRange ranges[] = {86{ "sram", "primary", "Scratchpad", PSP_GetScratchpadMemoryBase(), Memory::SCRATCHPAD_SIZE },87{ "vram", "primary", "VRAM", PSP_GetVidMemBase(), Memory::VRAM_SIZE },88{ "vram", "mirror", "VRAM (Swizzled)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 1, Memory::VRAM_SIZE },89{ "vram", "mirror", "VRAM (Mirror)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 2, Memory::VRAM_SIZE },90{ "vram", "mirror", "VRAM (Swizzled + Interleaved)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 3, Memory::VRAM_SIZE },91{ "ram", "primary", "Kernel Memory", 0x80000000 | PSP_GetKernelMemoryBase(), kernelMemorySize },92{ "ram", "primary", "Volatile Memory", PSP_GetVolatileMemoryStart(), volatileMemorySize },93// Size is specially calculated.94{ "ram", "primary", "User Memory", PSP_GetUserMemoryBase(), 0 },95};9697JsonWriter &json = req.Respond();98json.pushArray("ranges");99for (auto range : ranges) {100uint32_t size = range.size;101if (size == 0) {102size = Memory::g_MemorySize;103if (size == 0) {104size = Memory::RAM_NORMAL_SIZE;105}106size -= kernelMemorySize + volatileMemorySize;107}108json.pushDict();109json.writeString("type", range.type);110json.writeString("subtype", range.subtype);111json.writeString("name", range.name);112json.writeUint("address", range.address);113json.writeUint("size", size);114json.pop();115116// Also write the uncached range.117json.pushDict();118json.writeString("type", range.type);119json.writeString("subtype", "mirror");120json.writeString("name", std::string("Uncached ") + range.name);121json.writeUint("address", 0x40000000 | range.address);122json.writeUint("size", size);123json.pop();124}125json.pop();126}127128// Update memory info tracking config (memory.info.config)129//130// Parameters:131// - detailed: optional, boolean to force enable detailed tracking (perf impact.)132//133// Response (same event name):134// - detailed: boolean state of tracking before any changes.135//136// Note: Even if you set false, may stay enabled if set by user or another debug session.137void WebSocketMemoryInfoState::Config(DebuggerRequest &req) {138bool setDetailed = req.HasParam("detailed");139bool detailed = false;140if (!req.ParamBool("detailed", &detailed, DebuggerParamType::OPTIONAL))141return;142143JsonWriter &json = req.Respond();144json.writeBool("detailed", MemBlockInfoDetailed());145146if (setDetailed)147UpdateOverride(detailed);148}149150static MemBlockFlags FlagFromType(const std::string &type) {151if (type == "write")152return MemBlockFlags::WRITE;153if (type == "texture")154return MemBlockFlags::TEXTURE;155if (type == "alloc")156return MemBlockFlags::ALLOC;157if (type == "suballoc")158return MemBlockFlags::SUB_ALLOC;159if (type == "free")160return MemBlockFlags::FREE;161if (type == "subfree")162return MemBlockFlags::SUB_FREE;163return MemBlockFlags::SKIP_MEMCHECK;164}165166static std::string TypeFromFlag(const MemBlockFlags &flag) {167if (flag & MemBlockFlags::WRITE)168return "write";169else if (flag & MemBlockFlags::TEXTURE)170return "texture";171else if (flag & MemBlockFlags::ALLOC)172return "alloc";173else if (flag & MemBlockFlags::SUB_ALLOC)174return "suballoc";175return "error";176}177178// Update memory info tagging (memory.info.set)179//180// Parameters:181// - address: number representing start address of the range to modify.182// - size: number, bytes from start address.183// - type: string, one of:184// - "write" for last modification information.185// - "texture" for last texture usage information.186// - "alloc" for allocation information.187// - "suballoc" for allocations within an existing allocation.188// - "free" to mark a previous allocation and its suballocations freed (ignores tag.)189// - "subfree" to mark a previous suballocation freed (ignores tag.)190// - tag: string label to give the memory. Optional if type if free or subfree.191// - pc: optional, number indicating PC address for this tag.192//193// Response (same event name) with no extra data.194//195// Note: Only one tag per type is maintained for any given memory address.196// Small extent info may be ignored unless detailed tracking enabled (see memory.info.config.)197void WebSocketMemoryInfoState::Set(DebuggerRequest &req) {198if (!currentDebugMIPS->isAlive() || !Memory::IsActive())199return req.Fail("CPU not started");200201std::string type;202if (!req.ParamString("type", &type))203return;204std::string tag;205if (type != "free" && type != "subfree") {206if (!req.ParamString("tag", &tag))207return;208}209uint32_t addr;210if (!req.ParamU32("address", &addr))211return;212uint32_t size;213if (!req.ParamU32("size", &size))214return;215uint32_t pc = currentMIPS->pc;216if (!req.ParamU32("pc", &pc, false, DebuggerParamType::OPTIONAL))217return;218219MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);220if (flags == MemBlockFlags::SKIP_MEMCHECK)221return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");222223if (!Memory::IsValidAddress(addr))224return req.Fail("Invalid address");225else if (!Memory::IsValidRange(addr, size))226return req.Fail("Invalid size");227228NotifyMemInfoPC(flags, addr, size, pc, tag.c_str(), tag.size());229req.Respond();230}231232// List memory info tags for address range (memory.info.list)233//234// Parameters:235// - address: number representing start address of the range.236// - size: number, bytes from start address.237// - type: optional string to limit information to one of:238// - "write" for last modification information.239// - "texture" for last texture usage information.240// - "alloc" for allocation information.241// - "suballoc" for allocations within an existing allocation.242//243// Response (same event name):244// - extents: array of objects:245// - type: one of the above type string values.246// - address: number (may be outside requested range if overlapping.)247// - size: number (may be outside requested range if overlapping.)248// - ticks: number indicating tick counter as of last tag.249// - pc: number address of last tag.250// - tag: string tag for this memory extent.251// - allocated: boolean, if this extent is marked as allocated (for alloc/suballoc types.)252void WebSocketMemoryInfoState::List(DebuggerRequest &req) {253if (!currentDebugMIPS->isAlive() || !Memory::IsActive())254return req.Fail("CPU not started");255256std::string type;257if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))258return;259uint32_t addr;260if (!req.ParamU32("address", &addr))261return;262uint32_t size;263if (!req.ParamU32("size", &size))264return;265266// Allow type to be omitted.267MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);268if (flags == MemBlockFlags::SKIP_MEMCHECK && req.HasParam("type"))269return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");270271if (!Memory::IsValidAddress(addr))272return req.Fail("Invalid address");273else if (!Memory::IsValidRange(addr, size))274return req.Fail("Invalid size");275276std::vector<MemBlockInfo> results;277if (flags == MemBlockFlags::SKIP_MEMCHECK)278results = FindMemInfo(addr, size);279else280results = FindMemInfoByFlag(flags, addr, size);281282JsonWriter &json = req.Respond();283json.pushArray("extents");284for (const auto &result : results) {285json.pushDict();286json.writeString("type", TypeFromFlag(result.flags));287json.writeUint("address", result.start);288json.writeUint("size", result.size);289json.writeFloat("ticks", result.ticks);290json.writeUint("pc", result.pc);291json.writeString("tag", result.tag);292json.writeBool("allocated", result.allocated);293json.pop();294}295json.pop();296}297298// Search memory info tags for a string (memory.info.search)299//300// Parameters:301// - address: optional number representing start address of the range.302// - end: optional end address as a number (otherwise uses start address.)303// - match: string to search for within tag.304// - type: optional string to limit information to one of:305// - "write" for last modification information.306// - "texture" for last texture usage information.307// - "alloc" for allocation information.308// - "suballoc" for allocations within an existing allocation.309//310// Response (same event name):311// - extent: null, or matching object containing:312// - type: one of the above type string values.313// - address: number (may be outside requested range if overlapping.)314// - size: number (may be outside requested range if overlapping.)315// - ticks: number indicating tick counter as of last tag.316// - pc: number address of last tag.317// - tag: string tag for this memory extent.318// - allocated: boolean, if this extent is marked as allocated (for alloc/suballoc types.)319//320// Note: may not be fast.321void WebSocketMemoryInfoState::Search(DebuggerRequest &req) {322if (!currentDebugMIPS->isAlive() || !Memory::IsActive())323return req.Fail("CPU not started");324325uint32_t start = 0;326if (!req.ParamU32("address", &start, false, DebuggerParamType::OPTIONAL))327return;328uint32_t end = start;329if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))330return;331std::string type;332if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))333return;334std::string match;335if (!req.ParamString("match", &match))336return;337338// Allow type to be omitted.339MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);340if (flags == MemBlockFlags::SKIP_MEMCHECK && req.HasParam("type"))341return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");342343start = RoundMemAddressUp(start);344end = RoundMemAddressUp(end);345std::transform(match.begin(), match.end(), match.begin(), ::tolower);346347bool found = false;348MemBlockInfo foundResult;349350uint32_t addr = start;351constexpr uint32_t CHUNK_SIZE = 0x1000;352do {353uint32_t chunk_end = addr + CHUNK_SIZE;354if (addr < end && chunk_end >= end) {355chunk_end = end;356}357358std::vector<MemBlockInfo> results;359if (flags == MemBlockFlags::SKIP_MEMCHECK)360results = FindMemInfo(addr, chunk_end - addr);361else362results = FindMemInfoByFlag(flags, addr, chunk_end - addr);363364for (const auto &result : results) {365std::string lowercase = result.tag;366std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);367368if (lowercase.find(match) != lowercase.npos) {369found = true;370foundResult = result;371break;372}373}374addr = RoundMemAddressUp(chunk_end);375} while (!found && addr != end);376377JsonWriter &json = req.Respond();378if (found) {379json.pushDict("extent");380json.writeString("type", TypeFromFlag(foundResult.flags));381json.writeUint("address", foundResult.start);382json.writeUint("size", foundResult.size);383json.writeFloat("ticks", foundResult.ticks);384json.writeUint("pc", foundResult.pc);385json.writeString("tag", foundResult.tag);386json.writeBool("allocated", foundResult.allocated);387json.pop();388} else {389json.writeNull("extent");390}391}392393394