Path: blob/master/Core/Debugger/WebSocket/DisasmSubscriber.cpp
5654 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"] = [p](DebuggerRequest &req) { p->Base(req); };55map["memory.disasm"] = [p](DebuggerRequest &req) { p->Disasm(req); };56map["memory.searchDisasm"] = [p](DebuggerRequest &req) { p->SearchDisasm(req); };57map["memory.assemble"] = [p](DebuggerRequest &req) { p->Assemble(req); };58return p;59}6061static DebugInterface *CPUFromRequest(DebuggerRequest &req) {62if (!req.HasParam("thread"))63return currentDebugMIPS;6465u32 uid;66if (!req.ParamU32("thread", &uid))67return nullptr;6869DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);70if (!cpuDebug)71req.Fail("Thread could not be found");72return cpuDebug;73}7475void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {76u32 addr = l.info.opcodeAddress;77json.pushDict();78if (l.type == DISTYPE_OPCODE)79json.writeString("type", "opcode");80else if (l.type == DISTYPE_MACRO)81json.writeString("type", "macro");82else if (l.type == DISTYPE_DATA)83json.writeString("type", "data");84else if (l.type == DISTYPE_OTHER)85json.writeString("type", "other");8687json.writeUint("address", addr);88json.writeInt("addressSize", l.totalSize);89json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);90if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {91json.pushArray("macroEncoding");92for (u32 off = 0; off < l.totalSize; off += 4) {93json.writeUint(Memory::Read_Instruction(addr + off).encoding);94}95json.pop();96} else {97json.writeNull("macroEncoding");98}99int c = currentDebugMIPS->getColor(addr, false) & 0x00FFFFFF;100json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));101json.writeString("name", l.name);102json.writeString("params", l.params);103104const std::string addressSymbol = g_symbolMap->GetLabelString(addr);105if (addressSymbol.empty())106json.writeNull("symbol");107else108json.writeString("symbol", addressSymbol);109110const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);111const std::string funcName = g_symbolMap->GetLabelString(funcAddress);112if (funcName.empty())113json.writeNull("function");114else115json.writeString("function", funcName);116117if (l.type == DISTYPE_DATA) {118u32 dataStart = g_symbolMap->GetDataStart(addr);119if (dataStart == -1)120dataStart = addr;121const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);122123json.pushDict("dataSymbol");124json.writeUint("start", dataStart);125if (dataLabel.empty())126json.writeNull("label");127else128json.writeString("label", dataLabel);129json.pop();130} else {131json.writeNull("dataSymbol");132}133134bool enabled = false;135int breakpointOffset = -1;136for (u32 i = 0; i < l.totalSize; i += 4) {137if (g_breakpoints.IsAddressBreakPoint(addr + i, &enabled))138breakpointOffset = i;139if (breakpointOffset != -1 && enabled)140break;141}142// TODO: Account for bp inside macro?143if (breakpointOffset != -1) {144json.pushDict("breakpoint");145json.writeBool("enabled", enabled);146json.writeUint("address", addr + breakpointOffset);147auto cond = g_breakpoints.GetBreakPointCondition(addr + breakpointOffset);148if (cond)149json.writeString("condition", cond->expressionString);150else151json.writeNull("condition");152json.pop();153} else {154json.writeNull("breakpoint");155}156157// This is always the current execution's PC.158json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);159if (l.info.isBranch) {160json.pushDict("branch");161std::string targetSymbol;162if (!l.info.isBranchToRegister) {163targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);164json.writeUint("targetAddress", l.info.branchTarget);165json.writeNull("register");166} else {167json.writeNull("targetAddress");168json.writeInt("register", l.info.branchRegisterNum);169}170json.writeBool("isLinked", l.info.isLinkedBranch);171json.writeBool("isLikely", l.info.isLikelyBranch);172if (targetSymbol.empty())173json.writeNull("symbol");174else175json.writeString("symbol", targetSymbol);176json.pop();177} else {178json.writeNull("branch");179}180181if (l.info.hasRelevantAddress) {182json.pushDict("relevantData");183json.writeUint("address", l.info.relevantAddress);184if (Memory::IsValidRange(l.info.relevantAddress, 4))185json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));186else187json.writeNull("uintValue");188if (IsLikelyStringAt(l.info.relevantAddress))189json.writeString("stringValue", Memory::GetCharPointer(l.info.relevantAddress));190else191json.writeNull("stringValue");192json.pop();193} else {194json.writeNull("relevantData");195}196197if (l.info.isConditional)198json.writeBool("conditionMet", l.info.conditionMet);199else200json.writeNull("conditionMet");201202if (l.info.isDataAccess) {203json.pushDict("dataAccess");204json.writeUint("address", l.info.dataAddress);205json.writeInt("size", l.info.dataSize);206207std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);208std::string valueSymbol;209if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))210json.writeNull("uintValue");211else if (l.info.dataSize == 1)212json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));213else if (l.info.dataSize == 2)214json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));215else if (l.info.dataSize >= 4) {216u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);217valueSymbol = g_symbolMap->GetLabelString(data);218json.writeUint("uintValue", data);219}220221if (!dataSymbol.empty())222json.writeString("symbol", dataSymbol);223else224json.writeNull("symbol");225if (!valueSymbol.empty())226json.writeString("valueSymbol", valueSymbol);227else228json.writeNull("valueSymbol");229json.pop();230} else {231json.writeNull("dataAccess");232}233234json.pop();235}236237void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {238json.pushDict();239json.writeUint("top", l.first);240json.writeUint("bottom", l.second);241if (l.type == LINE_UP)242json.writeString("direction", "up");243else if (l.type == LINE_DOWN)244json.writeString("direction", "down");245else if (l.type == LINE_RIGHT)246json.writeString("direction", "right");247json.writeInt("lane", l.laneIndex);248json.pop();249}250251// Request the current PSP memory base address (memory.base)252//253// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.254//255// No parameters.256//257// Response (same event name):258// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)259void WebSocketDisasmState::Base(DebuggerRequest &req) {260JsonWriter &json = req.Respond();261Reporting::NotifyDebugger();262json.writeString("addressHex", StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));263}264265// Disassemble a range of memory as CPU instructions (memory.disasm)266//267// Parameters (by count):268// - thread: optional number indicating the thread id for branch info.269// - address: number specifying the start address.270// - count: number of lines to return (may be clamped to an internal limit.)271// - displaySymbols: boolean true to show symbol names in instruction params.272//273// Parameters (by end address):274// - thread: optional number indicating the thread id for branch info.275// - address: number specifying the start address.276// - end: number which must be after the start address (may be clamped to an internal limit.)277// - displaySymbols: boolean true to show symbol names in instruction params.278//279// Response (same event name):280// - range: object with result "start" and "end" properties, the addresses actually used.281// (disassembly may have snapped to a nearby instruction.)282// - branchGuides: array of objects:283// - top: the earlier address as a number.284// - bottom: the later address as a number.285// - direction: "up", "down", or "right" depending on the flow of the branch.286// - lane: number index to avoid overlapping guides.287// - lines: array of objects:288// - type: "opcode", "macro", "data", or "other".289// - address: address of first actual instruction.290// - addressSize: bytes used by this line (might be more than 4.)291// - encoding: uint value of actual instruction (may differ from memory read when using jit.)292// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.293// - name: string name of the instruction.294// - params: formatted parameters for the instruction.295// - (other info about the disassembled line.)296void WebSocketDisasmState::Disasm(DebuggerRequest &req) {297if (!currentDebugMIPS->isAlive() || !Memory::IsActive())298return req.Fail("CPU not started");299auto cpuDebug = CPUFromRequest(req);300if (!cpuDebug)301return;302303// In case of client errors, we limit the range to something that won't make us crash.304static const uint32_t MAX_RANGE = 10000;305306uint32_t start, end;307if (!req.ParamU32("address", &start))308return;309uint32_t count = 0;310if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))311return;312if (count != 0) {313count = std::min(count, MAX_RANGE);314// Let's assume everything is two instructions.315g_disassemblyManager.analyze(start - 4, count * 8 + 8);316start = g_disassemblyManager.getStartAddress(start);317if (start == -1)318req.ParamU32("address", &start);319end = g_disassemblyManager.getNthNextAddress(start, count);320} else if (req.ParamU32("end", &end)) {321end = std::max(start, end);322if (end - start > MAX_RANGE * 4)323end = start + MAX_RANGE * 4;324// Let's assume everything is two instructions at most.325g_disassemblyManager.analyze(start - 4, end - start + 8);326start = g_disassemblyManager.getStartAddress(start);327if (start == -1)328req.ParamU32("address", &start);329// Correct end and calculate count based on it.330// This accounts for macros as one line, although two instructions.331u32 stop = end;332u32 next = start;333count = 0;334if (stop < start) {335for (next = start; next > stop; next = g_disassemblyManager.getNthNextAddress(next, 1)) {336count++;337}338}339for (end = next; end < stop && end >= next; end = g_disassemblyManager.getNthNextAddress(end, 1)) {340count++;341}342} else {343// Error message already sent.344return;345}346347bool displaySymbols = true;348if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))349return;350351JsonWriter &json = req.Respond();352json.pushDict("range");353json.writeUint("start", start);354json.writeUint("end", end);355json.pop();356357json.pushArray("lines");358DisassemblyLineInfo line;359uint32_t addr = start;360for (uint32_t i = 0; i < count; ++i) {361g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug);362WriteDisasmLine(json, line);363addr += line.totalSize;364365// These are pretty long, so let's grease the wheels a bit.366if (i % 50 == 0)367req.Flush();368}369json.pop();370371json.pushArray("branchGuides");372auto branchGuides = g_disassemblyManager.getBranchLines(start, end - start);373for (auto bl : branchGuides)374WriteBranchGuide(json, bl);375json.pop();376}377378// Search disassembly for some text (memory.searchDisasm)379//380// Parameters:381// - thread: optional number indicating the thread id (may not affect search much.)382// - address: starting address as a number.383// - end: optional end address as a number (otherwise uses start address.)384// - match: string to search for.385// - displaySymbols: optional, specify false to hide symbols in the searched parameters.386//387// Response (same event name):388// - address: number address of match or null if none was found.389void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {390if (!currentDebugMIPS->isAlive() || !Memory::IsActive())391return req.Fail("CPU not started");392auto cpuDebug = CPUFromRequest(req);393if (!cpuDebug)394return;395396uint32_t start;397if (!req.ParamU32("address", &start))398return;399uint32_t end = start;400if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))401return;402std::string match;403if (!req.ParamString("match", &match))404return;405bool displaySymbols = true;406if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))407return;408409bool loopSearch = end <= start;410start = RoundMemAddressUp(start);411if ((end <= start) != loopSearch) {412// We must've passed end by rounding up.413JsonWriter &json = req.Respond();414json.writeNull("address");415return;416}417418// We do this after the check in case both were in unused memory.419end = RoundMemAddressUp(end);420421std::transform(match.begin(), match.end(), match.begin(), ::tolower);422423DisassemblyLineInfo line;424bool found = false;425uint32_t addr = start;426do {427g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug);428const std::string addressSymbol = g_symbolMap->GetLabelString(addr);429430std::string mergeForSearch;431// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.432mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());433434sprintf(&mergeForSearch[0], "%08x ", addr);435auto inserter = mergeForSearch.begin() + 9;436if (!addressSymbol.empty()) {437inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);438*inserter++ = ':';439*inserter++ = ' ';440}441inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);442*inserter++ = ' ';443inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);444445if (mergeForSearch.find(match) != mergeForSearch.npos) {446found = true;447break;448}449450addr = RoundMemAddressUp(addr + line.totalSize);451} while (addr != end);452453JsonWriter &json = req.Respond();454if (found)455json.writeUint("address", addr);456else457json.writeNull("address");458}459460// Assemble an instruction (memory.assemble)461//462// Parameters:463// - address: number indicating the address to write to.464// - code: string containing the instruction to assemble.465//466// Response (same event name):467// - encoding: resulting encoding at this address. Always returns one value, even for macros.468void WebSocketDisasmState::Assemble(DebuggerRequest &req) {469if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {470return req.Fail("CPU not started");471}472473uint32_t address;474if (!req.ParamU32("address", &address))475return;476std::string code;477if (!req.ParamString("code", &code))478return;479480std::string error;481if (!MipsAssembleOpcode(code, currentDebugMIPS, address, &error)) {482return req.Fail(StringFromFormat("Could not assemble: %s", error.c_str()));483}484485JsonWriter &json = req.Respond();486Reporting::NotifyDebugger();487json.writeUint("encoding", Memory::Read_Instruction(address).encoding);488}489490491