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/DisasmSubscriber.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 <algorithm>18#include <cctype>1920#include "Common/Data/Encoding/Utf8.h"2122#include "Common/StringUtils.h"23#include "Core/Debugger/Breakpoints.h"24#include "Core/Debugger/DisassemblyManager.h"25#include "Core/Debugger/WebSocket/DisasmSubscriber.h"26#include "Core/Debugger/WebSocket/WebSocketUtils.h"27#include "Core/HLE/sceKernelThread.h"28#include "Core/MemMap.h"29#include "Core/MIPS/MIPSAsm.h"30#include "Core/MIPS/MIPSDebugInterface.h"31#include "Core/Reporting.h"3233class WebSocketDisasmState : public DebuggerSubscriber {34public:35WebSocketDisasmState() {36disasm_.setCpu(currentDebugMIPS);37}38~WebSocketDisasmState() {39disasm_.clear();40}4142void Base(DebuggerRequest &req);43void Disasm(DebuggerRequest &req);44void SearchDisasm(DebuggerRequest &req);45void Assemble(DebuggerRequest &req);4647protected:48void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l);49void WriteBranchGuide(JsonWriter &json, const BranchLine &l);5051DisassemblyManager disasm_;52};5354DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) {55auto p = new WebSocketDisasmState();56map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1);57map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1);58map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1);59map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1);6061return p;62}6364static DebugInterface *CPUFromRequest(DebuggerRequest &req) {65if (!req.HasParam("thread"))66return currentDebugMIPS;6768u32 uid;69if (!req.ParamU32("thread", &uid))70return nullptr;7172DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);73if (!cpuDebug)74req.Fail("Thread could not be found");75return cpuDebug;76}7778void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {79u32 addr = l.info.opcodeAddress;80json.pushDict();81if (l.type == DISTYPE_OPCODE)82json.writeString("type", "opcode");83else if (l.type == DISTYPE_MACRO)84json.writeString("type", "macro");85else if (l.type == DISTYPE_DATA)86json.writeString("type", "data");87else if (l.type == DISTYPE_OTHER)88json.writeString("type", "other");8990json.writeUint("address", addr);91json.writeInt("addressSize", l.totalSize);92json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);93if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {94json.pushArray("macroEncoding");95for (u32 off = 0; off < l.totalSize; off += 4) {96json.writeUint(Memory::Read_Instruction(addr + off).encoding);97}98json.pop();99} else {100json.writeNull("macroEncoding");101}102int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF;103json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));104json.writeString("name", l.name);105json.writeString("params", l.params);106107const std::string addressSymbol = g_symbolMap->GetLabelString(addr);108if (addressSymbol.empty())109json.writeNull("symbol");110else111json.writeString("symbol", addressSymbol);112113const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);114const std::string funcName = g_symbolMap->GetLabelString(funcAddress);115if (funcName.empty())116json.writeNull("function");117else118json.writeString("function", funcName);119120if (l.type == DISTYPE_DATA) {121u32 dataStart = g_symbolMap->GetDataStart(addr);122if (dataStart == -1)123dataStart = addr;124const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);125126json.pushDict("dataSymbol");127json.writeUint("start", dataStart);128if (dataLabel.empty())129json.writeNull("label");130else131json.writeString("label", dataLabel);132json.pop();133} else {134json.writeNull("dataSymbol");135}136137bool enabled = false;138int breakpointOffset = -1;139for (u32 i = 0; i < l.totalSize; i += 4) {140if (CBreakPoints::IsAddressBreakPoint(addr + i, &enabled))141breakpointOffset = i;142if (breakpointOffset != -1 && enabled)143break;144}145// TODO: Account for bp inside macro?146if (breakpointOffset != -1) {147json.pushDict("breakpoint");148json.writeBool("enabled", enabled);149json.writeUint("address", addr + breakpointOffset);150auto cond = CBreakPoints::GetBreakPointCondition(addr + breakpointOffset);151if (cond)152json.writeString("condition", cond->expressionString);153else154json.writeNull("condition");155json.pop();156} else {157json.writeNull("breakpoint");158}159160// This is always the current execution's PC.161json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);162if (l.info.isBranch) {163json.pushDict("branch");164std::string targetSymbol;165if (!l.info.isBranchToRegister) {166targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);167json.writeUint("targetAddress", l.info.branchTarget);168json.writeNull("register");169} else {170json.writeNull("targetAddress");171json.writeInt("register", l.info.branchRegisterNum);172}173json.writeBool("isLinked", l.info.isLinkedBranch);174json.writeBool("isLikely", l.info.isLikelyBranch);175if (targetSymbol.empty())176json.writeNull("symbol");177else178json.writeString("symbol", targetSymbol);179json.pop();180} else {181json.writeNull("branch");182}183184if (l.info.hasRelevantAddress) {185json.pushDict("relevantData");186json.writeUint("address", l.info.relevantAddress);187if (Memory::IsValidRange(l.info.relevantAddress, 4))188json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));189else190json.writeNull("uintValue");191if (IsLikelyStringAt(l.info.relevantAddress))192json.writeString("stringValue", Memory::GetCharPointer(l.info.relevantAddress));193else194json.writeNull("stringValue");195json.pop();196} else {197json.writeNull("relevantData");198}199200if (l.info.isConditional)201json.writeBool("conditionMet", l.info.conditionMet);202else203json.writeNull("conditionMet");204205if (l.info.isDataAccess) {206json.pushDict("dataAccess");207json.writeUint("address", l.info.dataAddress);208json.writeInt("size", l.info.dataSize);209210std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);211std::string valueSymbol;212if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))213json.writeNull("uintValue");214else if (l.info.dataSize == 1)215json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));216else if (l.info.dataSize == 2)217json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));218else if (l.info.dataSize >= 4) {219u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);220valueSymbol = g_symbolMap->GetLabelString(data);221json.writeUint("uintValue", data);222}223224if (!dataSymbol.empty())225json.writeString("symbol", dataSymbol);226else227json.writeNull("symbol");228if (!valueSymbol.empty())229json.writeString("valueSymbol", valueSymbol);230else231json.writeNull("valueSymbol");232json.pop();233} else {234json.writeNull("dataAccess");235}236237json.pop();238}239240void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {241json.pushDict();242json.writeUint("top", l.first);243json.writeUint("bottom", l.second);244if (l.type == LINE_UP)245json.writeString("direction", "up");246else if (l.type == LINE_DOWN)247json.writeString("direction", "down");248else if (l.type == LINE_RIGHT)249json.writeString("direction", "right");250json.writeInt("lane", l.laneIndex);251json.pop();252}253254// Request the current PSP memory base address (memory.base)255//256// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.257//258// No parameters.259//260// Response (same event name):261// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)262void WebSocketDisasmState::Base(DebuggerRequest &req) {263JsonWriter &json = req.Respond();264Reporting::NotifyDebugger();265json.writeString("addressHex", StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));266}267268// Disassemble a range of memory as CPU instructions (memory.disasm)269//270// Parameters (by count):271// - thread: optional number indicating the thread id for branch info.272// - address: number specifying the start address.273// - count: number of lines to return (may be clamped to an internal limit.)274// - displaySymbols: boolean true to show symbol names in instruction params.275//276// Parameters (by end address):277// - thread: optional number indicating the thread id for branch info.278// - address: number specifying the start address.279// - end: number which must be after the start address (may be clamped to an internal limit.)280// - displaySymbols: boolean true to show symbol names in instruction params.281//282// Response (same event name):283// - range: object with result "start" and "end" properties, the addresses actually used.284// (disassembly may have snapped to a nearby instruction.)285// - branchGuides: array of objects:286// - top: the earlier address as a number.287// - bottom: the later address as a number.288// - direction: "up", "down", or "right" depending on the flow of the branch.289// - lane: number index to avoid overlapping guides.290// - lines: array of objects:291// - type: "opcode", "macro", "data", or "other".292// - address: address of first actual instruction.293// - addressSize: bytes used by this line (might be more than 4.)294// - encoding: uint value of actual instruction (may differ from memory read when using jit.)295// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.296// - name: string name of the instruction.297// - params: formatted parameters for the instruction.298// - (other info about the disassembled line.)299void WebSocketDisasmState::Disasm(DebuggerRequest &req) {300if (!currentDebugMIPS->isAlive() || !Memory::IsActive())301return req.Fail("CPU not started");302auto cpuDebug = CPUFromRequest(req);303if (!cpuDebug)304return;305306// In case of client errors, we limit the range to something that won't make us crash.307static const uint32_t MAX_RANGE = 10000;308309uint32_t start, end;310if (!req.ParamU32("address", &start))311return;312uint32_t count = 0;313if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))314return;315if (count != 0) {316count = std::min(count, MAX_RANGE);317// Let's assume everything is two instructions.318disasm_.analyze(start - 4, count * 8 + 8);319start = disasm_.getStartAddress(start);320if (start == -1)321req.ParamU32("address", &start);322end = disasm_.getNthNextAddress(start, count);323} else if (req.ParamU32("end", &end)) {324end = std::max(start, end);325if (end - start > MAX_RANGE * 4)326end = start + MAX_RANGE * 4;327// Let's assume everything is two instructions at most.328disasm_.analyze(start - 4, end - start + 8);329start = disasm_.getStartAddress(start);330if (start == -1)331req.ParamU32("address", &start);332// Correct end and calculate count based on it.333// This accounts for macros as one line, although two instructions.334u32 stop = end;335u32 next = start;336count = 0;337if (stop < start) {338for (next = start; next > stop; next = disasm_.getNthNextAddress(next, 1)) {339count++;340}341}342for (end = next; end < stop && end >= next; end = disasm_.getNthNextAddress(end, 1)) {343count++;344}345} else {346// Error message already sent.347return;348}349350bool displaySymbols = true;351if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))352return;353354JsonWriter &json = req.Respond();355json.pushDict("range");356json.writeUint("start", start);357json.writeUint("end", end);358json.pop();359360json.pushArray("lines");361DisassemblyLineInfo line;362uint32_t addr = start;363for (uint32_t i = 0; i < count; ++i) {364disasm_.getLine(addr, displaySymbols, line, cpuDebug);365WriteDisasmLine(json, line);366addr += line.totalSize;367368// These are pretty long, so let's grease the wheels a bit.369if (i % 50 == 0)370req.Flush();371}372json.pop();373374json.pushArray("branchGuides");375auto branchGuides = disasm_.getBranchLines(start, end - start);376for (auto bl : branchGuides)377WriteBranchGuide(json, bl);378json.pop();379}380381// Search disassembly for some text (memory.searchDisasm)382//383// Parameters:384// - thread: optional number indicating the thread id (may not affect search much.)385// - address: starting address as a number.386// - end: optional end address as a number (otherwise uses start address.)387// - match: string to search for.388// - displaySymbols: optional, specify false to hide symbols in the searched parameters.389//390// Response (same event name):391// - address: number address of match or null if none was found.392void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {393if (!currentDebugMIPS->isAlive() || !Memory::IsActive())394return req.Fail("CPU not started");395auto cpuDebug = CPUFromRequest(req);396if (!cpuDebug)397return;398399uint32_t start;400if (!req.ParamU32("address", &start))401return;402uint32_t end = start;403if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))404return;405std::string match;406if (!req.ParamString("match", &match))407return;408bool displaySymbols = true;409if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))410return;411412bool loopSearch = end <= start;413start = RoundMemAddressUp(start);414if ((end <= start) != loopSearch) {415// We must've passed end by rounding up.416JsonWriter &json = req.Respond();417json.writeNull("address");418return;419}420421// We do this after the check in case both were in unused memory.422end = RoundMemAddressUp(end);423424std::transform(match.begin(), match.end(), match.begin(), ::tolower);425426DisassemblyLineInfo line;427bool found = false;428uint32_t addr = start;429do {430disasm_.getLine(addr, displaySymbols, line, cpuDebug);431const std::string addressSymbol = g_symbolMap->GetLabelString(addr);432433std::string mergeForSearch;434// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.435mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());436437sprintf(&mergeForSearch[0], "%08x ", addr);438auto inserter = mergeForSearch.begin() + 9;439if (!addressSymbol.empty()) {440inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);441*inserter++ = ':';442*inserter++ = ' ';443}444inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);445*inserter++ = ' ';446inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);447448if (mergeForSearch.find(match) != mergeForSearch.npos) {449found = true;450break;451}452453addr = RoundMemAddressUp(addr + line.totalSize);454} while (addr != end);455456JsonWriter &json = req.Respond();457if (found)458json.writeUint("address", addr);459else460json.writeNull("address");461}462463// Assemble an instruction (memory.assemble)464//465// Parameters:466// - address: number indicating the address to write to.467// - code: string containing the instruction to assemble.468//469// Response (same event name):470// - encoding: resulting encoding at this address. Always returns one value, even for macros.471void WebSocketDisasmState::Assemble(DebuggerRequest &req) {472if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {473return req.Fail("CPU not started");474}475476uint32_t address;477if (!req.ParamU32("address", &address))478return;479std::string code;480if (!req.ParamString("code", &code))481return;482483if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))484return req.Fail(StringFromFormat("Could not assemble: %s", MIPSAsm::GetAssembleError().c_str()));485486JsonWriter &json = req.Respond();487Reporting::NotifyDebugger();488json.writeUint("encoding", Memory::Read_Instruction(address).encoding);489}490491492