mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Also move colorutil.cpp/h linking build fix experiment Delete a bunch of unused CMakeLists.txt files CMakeLists.txt linking fix Don't include NativeApp.h from any headers. Android.mk buildfix Half of the UWP fix Buildfix Minor project file cleanup Buildfixes Guess what? More buildfixes!
480 lines
16 KiB
C++
480 lines
16 KiB
C++
// Copyright (c) 2018- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
#include "Core/Debugger/DisassemblyManager.h"
|
|
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
|
|
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/MIPSAsm.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
|
|
class WebSocketDisasmState : public DebuggerSubscriber {
|
|
public:
|
|
WebSocketDisasmState() {
|
|
disasm_.setCpu(currentDebugMIPS);
|
|
}
|
|
~WebSocketDisasmState() override {
|
|
disasm_.clear();
|
|
}
|
|
|
|
void Base(DebuggerRequest &req);
|
|
void Disasm(DebuggerRequest &req);
|
|
void SearchDisasm(DebuggerRequest &req);
|
|
void Assemble(DebuggerRequest &req);
|
|
|
|
protected:
|
|
void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l);
|
|
void WriteBranchGuide(JsonWriter &json, const BranchLine &l);
|
|
uint32_t RoundAddressUp(uint32_t addr);
|
|
|
|
DisassemblyManager disasm_;
|
|
};
|
|
|
|
DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) {
|
|
auto p = new WebSocketDisasmState();
|
|
map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1);
|
|
map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1);
|
|
map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1);
|
|
map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1);
|
|
|
|
return p;
|
|
}
|
|
|
|
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
|
|
if (!req.HasParam("thread"))
|
|
return currentDebugMIPS;
|
|
|
|
u32 uid;
|
|
if (!req.ParamU32("thread", &uid))
|
|
return nullptr;
|
|
|
|
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
|
|
if (!cpuDebug)
|
|
req.Fail("Thread could not be found");
|
|
return cpuDebug;
|
|
}
|
|
|
|
void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {
|
|
u32 addr = l.info.opcodeAddress;
|
|
json.pushDict();
|
|
if (l.type == DISTYPE_OPCODE)
|
|
json.writeString("type", "opcode");
|
|
else if (l.type == DISTYPE_MACRO)
|
|
json.writeString("type", "macro");
|
|
else if (l.type == DISTYPE_DATA)
|
|
json.writeString("type", "data");
|
|
else if (l.type == DISTYPE_OTHER)
|
|
json.writeString("type", "other");
|
|
|
|
json.writeUint("address", addr);
|
|
json.writeInt("addressSize", l.totalSize);
|
|
json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);
|
|
if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {
|
|
json.pushArray("macroEncoding");
|
|
for (u32 off = 0; off < l.totalSize; off += 4) {
|
|
json.writeUint(Memory::Read_Instruction(addr + off).encoding);
|
|
}
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("macroEncoding");
|
|
}
|
|
int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF;
|
|
json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));
|
|
json.writeString("name", l.name);
|
|
json.writeString("params", l.params);
|
|
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
|
|
if (addressSymbol.empty())
|
|
json.writeNull("symbol");
|
|
else
|
|
json.writeString("symbol", addressSymbol);
|
|
|
|
const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);
|
|
const std::string funcName = g_symbolMap->GetLabelString(funcAddress);
|
|
if (funcName.empty())
|
|
json.writeNull("function");
|
|
else
|
|
json.writeString("function", funcName);
|
|
|
|
if (l.type == DISTYPE_DATA) {
|
|
u32 dataStart = g_symbolMap->GetDataStart(addr);
|
|
if (dataStart == -1)
|
|
dataStart = addr;
|
|
const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);
|
|
|
|
json.pushDict("dataSymbol");
|
|
json.writeUint("start", dataStart);
|
|
if (dataLabel.empty())
|
|
json.writeNull("label");
|
|
else
|
|
json.writeString("label", dataLabel);
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("dataSymbol");
|
|
}
|
|
|
|
bool enabled;
|
|
// TODO: Account for bp inside macro?
|
|
if (CBreakPoints::IsAddressBreakPoint(addr, &enabled)) {
|
|
json.pushDict("breakpoint");
|
|
json.writeBool("enabled", enabled);
|
|
auto cond = CBreakPoints::GetBreakPointCondition(addr);
|
|
if (cond)
|
|
json.writeString("condition", cond->expressionString);
|
|
else
|
|
json.writeNull("condition");
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("breakpoint");
|
|
}
|
|
|
|
// This is always the current execution's PC.
|
|
json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);
|
|
if (l.info.isBranch) {
|
|
json.pushDict("branch");
|
|
std::string targetSymbol;
|
|
if (!l.info.isBranchToRegister) {
|
|
targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);
|
|
json.writeUint("targetAddress", l.info.branchTarget);
|
|
json.writeNull("register");
|
|
} else {
|
|
json.writeNull("targetAddress");
|
|
json.writeInt("register", l.info.branchRegisterNum);
|
|
}
|
|
json.writeBool("isLinked", l.info.isLinkedBranch);
|
|
json.writeBool("isLikely", l.info.isLikelyBranch);
|
|
if (targetSymbol.empty())
|
|
json.writeNull("symbol");
|
|
else
|
|
json.writeString("symbol", targetSymbol);
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("branch");
|
|
}
|
|
|
|
if (l.info.hasRelevantAddress) {
|
|
json.pushDict("relevantData");
|
|
json.writeUint("address", l.info.relevantAddress);
|
|
if (Memory::IsValidRange(l.info.relevantAddress, 4))
|
|
json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));
|
|
else
|
|
json.writeNull("uintValue");
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("relevantData");
|
|
}
|
|
|
|
if (l.info.isConditional)
|
|
json.writeBool("conditionMet", l.info.conditionMet);
|
|
else
|
|
json.writeNull("conditionMet");
|
|
|
|
if (l.info.isDataAccess) {
|
|
json.pushDict("dataAccess");
|
|
json.writeUint("address", l.info.dataAddress);
|
|
json.writeInt("size", l.info.dataSize);
|
|
|
|
std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);
|
|
std::string valueSymbol;
|
|
if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))
|
|
json.writeNull("uintValue");
|
|
else if (l.info.dataSize == 1)
|
|
json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));
|
|
else if (l.info.dataSize == 2)
|
|
json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));
|
|
else if (l.info.dataSize >= 4) {
|
|
u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);
|
|
valueSymbol = g_symbolMap->GetLabelString(data);
|
|
json.writeUint("uintValue", data);
|
|
}
|
|
|
|
if (!dataSymbol.empty())
|
|
json.writeString("symbol", dataSymbol);
|
|
else
|
|
json.writeNull("symbol");
|
|
if (!valueSymbol.empty())
|
|
json.writeString("valueSymbol", valueSymbol);
|
|
else
|
|
json.writeNull("valueSymbol");
|
|
json.pop();
|
|
} else {
|
|
json.writeNull("dataAccess");
|
|
}
|
|
|
|
json.pop();
|
|
}
|
|
|
|
void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {
|
|
json.pushDict();
|
|
json.writeUint("top", l.first);
|
|
json.writeUint("bottom", l.second);
|
|
if (l.type == LINE_UP)
|
|
json.writeString("direction", "up");
|
|
else if (l.type == LINE_DOWN)
|
|
json.writeString("direction", "down");
|
|
else if (l.type == LINE_RIGHT)
|
|
json.writeString("direction", "right");
|
|
json.writeInt("lane", l.laneIndex);
|
|
json.pop();
|
|
}
|
|
|
|
uint32_t WebSocketDisasmState::RoundAddressUp(uint32_t addr) {
|
|
if (addr < PSP_GetScratchpadMemoryBase())
|
|
return PSP_GetScratchpadMemoryBase();
|
|
else if (addr >= PSP_GetScratchpadMemoryEnd() && addr < PSP_GetVidMemBase())
|
|
return PSP_GetVidMemBase();
|
|
else if (addr >= PSP_GetVidMemEnd() && addr < PSP_GetKernelMemoryBase())
|
|
return PSP_GetKernelMemoryBase();
|
|
else if (addr >= PSP_GetUserMemoryEnd())
|
|
return PSP_GetScratchpadMemoryBase();
|
|
return addr;
|
|
}
|
|
|
|
// Request the current PSP memory base address (memory.base)
|
|
//
|
|
// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.
|
|
//
|
|
// No parameters.
|
|
//
|
|
// Response (same event name):
|
|
// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)
|
|
void WebSocketDisasmState::Base(DebuggerRequest &req) {
|
|
JsonWriter &json = req.Respond();
|
|
json.writeString("addressHex", StringFromFormat("%016llx", Memory::base));
|
|
}
|
|
|
|
// Disassemble a range of memory as CPU instructions (memory.disasm)
|
|
//
|
|
// Parameters (by count):
|
|
// - thread: optional number indicating the thread id for branch info.
|
|
// - address: number specifying the start address.
|
|
// - count: number of lines to return (may be clamped to an internal limit.)
|
|
// - displaySymbols: boolean true to show symbol names in instruction params.
|
|
//
|
|
// Parameters (by end address):
|
|
// - thread: optional number indicating the thread id for branch info.
|
|
// - address: number specifying the start address.
|
|
// - end: number which must be after the start address (may be clamped to an internal limit.)
|
|
// - displaySymbols: boolean true to show symbol names in instruction params.
|
|
//
|
|
// Response (same event name):
|
|
// - range: object with result "start" and "end" properties, the addresses actually used.
|
|
// (disassembly may have snapped to a nearby instruction.)
|
|
// - branchGuides: array of objects:
|
|
// - top: the earlier address as a number.
|
|
// - bottom: the later address as a number.
|
|
// - direction: "up", "down", or "right" depending on the flow of the branch.
|
|
// - lane: number index to avoid overlapping guides.
|
|
// - lines: array of objects:
|
|
// - type: "opcode", "macro", "data", or "other".
|
|
// - address: address of first actual instruction.
|
|
// - addressSize: bytes used by this line (might be more than 4.)
|
|
// - encoding: uint value of actual instruction (may differ from memory read when using jit.)
|
|
// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.
|
|
// - name: string name of the instruction.
|
|
// - params: formatted parameters for the instruction.
|
|
// - (other info about the disassembled line.)
|
|
void WebSocketDisasmState::Disasm(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
|
|
return req.Fail("CPU not started");
|
|
auto cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
// In case of client errors, we limit the range to something that won't make us crash.
|
|
static const uint32_t MAX_RANGE = 10000;
|
|
|
|
uint32_t start, end;
|
|
if (!req.ParamU32("address", &start))
|
|
return;
|
|
uint32_t count = 0;
|
|
if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))
|
|
return;
|
|
if (count != 0) {
|
|
count = std::min(count, MAX_RANGE);
|
|
// Let's assume everything is two instructions.
|
|
disasm_.analyze(start - 4, count * 8 + 8);
|
|
start = disasm_.getStartAddress(start);
|
|
if (start == -1)
|
|
req.ParamU32("address", &start);
|
|
end = disasm_.getNthNextAddress(start, count);
|
|
} else if (req.ParamU32("end", &end)) {
|
|
end = std::min(std::max(start, end), start + MAX_RANGE * 4);
|
|
// Let's assume everything is two instructions.
|
|
disasm_.analyze(start - 4, end - start + 8);
|
|
start = disasm_.getStartAddress(start);
|
|
if (start == -1)
|
|
req.ParamU32("address", &start);
|
|
// Correct end and calculate count based on it.
|
|
// This accounts for macros as one line, although two instructions.
|
|
u32 stop = end;
|
|
count = 0;
|
|
for (end = start; end < stop; end = disasm_.getNthNextAddress(end, 1)) {
|
|
count++;
|
|
}
|
|
} else {
|
|
// Error message already sent.
|
|
return;
|
|
}
|
|
|
|
bool displaySymbols = true;
|
|
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
|
|
return;
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.pushDict("range");
|
|
json.writeUint("start", start);
|
|
json.writeUint("end", end);
|
|
json.pop();
|
|
|
|
json.pushArray("lines");
|
|
DisassemblyLineInfo line;
|
|
uint32_t addr = start;
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
|
|
WriteDisasmLine(json, line);
|
|
addr += line.totalSize;
|
|
|
|
// These are pretty long, so let's grease the wheels a bit.
|
|
if (i % 50 == 0)
|
|
req.Flush();
|
|
}
|
|
json.pop();
|
|
|
|
json.pushArray("branchGuides");
|
|
auto branchGuides = disasm_.getBranchLines(start, end - start);
|
|
for (auto bl : branchGuides)
|
|
WriteBranchGuide(json, bl);
|
|
json.pop();
|
|
}
|
|
|
|
// Search disassembly for some text (cpu.searchDisasm)
|
|
//
|
|
// Parameters:
|
|
// - thread: optional number indicating the thread id (may not affect search much.)
|
|
// - address: starting address as a number.
|
|
// - end: optional end address as a number (otherwise uses start.)
|
|
// - match: string to search for.
|
|
// - displaySymbols: optional, specify false to hide symbols in the searched parameters.
|
|
//
|
|
// Response (same event name):
|
|
// - address: number address of match or null if none was found.
|
|
void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
|
|
return req.Fail("CPU not started");
|
|
auto cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
uint32_t start;
|
|
if (!req.ParamU32("address", &start))
|
|
return;
|
|
uint32_t end = start;
|
|
if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))
|
|
return;
|
|
std::string match;
|
|
if (!req.ParamString("match", &match))
|
|
return;
|
|
bool displaySymbols = true;
|
|
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
|
|
return;
|
|
|
|
bool loopSearch = end <= start;
|
|
start = RoundAddressUp(start);
|
|
if ((end <= start) != loopSearch) {
|
|
// We must've passed end by rounding up.
|
|
JsonWriter &json = req.Respond();
|
|
json.writeNull("address");
|
|
return;
|
|
}
|
|
|
|
// We do this after the check in case both were in unused memory.
|
|
end = RoundAddressUp(end);
|
|
|
|
std::transform(match.begin(), match.end(), match.begin(), ::tolower);
|
|
|
|
DisassemblyLineInfo line;
|
|
bool found = false;
|
|
uint32_t addr = start;
|
|
do {
|
|
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
|
|
|
|
std::string mergeForSearch;
|
|
// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.
|
|
mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());
|
|
|
|
sprintf(&mergeForSearch[0], "%08x ", addr);
|
|
auto inserter = mergeForSearch.begin() + 9;
|
|
if (!addressSymbol.empty()) {
|
|
inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);
|
|
*inserter++ = ':';
|
|
*inserter++ = ' ';
|
|
}
|
|
inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);
|
|
*inserter++ = ' ';
|
|
inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);
|
|
|
|
if (mergeForSearch.find(match) != mergeForSearch.npos) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
addr = RoundAddressUp(addr + line.totalSize);
|
|
} while (addr != end);
|
|
|
|
JsonWriter &json = req.Respond();
|
|
if (found)
|
|
json.writeUint("address", addr);
|
|
else
|
|
json.writeNull("address");
|
|
}
|
|
|
|
// Assemble an instruction (cpu.assemble)
|
|
//
|
|
// Parameters:
|
|
// - address: number indicating the address to write to.
|
|
// - code: string containing the instruction to assemble.
|
|
//
|
|
// Response (same event name):
|
|
// - encoding: resulting encoding at this address. Always returns one value, even for macros.
|
|
void WebSocketDisasmState::Assemble(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {
|
|
return req.Fail("CPU not started");
|
|
}
|
|
|
|
uint32_t address;
|
|
if (!req.ParamU32("address", &address))
|
|
return;
|
|
std::string code;
|
|
if (!req.ParamString("code", &code))
|
|
return;
|
|
|
|
if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))
|
|
return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str()));
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.writeUint("encoding", Memory::Read_Instruction(address).encoding);
|
|
}
|