mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 23:40:29 +02:00
The libretro core is the frontend, and the headless tools are the test and debugging hosts. The Windows, UWP, Android, iOS, macOS and SDL apps, the UI framework and imgui go, along with what only they used: the Win32 debugger hooks (g_frameMutex, the shutdown lock), JNI thread attachment, the Android text drawer, RAIntegration, Discord, recent files, game installs, tilt input, the audio resamplers, the control mapper, memstick moving and the remote ISO and upload server modes. The web server keeps the debugger. The MSVC, Gradle and ndk-build projects go too.
1135 lines
29 KiB
C++
1135 lines
29 KiB
C++
// Copyright (c) 2012- 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 "ppsspp_config.h"
|
|
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <map>
|
|
|
|
#include "ext/xxhash.h"
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/Math/math_util.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/System.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/MIPS/MIPSCodeUtils.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/Debugger/DebugInterface.h"
|
|
#include "Core/Debugger/SymbolMap.h"
|
|
#include "Core/Debugger/DisassemblyManager.h"
|
|
|
|
DisassemblyManager g_disassemblyManager;
|
|
|
|
void ClearDisassemblyCache() {
|
|
g_disassemblyManager.clear();
|
|
}
|
|
|
|
bool isInInterval(u32 start, u32 size, u32 value) {
|
|
return start <= value && value <= (start+size-1);
|
|
}
|
|
|
|
bool IsLikelyStringAt(uint32_t addr) {
|
|
uint32_t maxLen = Memory::ClampValidSizeAt(addr, 128);
|
|
if (maxLen <= 1)
|
|
return false;
|
|
const char *p = Memory::GetCharPointer(addr);
|
|
// If there's no terminator nearby, let's say no.
|
|
if (memchr(p, 0, maxLen) == nullptr)
|
|
return false;
|
|
|
|
// Allow tabs and newlines.
|
|
static constexpr bool validControl[] = {
|
|
false, false, false, false, false, false, false, false,
|
|
false, true, true, true, false, true, false, false,
|
|
false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false,
|
|
};
|
|
|
|
// Check that there's some bytes before the terminator that look like a string.
|
|
UTF8 utf(p);
|
|
if (utf.end())
|
|
return false;
|
|
|
|
char verify[4];
|
|
while (!utf.end()) {
|
|
if (utf.invalid())
|
|
return false;
|
|
|
|
int pos = utf.byteIndex();
|
|
uint32_t c = utf.next();
|
|
int len = UTF8::encode(verify, c);
|
|
// Our decoder is a bit lax, so let's verify this is a normal encoding.
|
|
// This prevents us from trying to output invalid encodings in the debugger.
|
|
if (memcmp(p + pos, verify, len) != 0 || pos + len != utf.byteIndex())
|
|
return false;
|
|
|
|
if (c < ARRAY_SIZE(validControl) && !validControl[c])
|
|
return false;
|
|
if (c > 0x0010FFFF)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static HashType computeHash(u32 address, u32 size)
|
|
{
|
|
if (!Memory::IsValidAddress(address))
|
|
return 0;
|
|
|
|
size = Memory::ClampValidSizeAt(address, size);
|
|
#if PPSSPP_ARCH(AMD64)
|
|
return XXH3_64bits(Memory::GetPointerUnchecked(address), size);
|
|
#else
|
|
return XXH3_64bits(Memory::GetPointerUnchecked(address), size) & 0xFFFFFFFF;
|
|
#endif
|
|
}
|
|
|
|
|
|
static void parseDisasm(const char *disasm, char *opcode, size_t opcodeSize, char *arguments, size_t argumentsSize, bool insertSymbols) {
|
|
// copy opcode
|
|
size_t opcodePos = 0;
|
|
while (*disasm != 0 && *disasm != '\t' && opcodePos + 1 < opcodeSize) {
|
|
opcode[opcodePos++] = *disasm++;
|
|
}
|
|
opcode[opcodePos] = 0;
|
|
|
|
// Otherwise it's a tab, and we skip intentionally.
|
|
if (*disasm++ == 0) {
|
|
*arguments = 0;
|
|
return;
|
|
}
|
|
|
|
const char* jumpAddress = strstr(disasm,"->$");
|
|
const char* jumpRegister = strstr(disasm,"->");
|
|
size_t argumentsPos = 0;
|
|
while (*disasm != 0 && argumentsPos + 1 < argumentsSize) {
|
|
// parse symbol
|
|
if (disasm == jumpAddress) {
|
|
u32 branchTarget = 0;
|
|
sscanf(disasm+3, "%08x", &branchTarget);
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(branchTarget);
|
|
if (!addressSymbol.empty() && insertSymbols) {
|
|
argumentsPos += snprintf(&arguments[argumentsPos], argumentsSize - argumentsPos, "%s", addressSymbol.c_str());
|
|
} else {
|
|
argumentsPos += snprintf(&arguments[argumentsPos], argumentsSize - argumentsPos, "0x%08X", branchTarget);
|
|
}
|
|
|
|
disasm += 3+8;
|
|
continue;
|
|
}
|
|
|
|
if (disasm == jumpRegister)
|
|
disasm += 2;
|
|
|
|
if (*disasm == ' ') {
|
|
disasm++;
|
|
continue;
|
|
}
|
|
arguments[argumentsPos++] = *disasm++;
|
|
}
|
|
|
|
arguments[argumentsPos] = 0;
|
|
}
|
|
|
|
std::map<u32,DisassemblyEntry*>::iterator findDisassemblyEntry(std::map<u32,DisassemblyEntry*>& entries, u32 address, bool exact)
|
|
{
|
|
if (exact)
|
|
return entries.find(address);
|
|
|
|
if (entries.size() == 0)
|
|
return entries.end();
|
|
|
|
// find first elem that's >= address
|
|
auto it = entries.lower_bound(address);
|
|
if (it != entries.end())
|
|
{
|
|
// it may be an exact match
|
|
if (isInInterval(it->second->getLineAddress(0),it->second->getTotalSize(),address))
|
|
return it;
|
|
|
|
// otherwise it may point to the next
|
|
if (it != entries.begin())
|
|
{
|
|
it--;
|
|
if (isInInterval(it->second->getLineAddress(0),it->second->getTotalSize(),address))
|
|
return it;
|
|
}
|
|
}
|
|
|
|
// check last entry manually
|
|
auto rit = entries.rbegin();
|
|
if (isInInterval(rit->second->getLineAddress(0),rit->second->getTotalSize(),address))
|
|
{
|
|
return (++rit).base();
|
|
}
|
|
|
|
// no match otherwise
|
|
return entries.end();
|
|
}
|
|
|
|
void DisassemblyManager::setCpu(DebugInterface *cpu) {
|
|
_dbg_assert_(cpu);
|
|
cpu_ = cpu;
|
|
};
|
|
|
|
void DisassemblyManager::analyze(u32 address, u32 size = 1024)
|
|
{
|
|
u32 end = address+size;
|
|
|
|
address = RoundDownToMultipleOf(address, 4);
|
|
u32 start = address;
|
|
|
|
while (address < end && start <= address)
|
|
{
|
|
if (PSP_GetBootState() != BootState::Complete)
|
|
return;
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
auto it = findDisassemblyEntry(entries, address, false);
|
|
if (it != entries.end())
|
|
{
|
|
DisassemblyEntry* entry = it->second;
|
|
entry->recheck();
|
|
address = entry->getLineAddress(0)+entry->getTotalSize();
|
|
continue;
|
|
}
|
|
|
|
SymbolInfo info;
|
|
if (!g_symbolMap->GetSymbolInfo(&info,address,ST_ALL))
|
|
{
|
|
if (address % 4)
|
|
{
|
|
u32 next = std::min<u32>((address+3) & ~3,g_symbolMap->GetNextSymbolAddress(address,ST_ALL));
|
|
DisassemblyData* data = new DisassemblyData(address,next-address,DATATYPE_BYTE);
|
|
entries[address] = data;
|
|
address = next;
|
|
continue;
|
|
}
|
|
|
|
u32 next = g_symbolMap->GetNextSymbolAddress(address,ST_ALL);
|
|
|
|
if ((next % 4) && next != (u32)-1)
|
|
{
|
|
u32 alignedNext = next & ~3;
|
|
|
|
if (alignedNext != address)
|
|
{
|
|
DisassemblyOpcode* opcode = new DisassemblyOpcode(address,(alignedNext-address)/4);
|
|
entries[address] = opcode;
|
|
}
|
|
|
|
// This entry represents the misaligned tail [alignedNext, next), stored
|
|
// under key alignedNext - its own address must match, not the (possibly
|
|
// much earlier) start of the aligned opcode range above.
|
|
DisassemblyData* data = new DisassemblyData(alignedNext,next-alignedNext,DATATYPE_BYTE);
|
|
entries[alignedNext] = data;
|
|
} else {
|
|
DisassemblyOpcode* opcode = new DisassemblyOpcode(address,(next-address)/4);
|
|
entries[address] = opcode;
|
|
}
|
|
|
|
address = next;
|
|
continue;
|
|
}
|
|
|
|
switch (info.type)
|
|
{
|
|
case ST_FUNCTION:
|
|
{
|
|
DisassemblyFunction* function = new DisassemblyFunction(info.address,info.size);
|
|
entries[info.address] = function;
|
|
address = info.address+info.size;
|
|
}
|
|
break;
|
|
case ST_DATA:
|
|
{
|
|
DisassemblyData* data = new DisassemblyData(info.address,info.size,g_symbolMap->GetDataType(info.address));
|
|
entries[info.address] = data;
|
|
address = info.address+info.size;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
std::vector<BranchLine> DisassemblyManager::getBranchLines(u32 start, u32 size)
|
|
{
|
|
std::vector<BranchLine> result;
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
auto it = findDisassemblyEntry(entries,start,false);
|
|
if (it != entries.end())
|
|
{
|
|
do
|
|
{
|
|
it->second->getBranchLines(start,size,result);
|
|
it++;
|
|
} while (it != entries.end() && start+size > it->second->getLineAddress(0));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
auto it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end()) {
|
|
analyze(address);
|
|
it = findDisassemblyEntry(entries,address,false);
|
|
}
|
|
|
|
if (it != entries.end()) {
|
|
DisassemblyEntry *entry = it->second;
|
|
if (entry->disassemble(address, dest, insertSymbols, cpuDebug))
|
|
return;
|
|
}
|
|
|
|
dest.type = DISTYPE_OTHER;
|
|
memset(&dest.info, 0, sizeof(dest.info));
|
|
dest.info.opcodeAddress = address;
|
|
if (address % 4)
|
|
dest.totalSize = ((address+3) & ~3)-address;
|
|
else
|
|
dest.totalSize = 4;
|
|
if (Memory::IsValidRange(address, 4)) {
|
|
dest.name = "ERROR";
|
|
dest.params = "Disassembly failure";
|
|
} else {
|
|
dest.name = "-";
|
|
dest.params.clear();
|
|
}
|
|
}
|
|
|
|
u32 DisassemblyManager::getStartAddress(u32 address)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
auto it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end())
|
|
{
|
|
analyze(address);
|
|
it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end())
|
|
return address;
|
|
}
|
|
|
|
DisassemblyEntry* entry = it->second;
|
|
int line = entry->getLineNum(address,true);
|
|
return entry->getLineAddress(line);
|
|
}
|
|
|
|
u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
while (Memory::IsValidAddress(address))
|
|
{
|
|
auto it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end())
|
|
break;
|
|
while (it != entries.end()) {
|
|
DisassemblyEntry* entry = it->second;
|
|
int oldLineNum = entry->getLineNum(address,true);
|
|
if (n <= oldLineNum)
|
|
{
|
|
return entry->getLineAddress(oldLineNum-n);
|
|
}
|
|
|
|
address = entry->getLineAddress(0)-1;
|
|
n -= oldLineNum+1;
|
|
it = findDisassemblyEntry(entries,address,false);
|
|
}
|
|
|
|
analyze(address-127,128);
|
|
}
|
|
|
|
return (address - n * 4) & ~3;
|
|
}
|
|
|
|
u32 DisassemblyManager::getNthNextAddress(u32 address, int n)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
while (Memory::IsValidAddress(address))
|
|
{
|
|
auto it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end()) {
|
|
break;
|
|
}
|
|
|
|
while (it != entries.end())
|
|
{
|
|
DisassemblyEntry* entry = it->second;
|
|
int oldLineNum = entry->getLineNum(address,true);
|
|
int oldNumLines = entry->getNumLines();
|
|
if (oldLineNum+n < oldNumLines)
|
|
{
|
|
return entry->getLineAddress(oldLineNum+n);
|
|
}
|
|
|
|
address = entry->getLineAddress(0)+entry->getTotalSize();
|
|
n -= (oldNumLines-oldLineNum);
|
|
it = findDisassemblyEntry(entries,address,false);
|
|
}
|
|
|
|
analyze(address);
|
|
}
|
|
|
|
return (address + n * 4) & ~3;
|
|
}
|
|
|
|
DisassemblyManager::~DisassemblyManager() {
|
|
}
|
|
|
|
void DisassemblyManager::clear()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
|
|
for (auto it = entries.begin(); it != entries.end(); it++)
|
|
{
|
|
delete it->second;
|
|
}
|
|
entries.clear();
|
|
}
|
|
|
|
DisassemblyFunction::DisassemblyFunction(u32 _address, u32 _size): address(_address), size(_size)
|
|
{
|
|
if (PSP_GetBootState() != BootState::Complete)
|
|
return;
|
|
|
|
hash = computeHash(address,size);
|
|
load();
|
|
}
|
|
|
|
DisassemblyFunction::~DisassemblyFunction() {
|
|
clear();
|
|
}
|
|
|
|
void DisassemblyFunction::recheck()
|
|
{
|
|
if (PSP_GetBootState() != BootState::Complete)
|
|
return;
|
|
|
|
HashType newHash = computeHash(address,size);
|
|
if (hash != newHash)
|
|
{
|
|
hash = newHash;
|
|
clear();
|
|
load();
|
|
}
|
|
}
|
|
|
|
int DisassemblyFunction::getNumLines()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
return (int) lineAddresses.size();
|
|
}
|
|
|
|
int DisassemblyFunction::getLineNum(u32 address, bool findStart)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
if (findStart)
|
|
{
|
|
int last = (int)lineAddresses.size() - 1;
|
|
for (int i = 0; i < last; i++)
|
|
{
|
|
u32 next = lineAddresses[i + 1];
|
|
if (lineAddresses[i] <= address && next > address)
|
|
return i;
|
|
}
|
|
if (lineAddresses[last] <= address && this->address + this->size > address)
|
|
return last;
|
|
}
|
|
else
|
|
{
|
|
int last = (int)lineAddresses.size() - 1;
|
|
for (int i = 0; i < last; i++)
|
|
{
|
|
if (lineAddresses[i] == address)
|
|
return i;
|
|
}
|
|
if (lineAddresses[last] == address)
|
|
return last;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 DisassemblyFunction::getLineAddress(int line)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
// A zero-size function leaves lineAddresses empty (e.g. from a symbol added
|
|
// with size 0 via the WebSocket debugger API or a crafted ELF symtab entry);
|
|
// fall back to the function's own base address rather than indexing OOB.
|
|
if (line < 0 || (size_t)line >= lineAddresses.size())
|
|
return address;
|
|
return lineAddresses[line];
|
|
}
|
|
|
|
bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
auto it = findDisassemblyEntry(entries,address,false);
|
|
if (it == entries.end())
|
|
return false;
|
|
|
|
return it->second->disassemble(address, dest, insertSymbols, cpuDebug);
|
|
}
|
|
|
|
void DisassemblyFunction::getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest)
|
|
{
|
|
u32 end = start+size;
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
for (size_t i = 0; i < lines.size(); i++)
|
|
{
|
|
BranchLine& line = lines[i];
|
|
|
|
u32 first = line.first;
|
|
u32 second = line.second;
|
|
|
|
// skip branches that are entirely before or entirely after the window
|
|
if ((first < start && second < start) ||
|
|
(first > end && second > end))
|
|
continue;
|
|
|
|
dest.push_back(line);
|
|
}
|
|
}
|
|
|
|
#define NUM_LANES 16
|
|
|
|
void DisassemblyFunction::generateBranchLines()
|
|
{
|
|
struct LaneInfo
|
|
{
|
|
bool used;
|
|
u32 end;
|
|
};
|
|
|
|
LaneInfo lanes[NUM_LANES];
|
|
for (int i = 0; i < NUM_LANES; i++)
|
|
lanes[i].used = false;
|
|
|
|
u32 end = address+size;
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
DebugInterface *cpu = g_disassemblyManager.getCpu();
|
|
for (u32 funcPos = address; funcPos < end; funcPos += 4)
|
|
{
|
|
MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu, funcPos);
|
|
|
|
bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end);
|
|
if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) {
|
|
if (!Memory::IsValidAddress(opInfo.branchTarget))
|
|
continue;
|
|
|
|
BranchLine line;
|
|
if (opInfo.branchTarget < funcPos) {
|
|
line.first = opInfo.branchTarget;
|
|
line.second = funcPos;
|
|
line.type = LINE_UP;
|
|
} else {
|
|
line.first = funcPos;
|
|
line.second = opInfo.branchTarget;
|
|
line.type = LINE_DOWN;
|
|
}
|
|
|
|
lines.push_back(line);
|
|
}
|
|
}
|
|
|
|
std::sort(lines.begin(),lines.end());
|
|
for (size_t i = 0; i < lines.size(); i++)
|
|
{
|
|
for (int l = 0; l < NUM_LANES; l++)
|
|
{
|
|
if (lines[i].first > lanes[l].end)
|
|
lanes[l].used = false;
|
|
}
|
|
|
|
int lane = -1;
|
|
for (int l = 0; l < NUM_LANES; l++)
|
|
{
|
|
if (lanes[l].used == false)
|
|
{
|
|
lane = l;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lane == -1)
|
|
{
|
|
// Let's just pile on.
|
|
lines[i].laneIndex = 15;
|
|
continue;
|
|
}
|
|
|
|
lanes[lane].end = lines[i].second;
|
|
lanes[lane].used = true;
|
|
lines[i].laneIndex = lane;
|
|
}
|
|
}
|
|
|
|
void DisassemblyFunction::addOpcodeSequence(u32 start, u32 end)
|
|
{
|
|
DisassemblyOpcode* opcode = new DisassemblyOpcode(start,(end-start)/4);
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
entries[start] = opcode;
|
|
lineAddresses.reserve((end - start) / 4);
|
|
for (u32 pos = start; pos < end; pos += 4)
|
|
{
|
|
lineAddresses.push_back(pos);
|
|
}
|
|
}
|
|
|
|
void DisassemblyFunction::load()
|
|
{
|
|
generateBranchLines();
|
|
|
|
DebugInterface *cpu = g_disassemblyManager.getCpu();
|
|
u32 funcPos = address;
|
|
u32 funcEnd = address+size;
|
|
|
|
u32 nextData = g_symbolMap->GetNextSymbolAddress(funcPos-1,ST_DATA);
|
|
u32 opcodeSequenceStart = funcPos;
|
|
while (funcPos < funcEnd)
|
|
{
|
|
if (funcPos == nextData)
|
|
{
|
|
if (opcodeSequenceStart != funcPos)
|
|
addOpcodeSequence(opcodeSequenceStart,funcPos);
|
|
|
|
DisassemblyData* data = new DisassemblyData(funcPos,g_symbolMap->GetDataSize(funcPos),g_symbolMap->GetDataType(funcPos));
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
entries[funcPos] = data;
|
|
lineAddresses.push_back(funcPos);
|
|
funcPos += data->getTotalSize();
|
|
|
|
nextData = g_symbolMap->GetNextSymbolAddress(funcPos-1,ST_DATA);
|
|
opcodeSequenceStart = funcPos;
|
|
continue;
|
|
}
|
|
|
|
// force align
|
|
if (funcPos % 4)
|
|
{
|
|
u32 nextPos = (funcPos+3) & ~3;
|
|
|
|
DisassemblyComment* comment = new DisassemblyComment(funcPos,nextPos-funcPos,".align","4");
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
entries[funcPos] = comment;
|
|
lineAddresses.push_back(funcPos);
|
|
|
|
funcPos = nextPos;
|
|
opcodeSequenceStart = funcPos;
|
|
continue;
|
|
}
|
|
|
|
MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos);
|
|
funcPos += 4;
|
|
|
|
// skip branches and their delay slots
|
|
if (opInfo.isBranch)
|
|
{
|
|
funcPos += 4;
|
|
continue;
|
|
}
|
|
|
|
// just a normal opcode
|
|
}
|
|
|
|
if (opcodeSequenceStart != funcPos)
|
|
addOpcodeSequence(opcodeSequenceStart,funcPos);
|
|
}
|
|
|
|
void DisassemblyFunction::clear()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
for (auto it = entries.begin(); it != entries.end(); it++)
|
|
{
|
|
delete it->second;
|
|
}
|
|
|
|
entries.clear();
|
|
lines.clear();
|
|
lineAddresses.clear();
|
|
hash = 0;
|
|
}
|
|
|
|
bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) {
|
|
char opcode[64],arguments[256];
|
|
char dizz[512];
|
|
DisAsm(address, dizz, sizeof(dizz));
|
|
parseDisasm(dizz, opcode, sizeof(opcode), arguments, sizeof(arguments), insertSymbols);
|
|
dest.type = DISTYPE_OPCODE;
|
|
dest.name = opcode;
|
|
dest.params = arguments;
|
|
dest.totalSize = 4;
|
|
dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);
|
|
return true;
|
|
}
|
|
|
|
void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest)
|
|
{
|
|
if (start < address)
|
|
{
|
|
size = start+size-address;
|
|
start = address;
|
|
}
|
|
|
|
if (start+size > address+num*4)
|
|
size = address+num*4-start;
|
|
|
|
int lane = 0;
|
|
for (u32 pos = start; pos < start+size; pos += 4)
|
|
{
|
|
MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(g_disassemblyManager.getCpu(),pos);
|
|
if (info.isBranch && !info.isBranchToRegister && !info.isLinkedBranch) {
|
|
if (!Memory::IsValidAddress(info.branchTarget))
|
|
continue;
|
|
|
|
BranchLine line;
|
|
line.laneIndex = lane++;
|
|
|
|
if (info.branchTarget < pos) {
|
|
line.first = info.branchTarget;
|
|
line.second = pos;
|
|
line.type = LINE_UP;
|
|
} else {
|
|
line.first = pos;
|
|
line.second = info.branchTarget;
|
|
line.type = LINE_DOWN;
|
|
}
|
|
|
|
dest.push_back(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DisassemblyData::DisassemblyData(u32 _address, u32 _size, DataType _type): address(_address), size(_size), type(_type)
|
|
{
|
|
_dbg_assert_(PSP_GetBootState() == BootState::Complete);
|
|
hash = computeHash(address,size);
|
|
createLines();
|
|
}
|
|
|
|
void DisassemblyData::recheck()
|
|
{
|
|
if (PSP_GetBootState() != BootState::Complete)
|
|
return;
|
|
|
|
HashType newHash = computeHash(address,size);
|
|
if (newHash != hash)
|
|
{
|
|
hash = newHash;
|
|
createLines();
|
|
}
|
|
}
|
|
|
|
bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
|
|
{
|
|
dest.type = DISTYPE_DATA;
|
|
|
|
switch (type)
|
|
{
|
|
case DATATYPE_BYTE:
|
|
dest.name = ".byte";
|
|
break;
|
|
case DATATYPE_HALFWORD:
|
|
dest.name = ".half";
|
|
break;
|
|
case DATATYPE_WORD:
|
|
dest.name = ".word";
|
|
break;
|
|
case DATATYPE_ASCII:
|
|
dest.name = ".ascii";
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
auto it = lines.find(address);
|
|
if (it == lines.end())
|
|
return false;
|
|
|
|
dest.params = it->second.text;
|
|
dest.totalSize = it->second.size;
|
|
return true;
|
|
}
|
|
|
|
int DisassemblyData::getLineNum(u32 address, bool findStart)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
auto it = lines.upper_bound(address);
|
|
if (it != lines.end())
|
|
{
|
|
if (it == lines.begin())
|
|
return 0;
|
|
it--;
|
|
return it->second.lineNum;
|
|
}
|
|
|
|
return lines.rbegin()->second.lineNum;
|
|
}
|
|
|
|
void DisassemblyData::createLines()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock_);
|
|
lines.clear();
|
|
lineAddresses.clear();
|
|
|
|
u32 pos = address;
|
|
const u32 end = address + size;
|
|
|
|
if (!Memory::IsValidRange(address, size)) {
|
|
ERROR_LOG(Log::CPU, "DisassemblyData can't create lines for invalid range 0x%08X-0x%08X", address, end);
|
|
// The loop below reads through [pos, end) with the Unchecked accessors, which
|
|
// on non-masked builds do a raw *(base+address) with no bounds check at all -
|
|
// don't fall through into that for a range we just determined is invalid.
|
|
return;
|
|
}
|
|
|
|
const u32 maxChars = g_disassemblyManager.getMaxParamChars();
|
|
|
|
std::string currentLine;
|
|
u32 currentLineStart = pos;
|
|
|
|
int lineCount = 0;
|
|
if (type == DATATYPE_ASCII)
|
|
{
|
|
bool inString = false;
|
|
while (pos < end)
|
|
{
|
|
u8 b = Memory::ReadUnchecked_U8(pos++);
|
|
if (b >= 0x20 && b <= 0x7F)
|
|
{
|
|
if (currentLine.size()+1 >= maxChars)
|
|
{
|
|
if (inString == true)
|
|
currentLine += "\"";
|
|
|
|
DataEntry entry = {currentLine,pos-1-currentLineStart,lineCount++};
|
|
lines[currentLineStart] = entry;
|
|
lineAddresses.push_back(currentLineStart);
|
|
|
|
currentLine.clear();
|
|
currentLineStart = pos-1;
|
|
inString = false;
|
|
}
|
|
|
|
if (inString == false)
|
|
currentLine += "\"";
|
|
currentLine += (char)b;
|
|
inString = true;
|
|
} else {
|
|
char buffer[64];
|
|
if (pos == end && b == 0)
|
|
truncate_cpy(buffer, "0");
|
|
else
|
|
snprintf(buffer, sizeof(buffer), "0x%02X", b);
|
|
|
|
if (currentLine.size()+strlen(buffer) >= maxChars)
|
|
{
|
|
if (inString == true)
|
|
currentLine += "\"";
|
|
|
|
DataEntry entry = {currentLine,pos-1-currentLineStart,lineCount++};
|
|
lines[currentLineStart] = entry;
|
|
lineAddresses.push_back(currentLineStart);
|
|
|
|
currentLine.clear();
|
|
currentLineStart = pos-1;
|
|
inString = false;
|
|
}
|
|
|
|
bool comma = false;
|
|
if (currentLine.size() != 0)
|
|
comma = true;
|
|
|
|
if (inString)
|
|
currentLine += "\"";
|
|
|
|
if (comma)
|
|
currentLine += ",";
|
|
|
|
currentLine += buffer;
|
|
inString = false;
|
|
}
|
|
}
|
|
|
|
if (inString == true)
|
|
currentLine += "\"";
|
|
|
|
if (currentLine.size() != 0)
|
|
{
|
|
DataEntry entry = {currentLine,pos-currentLineStart,lineCount++};
|
|
lines[currentLineStart] = entry;
|
|
lineAddresses.push_back(currentLineStart);
|
|
}
|
|
} else {
|
|
while (pos < end)
|
|
{
|
|
char buffer[256];
|
|
u32 value;
|
|
|
|
u32 currentPos = pos;
|
|
|
|
switch (type)
|
|
{
|
|
case DATATYPE_BYTE:
|
|
value = Memory::ReadUnchecked_U8(pos);
|
|
snprintf(buffer, sizeof(buffer), "0x%02X", value);
|
|
pos++;
|
|
break;
|
|
case DATATYPE_HALFWORD:
|
|
value = Memory::ReadUnchecked_U16(pos);
|
|
snprintf(buffer, sizeof(buffer), "0x%04X", value);
|
|
pos += 2;
|
|
break;
|
|
case DATATYPE_WORD:
|
|
{
|
|
value = Memory::ReadUnchecked_U32(pos);
|
|
const std::string label = g_symbolMap->GetLabelString(value);
|
|
if (!label.empty())
|
|
snprintf(buffer, sizeof(buffer), "%s", label.c_str());
|
|
else
|
|
snprintf(buffer, sizeof(buffer), "0x%08X", value);
|
|
pos += 4;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
size_t len = strlen(buffer);
|
|
if (currentLine.size() != 0 && currentLine.size()+len >= maxChars)
|
|
{
|
|
DataEntry entry = {currentLine,currentPos-currentLineStart,lineCount++};
|
|
lines[currentLineStart] = entry;
|
|
lineAddresses.push_back(currentLineStart);
|
|
|
|
currentLine.clear();
|
|
currentLineStart = currentPos;
|
|
}
|
|
|
|
if (currentLine.size() != 0)
|
|
currentLine += ",";
|
|
currentLine += buffer;
|
|
}
|
|
|
|
if (currentLine.size() != 0) {
|
|
DataEntry entry = {currentLine,pos-currentLineStart,lineCount++};
|
|
lines[currentLineStart] = entry;
|
|
lineAddresses.push_back(currentLineStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string_view _name, std::string_view _param)
|
|
: address(_address), size(_size), name(_name), param(_param) {}
|
|
|
|
bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) {
|
|
dest.type = DISTYPE_OTHER;
|
|
dest.name = name;
|
|
dest.params = param;
|
|
dest.totalSize = size;
|
|
return true;
|
|
}
|
|
|
|
bool GetDisasmAddressText(u32 address, char *dest, size_t bufSize, bool abbreviateLabels, bool showData, bool displaySymbols) {
|
|
if (!Memory::IsValid4AlignedAddress(address)) {
|
|
snprintf(dest, bufSize, "(bad address!)");
|
|
return true;
|
|
}
|
|
if (displaySymbols) {
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(address);
|
|
if (!addressSymbol.empty()) {
|
|
for (int k = 0; addressSymbol[k] != 0; k++) {
|
|
// abbreviate long names
|
|
if (abbreviateLabels && k == 16 && addressSymbol[k+1] != 0) {
|
|
*dest++ = '+';
|
|
break;
|
|
}
|
|
*dest++ = addressSymbol[k];
|
|
}
|
|
*dest++ = ':';
|
|
*dest = 0;
|
|
return true;
|
|
} else {
|
|
snprintf(dest, bufSize, " %08X",address);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (showData) {
|
|
const u32 encoding = Memory::Read_Instruction(address, true).encoding;
|
|
snprintf(dest, bufSize, "%08X %08X", address, encoding);
|
|
} else {
|
|
snprintf(dest, bufSize, "%08X", address);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Utilify function from the old debugger.
|
|
std::string DisassembleRange(u32 start, u32 size, bool displaySymbols, MIPSDebugInterface *debugger) {
|
|
std::string result;
|
|
|
|
// gather all branch targets without labels
|
|
std::set<u32> branchAddresses;
|
|
for (u32 i = 0; i < size; i += debugger->getInstructionSize(0)) {
|
|
MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(debugger, start + i);
|
|
|
|
if (info.isBranch && g_symbolMap->GetLabelString(info.branchTarget).empty()) {
|
|
if (branchAddresses.find(info.branchTarget) == branchAddresses.end()) {
|
|
branchAddresses.insert(info.branchTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 disAddress = start;
|
|
bool previousLabel = true;
|
|
DisassemblyLineInfo line;
|
|
while (disAddress < start + size) {
|
|
char addressText[64], buffer[512];
|
|
|
|
g_disassemblyManager.getLine(disAddress, displaySymbols, line, debugger);
|
|
bool isLabel = GetDisasmAddressText(disAddress, addressText, sizeof(addressText), false, line.type == DISTYPE_OPCODE, displaySymbols);
|
|
|
|
if (isLabel) {
|
|
if (!previousLabel)
|
|
result += "\r\n";
|
|
snprintf(buffer, sizeof(buffer), "%s\r\n\r\n", addressText);
|
|
result += buffer;
|
|
} else if (branchAddresses.find(disAddress) != branchAddresses.end()) {
|
|
if (!previousLabel)
|
|
result += "\r\n";
|
|
snprintf(buffer, sizeof(buffer), "pos_%08X:\r\n\r\n", disAddress);
|
|
result += buffer;
|
|
}
|
|
|
|
if (line.info.isBranch && !line.info.isBranchToRegister
|
|
&& g_symbolMap->GetLabelString(line.info.branchTarget).empty()
|
|
&& branchAddresses.find(line.info.branchTarget) != branchAddresses.end()) {
|
|
snprintf(buffer, sizeof(buffer), "pos_%08X", line.info.branchTarget);
|
|
line.params = line.params.substr(0, line.params.find("0x")) + buffer;
|
|
}
|
|
|
|
snprintf(buffer, sizeof(buffer), "\t%s\t%s\r\n", line.name.c_str(), line.params.c_str());
|
|
result += buffer;
|
|
previousLabel = isLabel;
|
|
disAddress += line.totalSize;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void DisassemblyLineInfo::ToString(char *text, size_t bufSize, u32 curAddress) const {
|
|
text[0] = 0;
|
|
if (type == DISTYPE_OPCODE) {
|
|
if (info.hasRelevantAddress && IsLikelyStringAt(info.relevantAddress)) {
|
|
snprintf(text, bufSize, "[%08X] = \"%s\"", info.relevantAddress, Memory::GetCharPointer(info.relevantAddress));
|
|
}
|
|
|
|
if (info.isDataAccess) {
|
|
if (!Memory::IsValidRange(info.dataAddress, info.dataSize)) {
|
|
snprintf(text, bufSize, "Invalid address %08X", info.dataAddress);
|
|
} else {
|
|
bool isFloat = MIPSGetInfo(info.encodedOpcode) & (IS_FPU | IS_VFPU);
|
|
switch (info.dataSize) {
|
|
case 1:
|
|
snprintf(text, bufSize, "[%08X] = %02X", info.dataAddress, Memory::ReadUnchecked_U8(info.dataAddress));
|
|
break;
|
|
case 2:
|
|
snprintf(text, bufSize, "[%08X] = %04X", info.dataAddress, Memory::ReadUnchecked_U16(info.dataAddress));
|
|
break;
|
|
case 4:
|
|
{
|
|
u32 dataInt = Memory::ReadUnchecked_U32(info.dataAddress);
|
|
u32 dataFloat = Memory::ReadUnchecked_Float(info.dataAddress);
|
|
std::string dataString;
|
|
if (isFloat)
|
|
dataString = StringFromFormat("%08X / %f", dataInt, dataFloat);
|
|
else
|
|
dataString = StringFromFormat("%08X", dataInt);
|
|
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(dataInt);
|
|
if (!addressSymbol.empty()) {
|
|
snprintf(text, bufSize, "[%08X] = %s (%s)", info.dataAddress, addressSymbol.c_str(), dataString.c_str());
|
|
} else {
|
|
snprintf(text, bufSize, "[%08X] = %s", info.dataAddress, dataString.c_str());
|
|
}
|
|
break;
|
|
}
|
|
case 16:
|
|
{
|
|
uint32_t dataInt[4];
|
|
float dataFloat[4];
|
|
for (int i = 0; i < 4; ++i) {
|
|
dataInt[i] = Memory::ReadUnchecked_U32(info.dataAddress + i * 4);
|
|
dataFloat[i] = Memory::ReadUnchecked_Float(info.dataAddress + i * 4);
|
|
}
|
|
std::string dataIntString = StringFromFormat("%08X,%08X,%08X,%08X", dataInt[0], dataInt[1], dataInt[2], dataInt[3]);
|
|
std::string dataFloatString = StringFromFormat("%f,%f,%f,%f", dataFloat[0], dataFloat[1], dataFloat[2], dataFloat[3]);
|
|
|
|
snprintf(text, bufSize, "[%08X] = %s / %s", info.dataAddress, dataIntString.c_str(), dataFloatString.c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (info.isBranch) {
|
|
const std::string addressSymbol = g_symbolMap->GetLabelString(info.branchTarget);
|
|
if (addressSymbol.empty()) {
|
|
snprintf(text, bufSize, "%08X", info.branchTarget);
|
|
} else {
|
|
snprintf(text, bufSize, "%08X = %s", info.branchTarget, addressSymbol.c_str());
|
|
}
|
|
}
|
|
} else if (type == DISTYPE_DATA) {
|
|
u32 start = g_symbolMap->GetDataStart(curAddress);
|
|
if (start == -1)
|
|
start = curAddress;
|
|
|
|
u32 diff = curAddress - start;
|
|
const std::string label = g_symbolMap->GetLabelString(start);
|
|
|
|
if (!label.empty()) {
|
|
if (diff != 0)
|
|
snprintf(text, bufSize, "%08X (%s) + %08X", start, label.c_str(), diff);
|
|
else
|
|
snprintf(text, bufSize, "%08X (%s)", start, label.c_str());
|
|
} else {
|
|
if (diff != 0)
|
|
snprintf(text, bufSize, "%08X + %08X", start, diff);
|
|
else
|
|
snprintf(text, bufSize, "%08X", start);
|
|
}
|
|
}
|
|
}
|