mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding the offset into the code space, and replacement functions installed their own emuhack the same way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function hasher, games reading their own code) had to translate them back. Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS and Switch, where address space is short), with the low bits cleared so a misaligned PC can't read across two entries. Only the ranges under memory views are committed, so a bad PC still faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The dispatchers on every backend, and the IR interpreter's loop, look blocks up there. A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling replaces the entry with the block's, and destroying the block puts the hook value back. The hook value is rewritten when the JIT changes or regenerates its dispatcher. Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the hooked instruction, or when an icache invalidation finds the function's hash (immediates masked, as when matching it) changed. Those checks run when queued invalidations are processed, since cheats and the debugger queue them before writing. Installing and removing hooks invalidates deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes through MIPSState, so it drops changed hooks too. With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away, as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1136 lines
29 KiB
C++
1136 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).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) {
|
|
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
|
|
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);
|
|
}
|
|
}
|
|
}
|