Path: blob/master/Core/Debugger/WebSocket/DisasmSubscriber.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 <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() {36g_disassemblyManager.setCpu(currentDebugMIPS);37}38~WebSocketDisasmState() {39g_disassemblyManager.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);50};5152DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) {53auto p = new WebSocketDisasmState();54map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1);55map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1);56map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1);57map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1);5859return p;60}6162static DebugInterface *CPUFromRequest(DebuggerRequest &req) {63if (!req.HasParam("thread"))64return currentDebugMIPS;6566u32 uid;67if (!req.ParamU32("thread", &uid))68return nullptr;6970DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);71if (!cpuDebug)72req.Fail("Thread could not be found");73return cpuDebug;74}7576void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {77u32 addr = l.info.opcodeAddress;78json.pushDict();79if (l.type == DISTYPE_OPCODE)80json.writeString("type", "opcode");81else if (l.type == DISTYPE_MACRO)82json.writeString("type", "macro");83else if (l.type == DISTYPE_DATA)84json.writeString("type", "data");85else if (l.type == DISTYPE_OTHER)86json.writeString("type", "other");8788json.writeUint("address", addr);89json.writeInt("addressSize", l.totalSize);90json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);91if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {92json.pushArray("macroEncoding");93for (u32 off = 0; off < l.totalSize; off += 4) {94json.writeUint(Memory::Read_Instruction(addr + off).encoding);95}96json.pop();97} else {98json.writeNull("macroEncoding");99}100int c = currentDebugMIPS->getColor(addr, false) & 0x00FFFFFF;101json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));102json.writeString("name", l.name);103json.writeString("params", l.params);104105const std::string addressSymbol = g_symbolMap->GetLabelString(addr);106if (addressSymbol.empty())107json.writeNull("symbol");108else109json.writeString("symbol", addressSymbol);110111const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);112const std::string funcName = g_symbolMap->GetLabelString(funcAddress);113if (funcName.empty())114json.writeNull("function");115else116json.writeString("function", funcName);117118if (l.type == DISTYPE_DATA) {119u32 dataStart = g_symbolMap->GetDataStart(addr);120if (dataStart == -1)121dataStart = addr;122const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);123124json.pushDict("dataSymbol");125json.writeUint("start", dataStart);126if (dataLabel.empty())127json.writeNull("label");128else129json.writeString("label", dataLabel);130json.pop();131} else {132json.writeNull("dataSymbol");133}134135bool enabled = false;136int breakpointOffset = -1;137for (u32 i = 0; i < l.totalSize; i += 4) {138if (g_breakpoints.IsAddressBreakPoint(addr + i, &enabled))139breakpointOffset = i;140if (breakpointOffset != -1 && enabled)141break;142}143// TODO: Account for bp inside macro?144if (breakpointOffset != -1) {145json.pushDict("breakpoint");146json.writeBool("enabled", enabled);147json.writeUint("address", addr + breakpointOffset);148auto cond = g_breakpoints.GetBreakPointCondition(addr + breakpointOffset);149if (cond)150json.writeString("condition", cond->expressionString);151else152json.writeNull("condition");153json.pop();154} else {155json.writeNull("breakpoint");156}157158// This is always the current execution's PC.159json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);160if (l.info.isBranch) {161json.pushDict("branch");162std::string targetSymbol;163if (!l.info.isBranchToRegister) {164targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);165json.writeUint("targetAddress", l.info.branchTarget);166json.writeNull("register");167} else {168json.writeNull("targetAddress");169json.writeInt("register", l.info.branchRegisterNum);170}171json.writeBool("isLinked", l.info.isLinkedBranch);172json.writeBool("isLikely", l.info.isLikelyBranch);173if (targetSymbol.empty())174json.writeNull("symbol");175else176json.writeString("symbol", targetSymbol);177json.pop();178} else {179json.writeNull("branch");180}181182if (l.info.hasRelevantAddress) {183json.pushDict("relevantData");184json.writeUint("address", l.info.relevantAddress);185if (Memory::IsValidRange(l.info.relevantAddress, 4))186json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));187else188json.writeNull("uintValue");189if (IsLikelyStringAt(l.info.relevantAddress))190json.writeString("stringValue", Memory::GetCharPointer(l.info.relevantAddress));191else192json.writeNull("stringValue");193json.pop();194} else {195json.writeNull("relevantData");196}197198if (l.info.isConditional)199json.writeBool("conditionMet", l.info.conditionMet);200else201json.writeNull("conditionMet");202203if (l.info.isDataAccess) {204json.pushDict("dataAccess");205json.writeUint("address", l.info.dataAddress);206json.writeInt("size", l.info.dataSize);207208std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);209std::string valueSymbol;210if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))211json.writeNull("uintValue");212else if (l.info.dataSize == 1)213json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));214else if (l.info.dataSize == 2)215json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));216else if (l.info.dataSize >= 4) {217u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);218valueSymbol = g_symbolMap->GetLabelString(data);219json.writeUint("uintValue", data);220}221222if (!dataSymbol.empty())223json.writeString("symbol", dataSymbol);224else225json.writeNull("symbol");226if (!valueSymbol.empty())227json.writeString("valueSymbol", valueSymbol);228else229json.writeNull("valueSymbol");230json.pop();231} else {232json.writeNull("dataAccess");233}234235json.pop();236}237238void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {239json.pushDict();240json.writeUint("top", l.first);241json.writeUint("bottom", l.second);242if (l.type == LINE_UP)243json.writeString("direction", "up");244else if (l.type == LINE_DOWN)245json.writeString("direction", "down");246else if (l.type == LINE_RIGHT)247json.writeString("direction", "right");248json.writeInt("lane", l.laneIndex);249json.pop();250}251252// Request the current PSP memory base address (memory.base)253//254// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.255//256// No parameters.257//258// Response (same event name):259// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)260void WebSocketDisasmState::Base(DebuggerRequest &req) {261JsonWriter &json = req.Respond();262Reporting::NotifyDebugger();263json.writeString("addressHex", StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));264}265266// Disassemble a range of memory as CPU instructions (memory.disasm)267//268// Parameters (by count):269// - thread: optional number indicating the thread id for branch info.270// - address: number specifying the start address.271// - count: number of lines to return (may be clamped to an internal limit.)272// - displaySymbols: boolean true to show symbol names in instruction params.273//274// Parameters (by end address):275// - thread: optional number indicating the thread id for branch info.276// - address: number specifying the start address.277// - end: number which must be after the start address (may be clamped to an internal limit.)278// - displaySymbols: boolean true to show symbol names in instruction params.279//280// Response (same event name):281// - range: object with result "start" and "end" properties, the addresses actually used.282// (disassembly may have snapped to a nearby instruction.)283// - branchGuides: array of objects:284// - top: the earlier address as a number.285// - bottom: the later address as a number.286// - direction: "up", "down", or "right" depending on the flow of the branch.287// - lane: number index to avoid overlapping guides.288// - lines: array of objects:289// - type: "opcode", "macro", "data", or "other".290// - address: address of first actual instruction.291// - addressSize: bytes used by this line (might be more than 4.)292// - encoding: uint value of actual instruction (may differ from memory read when using jit.)293// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.294// - name: string name of the instruction.295// - params: formatted parameters for the instruction.296// - (other info about the disassembled line.)297void WebSocketDisasmState::Disasm(DebuggerRequest &req) {298if (!currentDebugMIPS->isAlive() || !Memory::IsActive())299return req.Fail("CPU not started");300auto cpuDebug = CPUFromRequest(req);301if (!cpuDebug)302return;303304// In case of client errors, we limit the range to something that won't make us crash.305static const uint32_t MAX_RANGE = 10000;306307uint32_t start, end;308if (!req.ParamU32("address", &start))309return;310uint32_t count = 0;311if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))312return;313if (count != 0) {314count = std::min(count, MAX_RANGE);315// Let's assume everything is two instructions.316g_disassemblyManager.analyze(start - 4, count * 8 + 8);317start = g_disassemblyManager.getStartAddress(start);318if (start == -1)319req.ParamU32("address", &start);320end = g_disassemblyManager.getNthNextAddress(start, count);321} else if (req.ParamU32("end", &end)) {322end = std::max(start, end);323if (end - start > MAX_RANGE * 4)324end = start + MAX_RANGE * 4;325// Let's assume everything is two instructions at most.326g_disassemblyManager.analyze(start - 4, end - start + 8);327start = g_disassemblyManager.getStartAddress(start);328if (start == -1)329req.ParamU32("address", &start);330// Correct end and calculate count based on it.331// This accounts for macros as one line, although two instructions.332u32 stop = end;333u32 next = start;334count = 0;335if (stop < start) {336for (next = start; next > stop; next = g_disassemblyManager.getNthNextAddress(next, 1)) {337count++;338}339}340for (end = next; end < stop && end >= next; end = g_disassemblyManager.getNthNextAddress(end, 1)) {341count++;342}343} else {344// Error message already sent.345return;346}347348bool displaySymbols = true;349if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))350return;351352JsonWriter &json = req.Respond();353json.pushDict("range");354json.writeUint("start", start);355json.writeUint("end", end);356json.pop();357358json.pushArray("lines");359DisassemblyLineInfo line;360uint32_t addr = start;361for (uint32_t i = 0; i < count; ++i) {362g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug);363WriteDisasmLine(json, line);364addr += line.totalSize;365366// These are pretty long, so let's grease the wheels a bit.367if (i % 50 == 0)368req.Flush();369}370json.pop();371372json.pushArray("branchGuides");373auto branchGuides = g_disassemblyManager.getBranchLines(start, end - start);374for (auto bl : branchGuides)375WriteBranchGuide(json, bl);376json.pop();377}378379// Search disassembly for some text (memory.searchDisasm)380//381// Parameters:382// - thread: optional number indicating the thread id (may not affect search much.)383// - address: starting address as a number.384// - end: optional end address as a number (otherwise uses start address.)385// - match: string to search for.386// - displaySymbols: optional, specify false to hide symbols in the searched parameters.387//388// Response (same event name):389// - address: number address of match or null if none was found.390void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {391if (!currentDebugMIPS->isAlive() || !Memory::IsActive())392return req.Fail("CPU not started");393auto cpuDebug = CPUFromRequest(req);394if (!cpuDebug)395return;396397uint32_t start;398if (!req.ParamU32("address", &start))399return;400uint32_t end = start;401if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))402return;403std::string match;404if (!req.ParamString("match", &match))405return;406bool displaySymbols = true;407if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))408return;409410bool loopSearch = end <= start;411start = RoundMemAddressUp(start);412if ((end <= start) != loopSearch) {413// We must've passed end by rounding up.414JsonWriter &json = req.Respond();415json.writeNull("address");416return;417}418419// We do this after the check in case both were in unused memory.420end = RoundMemAddressUp(end);421422std::transform(match.begin(), match.end(), match.begin(), ::tolower);423424DisassemblyLineInfo line;425bool found = false;426uint32_t addr = start;427do {428g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug);429const std::string addressSymbol = g_symbolMap->GetLabelString(addr);430431std::string mergeForSearch;432// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.433mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());434435sprintf(&mergeForSearch[0], "%08x ", addr);436auto inserter = mergeForSearch.begin() + 9;437if (!addressSymbol.empty()) {438inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);439*inserter++ = ':';440*inserter++ = ' ';441}442inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);443*inserter++ = ' ';444inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);445446if (mergeForSearch.find(match) != mergeForSearch.npos) {447found = true;448break;449}450451addr = RoundMemAddressUp(addr + line.totalSize);452} while (addr != end);453454JsonWriter &json = req.Respond();455if (found)456json.writeUint("address", addr);457else458json.writeNull("address");459}460461// Assemble an instruction (memory.assemble)462//463// Parameters:464// - address: number indicating the address to write to.465// - code: string containing the instruction to assemble.466//467// Response (same event name):468// - encoding: resulting encoding at this address. Always returns one value, even for macros.469void WebSocketDisasmState::Assemble(DebuggerRequest &req) {470if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {471return req.Fail("CPU not started");472}473474uint32_t address;475if (!req.ParamU32("address", &address))476return;477std::string code;478if (!req.ParamString("code", &code))479return;480481if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))482return req.Fail(StringFromFormat("Could not assemble: %s", MIPSAsm::GetAssembleError().c_str()));483484JsonWriter &json = req.Respond();485Reporting::NotifyDebugger();486json.writeUint("encoding", Memory::Read_Instruction(address).encoding);487}488489490