Files
ppsspp/Core/Debugger/DisassemblyManager.cpp
Henrik RydgårdandClaude Opus 5 75ff0d406c Rename Memory::Lock() to Core_LockAgainstShutdown(), move it to Core
It stopped being about memory when CPU_Shutdown started holding it across the
whole teardown - it's what keeps kernel objects, the symbol map and the memory
map from being freed while another thread reads them. The old name invited the
reading that it locks memory *access*, which it has never done.

Memory::Reinit() now holds it across both halves rather than relying on
Memory::Shutdown()'s own acquire: between Shutdown() and Init() there is no
memory map at all, and a reader could slip into that gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 13:11:16 +02:00

1132 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;
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) {
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);
}
}
}