Files
archived-pcsx2/pcsx2/DebugTools/DebugInterface.cpp

1391 lines
26 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DebugInterface.h"
#include "Memory.h"
#include "R5900.h"
#include "R5900OpcodeTables.h"
#include "Debug.h"
#include "VU.h"
#include "GS.h" // Required for gsNonMirroredRead()
#include "Counters.h"
#include "Host.h"
#include "R3000A.h"
#include "IopMem.h"
#include "VMManager.h"
#include "common/StringUtil.h"
R5900DebugInterface r5900Debug;
R3000DebugInterface r3000Debug;
enum ReferenceIndexType
{
REF_INDEX_PC = 32,
REF_INDEX_HI = 33,
REF_INDEX_LO = 34,
REF_INDEX_OPTARGET = 0x800,
REF_INDEX_OPSTORE = 0x1000,
REF_INDEX_OPLOAD = 0x2000,
REF_INDEX_IS_OPSL = REF_INDEX_OPTARGET | REF_INDEX_OPSTORE | REF_INDEX_OPLOAD,
REF_INDEX_FPU = 0x4000,
REF_INDEX_FPU_INT = 0x8000,
REF_INDEX_VFPU = 0x10000,
REF_INDEX_VFPU_INT = 0x20000,
REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU,
};
//
// DebugInterface
//
bool DebugInterface::m_pause_on_entry = false;
bool DebugInterface::isAlive()
{
return VMManager::HasValidVM();
}
bool DebugInterface::isCpuPaused()
{
return VMManager::GetState() == VMState::Paused;
}
void DebugInterface::pauseCpu()
{
VMManager::SetPaused(true);
}
void DebugInterface::resumeCpu()
{
VMManager::SetPaused(false);
}
char* DebugInterface::stringFromPointer(u32 p)
{
const int BUFFER_LEN = 64;
static char buf[BUFFER_LEN] = {0};
if (!isValidAddress(p))
return NULL;
// This is going to blow up if it hits a TLB miss..
// Hopefully the checks in isValidAddress() are sufficient.
for (u32 i = 0; i < BUFFER_LEN; i++)
{
char c = read8(p + i);
buf[i] = c;
if (c == 0)
{
return i > 0 ? buf : NULL;
}
else if (c < 0x20 || c >= 0x7f)
{
// non printable character
return NULL;
}
}
buf[BUFFER_LEN - 1] = 0;
buf[BUFFER_LEN - 2] = '~';
return buf;
}
std::optional<u32> DebugInterface::getCallerStackPointer(const ccc::Function& currentFunction)
{
u32 sp = getRegister(EECAT_GPR, 29);
u32 pc = getPC();
if (pc != currentFunction.address().value)
{
std::optional<u32> stack_frame_size = getStackFrameSize(currentFunction);
if (!stack_frame_size.has_value())
return std::nullopt;
sp += *stack_frame_size;
}
return sp;
}
std::optional<u32> DebugInterface::getStackFrameSize(const ccc::Function& function)
{
s32 stack_frame_size = function.stack_frame_size;
if (stack_frame_size < 0)
{
// The stack frame size isn't stored in the symbol table, so we try
// to extract it from the code by checking for an instruction at the
// start of the current function that is in the form of
// "addiu $sp, $sp, frame_size" instead.
u32 instruction = read32(function.address().value);
if ((instruction & 0xffff0000) == 0x27bd0000)
stack_frame_size = -static_cast<s16>(instruction & 0xffff);
if (stack_frame_size < 0)
return std::nullopt;
}
return static_cast<u32>(stack_frame_size);
}
bool DebugInterface::evaluateExpression(const char* expression, u64& dest, std::string& error)
{
PostfixExpression postfix;
if (!initExpression(expression, postfix, error))
return false;
if (!parseExpression(postfix, dest, error))
return false;
return true;
}
bool DebugInterface::initExpression(const char* exp, PostfixExpression& dest, std::string& error)
{
MipsExpressionFunctions funcs(this, nullptr, true);
return initPostfixExpression(exp, &funcs, dest, error);
}
bool DebugInterface::parseExpression(PostfixExpression& exp, u64& dest, std::string& error)
{
MipsExpressionFunctions funcs(this, nullptr, false);
return parsePostfixExpression(exp, &funcs, dest, error);
}
DebugInterface& DebugInterface::get(BreakPointCpu cpu)
{
switch (cpu)
{
case BREAKPOINT_EE:
return r5900Debug;
case BREAKPOINT_IOP:
return r3000Debug;
default:
{
}
}
pxFailRel("DebugInterface::get called with invalid cpu enum.");
return r5900Debug;
}
const char* DebugInterface::cpuName(BreakPointCpu cpu)
{
switch (cpu)
{
case BREAKPOINT_EE:
return "EE";
case BREAKPOINT_IOP:
return "IOP";
default:
{
}
}
pxFailRel("DebugInterface::cpuName called with invalid cpu enum.");
return "";
}
const char* DebugInterface::longCpuName(BreakPointCpu cpu)
{
switch (cpu)
{
case BREAKPOINT_EE:
return TRANSLATE("DebugInterface", "Emotion Engine");
case BREAKPOINT_IOP:
return TRANSLATE("DebugInteface", "Input Output Processor");
default:
{
}
}
pxFailRel("DebugInterface::longCpuName called with invalid cpu enum.");
return "";
}
//
// R5900DebugInterface
//
BreakPointCpu R5900DebugInterface::getCpuType()
{
return BREAKPOINT_EE;
}
u32 R5900DebugInterface::read8(u32 address)
{
if (!isValidAddress(address))
return -1;
u8 value;
if (!vtlb_memSafeReadBytes(address, &value, sizeof(u8)))
return -1;
return value;
}
u32 R5900DebugInterface::read8(u32 address, bool& valid)
{
if (!(valid = isValidAddress(address)))
return -1;
u8 value;
if (!(valid = vtlb_memSafeReadBytes(address, &value, sizeof(u8))))
return -1;
return value;
}
u32 R5900DebugInterface::read16(u32 address)
{
if (!isValidAddress(address) || address % 2)
return -1;
u16 value;
if (!vtlb_memSafeReadBytes(address, &value, sizeof(u16)))
return -1;
return static_cast<u32>(value);
}
u32 R5900DebugInterface::read16(u32 address, bool& valid)
{
if (!(valid = (isValidAddress(address) || address % 2)))
return -1;
u16 value;
if (!(valid = vtlb_memSafeReadBytes(address, &value, sizeof(u16))))
return -1;
return static_cast<u32>(value);
}
u32 R5900DebugInterface::read32(u32 address)
{
if (!isValidAddress(address) || address % 4)
return -1;
u32 value;
if (!vtlb_memSafeReadBytes(address, &value, sizeof(u32)))
return -1;
return value;
}
u32 R5900DebugInterface::read32(u32 address, bool& valid)
{
if (!(valid = (isValidAddress(address) || address % 4)))
return -1;
u32 value;
if (!(valid = vtlb_memSafeReadBytes(address, &value, sizeof(u32))))
return -1;
return value;
}
u64 R5900DebugInterface::read64(u32 address)
{
if (!isValidAddress(address) || address % 8)
return -1;
u64 value;
if (!vtlb_memSafeReadBytes(address, &value, sizeof(u64)))
return -1;
return value;
}
u64 R5900DebugInterface::read64(u32 address, bool& valid)
{
if (!(valid = (isValidAddress(address) || address % 8)))
return -1;
u64 value;
if (!(valid = vtlb_memSafeReadBytes(address, &value, sizeof(u64))))
return -1;
return value;
}
u128 R5900DebugInterface::read128(u32 address)
{
alignas(16) u128 result;
if (!isValidAddress(address) || address % 16)
{
result.hi = result.lo = -1;
return result;
}
if (!vtlb_memSafeReadBytes(address, &result, sizeof(u128)))
{
result.hi = result.lo = -1;
}
return result;
}
void R5900DebugInterface::write8(u32 address, u8 value)
{
if (!isValidAddress(address))
return;
vtlb_memSafeWriteBytes(address, &value, sizeof(u8));
}
void R5900DebugInterface::write16(u32 address, u16 value)
{
if (!isValidAddress(address))
return;
vtlb_memSafeWriteBytes(address, &value, sizeof(u16));
}
void R5900DebugInterface::write32(u32 address, u32 value)
{
if (!isValidAddress(address))
return;
vtlb_memSafeWriteBytes(address, &value, sizeof(u32));
}
void R5900DebugInterface::write64(u32 address, u64 value)
{
if (!isValidAddress(address))
return;
vtlb_memSafeWriteBytes(address, &value, sizeof(u64));
}
void R5900DebugInterface::write128(u32 address, u128 value)
{
if (!isValidAddress(address))
return;
vtlb_memSafeWriteBytes(address, &value, sizeof(u128));
}
int R5900DebugInterface::getRegisterCategoryCount()
{
return EECAT_COUNT;
}
const char* R5900DebugInterface::getRegisterCategoryName(int cat)
{
switch (cat)
{
case EECAT_GPR:
return "GPR";
case EECAT_CP0:
return "CP0";
case EECAT_FPR:
return "FPR";
case EECAT_FCR:
return "FCR";
case EECAT_VU0F:
return "VU0f";
case EECAT_VU0I:
return "VU0i";
case EECAT_GSPRIV:
return "GS";
default:
return "Invalid";
}
}
int R5900DebugInterface::getRegisterSize(int cat)
{
switch (cat)
{
case EECAT_GPR:
case EECAT_VU0F:
return 128;
case EECAT_CP0:
case EECAT_FPR:
case EECAT_FCR:
case EECAT_VU0I:
return 32;
case EECAT_GSPRIV:
return 64;
default:
return 0;
}
}
int R5900DebugInterface::getRegisterCount(int cat)
{
switch (cat)
{
case EECAT_GPR:
return 35; // 32 + pc + hi + lo
case EECAT_CP0:
case EECAT_FPR:
case EECAT_FCR:
case EECAT_VU0I:
return 32;
case EECAT_VU0F:
return 33; // 32 + ACC
case EECAT_GSPRIV:
return 19;
default:
return 0;
}
}
DebugInterface::RegisterType R5900DebugInterface::getRegisterType(int cat)
{
switch (cat)
{
case EECAT_GPR:
case EECAT_CP0:
case EECAT_VU0I:
case EECAT_FCR:
case EECAT_GSPRIV:
default:
return NORMAL;
case EECAT_FPR:
case EECAT_VU0F:
return SPECIAL;
}
}
const char* R5900DebugInterface::getRegisterName(int cat, int num)
{
switch (cat)
{
case EECAT_GPR:
switch (num)
{
case 32: // pc
return "pc";
case 33: // hi
return "hi";
case 34: // lo
return "lo";
default:
return R5900::GPR_REG[num];
}
case EECAT_CP0:
return R5900::COP0_REG[num];
case EECAT_FPR:
return R5900::COP1_REG_FP[num];
case EECAT_FCR:
return R5900::COP1_REG_FCR[num];
case EECAT_VU0F:
switch (num)
{
case 32: // ACC
return "ACC";
default:
return R5900::COP2_REG_FP[num];
}
case EECAT_VU0I:
return R5900::COP2_REG_CTL[num];
case EECAT_GSPRIV:
return R5900::GS_REG_PRIV[num];
default:
return "Invalid";
}
}
u128 R5900DebugInterface::getRegister(int cat, int num)
{
u128 result;
switch (cat)
{
case EECAT_GPR:
switch (num)
{
case 32: // pc
result = u128::From32(cpuRegs.pc);
break;
case 33: // hi
result = cpuRegs.HI.UQ;
break;
case 34: // lo
result = cpuRegs.LO.UQ;
break;
default:
result = cpuRegs.GPR.r[num].UQ;
break;
}
break;
case EECAT_CP0:
result = u128::From32(cpuRegs.CP0.r[num]);
break;
case EECAT_FPR:
result = u128::From32(fpuRegs.fpr[num].UL);
break;
case EECAT_FCR:
result = u128::From32(fpuRegs.fprc[num]);
break;
case EECAT_VU0F:
switch (num)
{
case 32: // ACC
result = VU0.ACC.UQ;
break;
default:
result = VU0.VF[num].UQ;
break;
}
break;
case EECAT_VU0I:
result = u128::From32(VU0.VI[num].UL);
break;
case EECAT_GSPRIV:
result = gsNonMirroredRead(0x12000000 | R5900::GS_REG_PRIV_ADDR[num]);
break;
default:
result = u128::From32(0);
break;
}
return result;
}
std::string R5900DebugInterface::getRegisterString(int cat, int num)
{
switch (cat)
{
case EECAT_GPR:
case EECAT_CP0:
case EECAT_FCR:
case EECAT_VU0F:
return StringUtil::U128ToString(getRegister(cat, num));
case EECAT_FPR:
return StringUtil::StdStringFromFormat("%f", fpuRegs.fpr[num].f);
default:
return {};
}
}
u128 R5900DebugInterface::getHI()
{
return cpuRegs.HI.UQ;
}
u128 R5900DebugInterface::getLO()
{
return cpuRegs.LO.UQ;
}
u32 R5900DebugInterface::getPC()
{
return cpuRegs.pc;
}
// Taken from COP0.cpp
bool R5900DebugInterface::getCPCOND0()
{
return (((dmacRegs.stat.CIS | ~dmacRegs.pcr.CPC) & 0x3FF) == 0x3ff);
}
void R5900DebugInterface::setPc(u32 newPc)
{
cpuRegs.pc = newPc;
}
void R5900DebugInterface::setRegister(int cat, int num, u128 newValue)
{
switch (cat)
{
case EECAT_GPR:
switch (num)
{
case 32: // pc
cpuRegs.pc = newValue._u32[0];
break;
case 33: // hi
cpuRegs.HI.UQ = newValue;
break;
case 34: // lo
cpuRegs.LO.UQ = newValue;
break;
default:
cpuRegs.GPR.r[num].UQ = newValue;
break;
}
break;
case EECAT_CP0:
cpuRegs.CP0.r[num] = newValue._u32[0];
break;
case EECAT_FPR:
fpuRegs.fpr[num].UL = newValue._u32[0];
break;
case EECAT_FCR:
fpuRegs.fprc[num] = newValue._u32[0];
break;
case EECAT_VU0F:
switch (num)
{
case 32: // ACC
VU0.ACC.UQ = newValue;
break;
default:
VU0.VF[num].UQ = newValue;
break;
}
break;
case EECAT_VU0I:
VU0.VI[num].UL = newValue._u32[0];
break;
case EECAT_GSPRIV:
memWrite64(0x12000000 | R5900::GS_REG_PRIV_ADDR[num], newValue.lo);
break;
default:
break;
}
}
std::string R5900DebugInterface::disasm(u32 address, bool simplify)
{
std::string out;
u32 op = read32(address);
R5900::disR5900Fasm(out, op, address, simplify);
return out;
}
bool R5900DebugInterface::isValidAddress(u32 addr)
{
u32 lopart = addr & 0xfFFffFF;
// get rid of ee ram mirrors
switch (addr >> 28)
{
case 0:
case 2:
// case 3: throw exception (not mapped ?)
// [ 0000_8000 - 01FF_FFFF ] RAM
// [ 2000_8000 - 21FF_FFFF ] RAM MIRROR
// [ 3000_8000 - 31FF_FFFF ] RAM MIRROR
if (lopart >= 0x80000 && lopart < Ps2MemSize::ExposedRam)
return !!vtlb_GetPhyPtr(lopart);
break;
case 1:
// [ 1000_0000 - 1000_CFFF ] EE register
if (lopart <= 0xcfff)
return true;
// [ 1100_0000 - 1100_FFFF ] VU mem
if (lopart >= 0x1000000 && lopart <= 0x100FFff)
return true;
// [ 1200_0000 - 1200_FFFF ] GS regs
if (lopart >= 0x2000000 && lopart <= 0x20010ff)
return true;
// [ 1E00_0000 - 1FFF_FFFF ] ROM
// if (lopart >= 0xe000000)
// return true; throw exception (not mapped ?)
break;
case 7:
// [ 7000_0000 - 7000_3FFF ] Scratchpad
if (lopart <= 0x3fff)
return true;
break;
case 8:
case 0xA:
if (lopart <= 0xFFFFF)
return true;
break;
case 9:
case 0xB:
// [ 8000_0000 - BFFF_FFFF ] kernel
if (lopart >= 0xFC00000)
return true;
break;
case 0xF:
// [ 8000_0000 - BFFF_FFFF ] IOP or kernel stack
if (lopart >= 0xfff8000)
return true;
break;
}
return false;
}
u32 R5900DebugInterface::getCycles()
{
return cpuRegs.cycle;
}
SymbolGuardian& R5900DebugInterface::GetSymbolGuardian() const
{
return R5900SymbolGuardian;
}
SymbolImporter* R5900DebugInterface::GetSymbolImporter() const
{
return &R5900SymbolImporter;
}
std::vector<std::unique_ptr<BiosThread>> R5900DebugInterface::GetThreadList() const
{
return getEEThreads();
}
std::vector<IopMod> R5900DebugInterface::GetModuleList() const
{
return {};
}
//
// R3000DebugInterface
//
BreakPointCpu R3000DebugInterface::getCpuType()
{
return BREAKPOINT_IOP;
}
u32 R3000DebugInterface::read8(u32 address)
{
if (!isValidAddress(address))
return -1;
return iopMemRead8(address);
}
u32 R3000DebugInterface::read8(u32 address, bool& valid)
{
if (!(valid = isValidAddress(address)))
return -1;
return iopMemRead8(address);
}
u32 R3000DebugInterface::read16(u32 address)
{
if (!isValidAddress(address))
return -1;
return iopMemRead16(address);
}
u32 R3000DebugInterface::read16(u32 address, bool& valid)
{
if (!(valid = isValidAddress(address)))
return -1;
return iopMemRead16(address);
}
u32 R3000DebugInterface::read32(u32 address)
{
if (!isValidAddress(address))
return -1;
return iopMemRead32(address);
}
u32 R3000DebugInterface::read32(u32 address, bool& valid)
{
if (!(valid = isValidAddress(address)))
return -1;
return iopMemRead32(address);
}
u64 R3000DebugInterface::read64(u32 address)
{
return 0;
}
u64 R3000DebugInterface::read64(u32 address, bool& valid)
{
return 0;
}
u128 R3000DebugInterface::read128(u32 address)
{
return u128::From32(0);
}
void R3000DebugInterface::write8(u32 address, u8 value)
{
if (!isValidAddress(address))
return;
iopMemWrite8(address, value);
}
void R3000DebugInterface::write16(u32 address, u16 value)
{
if (!isValidAddress(address))
return;
iopMemWrite16(address, value);
}
void R3000DebugInterface::write32(u32 address, u32 value)
{
if (!isValidAddress(address))
return;
iopMemWrite32(address, value);
}
void R3000DebugInterface::write64(u32 address, u64 value)
{
if (!isValidAddress(address))
return;
iopMemWrite32(address + 0, value);
iopMemWrite32(address + 4, value >> 32);
}
void R3000DebugInterface::write128(u32 address, u128 value)
{
if (!isValidAddress(address))
return;
iopMemWrite32(address + 0x0, value._u32[0]);
iopMemWrite32(address + 0x4, value._u32[1]);
iopMemWrite32(address + 0x8, value._u32[2]);
iopMemWrite32(address + 0xc, value._u32[3]);
}
int R3000DebugInterface::getRegisterCategoryCount()
{
return IOPCAT_COUNT;
}
const char* R3000DebugInterface::getRegisterCategoryName(int cat)
{
switch (cat)
{
case IOPCAT_GPR:
return "GPR";
default:
return "Invalid";
}
}
int R3000DebugInterface::getRegisterSize(int cat)
{
switch (cat)
{
case IOPCAT_GPR:
return 32;
default:
return 0;
}
}
int R3000DebugInterface::getRegisterCount(int cat)
{
switch (cat)
{
case IOPCAT_GPR:
return 35; // 32 + pc + hi + lo
default:
return 0;
}
}
DebugInterface::RegisterType R3000DebugInterface::getRegisterType(int cat)
{
switch (cat)
{
case IOPCAT_GPR:
default:
return DebugInterface::NORMAL;
}
}
const char* R3000DebugInterface::getRegisterName(int cat, int num)
{
switch (cat)
{
case IOPCAT_GPR:
switch (num)
{
case 32: // pc
return "pc";
case 33: // hi
return "hi";
case 34: // lo
return "lo";
default:
return R5900::GPR_REG[num];
}
default:
return "Invalid";
}
}
u128 R3000DebugInterface::getRegister(int cat, int num)
{
u32 value;
switch (cat)
{
case IOPCAT_GPR:
switch (num)
{
case 32: // pc
value = psxRegs.pc;
break;
case 33: // hi
value = psxRegs.GPR.n.hi;
break;
case 34: // lo
value = psxRegs.GPR.n.lo;
break;
default:
value = psxRegs.GPR.r[num];
break;
}
break;
default:
value = -1;
break;
}
return u128::From32(value);
}
std::string R3000DebugInterface::getRegisterString(int cat, int num)
{
switch (cat)
{
case IOPCAT_GPR:
return StringUtil::U128ToString(getRegister(cat, num));
default:
return "Invalid";
}
}
u128 R3000DebugInterface::getHI()
{
return u128::From32(psxRegs.GPR.n.hi);
}
u128 R3000DebugInterface::getLO()
{
return u128::From32(psxRegs.GPR.n.lo);
}
u32 R3000DebugInterface::getPC()
{
return psxRegs.pc;
}
bool R3000DebugInterface::getCPCOND0()
{
return false;
}
void R3000DebugInterface::setPc(u32 newPc)
{
psxRegs.pc = newPc;
}
void R3000DebugInterface::setRegister(int cat, int num, u128 newValue)
{
switch (cat)
{
case IOPCAT_GPR:
switch (num)
{
case 32: // pc
psxRegs.pc = newValue._u32[0];
break;
case 33: // hi
psxRegs.GPR.n.hi = newValue._u32[0];
break;
case 34: // lo
psxRegs.GPR.n.lo = newValue._u32[0];
break;
default:
psxRegs.GPR.r[num] = newValue._u32[0];
break;
}
break;
default:
break;
}
}
std::string R3000DebugInterface::disasm(u32 address, bool simplify)
{
std::string out;
u32 op = read32(address);
R5900::disR5900Fasm(out, op, address, simplify);
return out;
}
bool R3000DebugInterface::isValidAddress(u32 addr)
{
addr &= 0x1fffffff;
if (addr >= 0x1D000000 && addr < 0x1E000000)
{
return true;
}
if (addr >= 0x1F400000 && addr < 0x1FA00000)
{
return true;
}
if (addr >= 0x1FC00000 && addr < 0x20000000)
{
return true;
}
if (addr < 0x200000)
{
return true;
}
return false;
}
u32 R3000DebugInterface::getCycles()
{
return psxRegs.cycle;
}
SymbolGuardian& R3000DebugInterface::GetSymbolGuardian() const
{
return R3000SymbolGuardian;
}
SymbolImporter* R3000DebugInterface::GetSymbolImporter() const
{
return nullptr;
}
std::vector<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() const
{
return getIOPThreads();
}
std::vector<IopMod> R3000DebugInterface::GetModuleList() const
{
return getIOPModules();
}
ElfMemoryReader::ElfMemoryReader(const ccc::ElfFile& elf)
: m_elf(elf)
{
}
u32 ElfMemoryReader::read8(u32 address)
{
std::optional<u8> result = m_elf.get_object_virtual<u8>(address);
if (!result.has_value())
return 0;
return *result;
}
u32 ElfMemoryReader::read8(u32 address, bool& valid)
{
std::optional<u8> result = m_elf.get_object_virtual<u8>(address);
valid = result.has_value();
if (!valid)
return 0;
return *result;
}
u32 ElfMemoryReader::read16(u32 address)
{
std::optional<u16> result = m_elf.get_object_virtual<u16>(address);
if (!result.has_value())
return 0;
return *result;
}
u32 ElfMemoryReader::read16(u32 address, bool& valid)
{
std::optional<u16> result = m_elf.get_object_virtual<u16>(address);
valid = result.has_value();
if (!valid)
return 0;
return *result;
}
u32 ElfMemoryReader::read32(u32 address)
{
std::optional<u32> result = m_elf.get_object_virtual<u32>(address);
if (!result.has_value())
return 0;
return *result;
}
u32 ElfMemoryReader::read32(u32 address, bool& valid)
{
std::optional<u32> result = m_elf.get_object_virtual<u32>(address);
valid = result.has_value();
if (!valid)
return 0;
return *result;
}
u64 ElfMemoryReader::read64(u32 address)
{
std::optional<u64> result = m_elf.get_object_virtual<u64>(address);
if (!result.has_value())
return 0;
return *result;
}
u64 ElfMemoryReader::read64(u32 address, bool& valid)
{
std::optional<u64> result = m_elf.get_object_virtual<u64>(address);
valid = result.has_value();
if (!valid)
return 0;
return *result;
}
//
// MipsExpressionFunctions
//
MipsExpressionFunctions::MipsExpressionFunctions(
DebugInterface* cpu, const ccc::SymbolDatabase* symbolDatabase, bool shouldEnumerateSymbols)
: m_cpu(cpu)
, m_database(symbolDatabase)
{
if (!shouldEnumerateSymbols)
return;
if (symbolDatabase)
{
enumerateSymbols(*symbolDatabase);
}
else
{
m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
enumerateSymbols(database);
});
}
}
void MipsExpressionFunctions::enumerateSymbols(const ccc::SymbolDatabase& database)
{
// TODO: Add mangled symbol name maps to CCC and remove this.
for (const ccc::Function& function : database.functions)
m_mangled_function_names_to_handles.emplace(function.mangled_name(), function.handle());
for (const ccc::GlobalVariable& global : database.global_variables)
m_mangled_global_names_to_handles.emplace(global.mangled_name(), global.handle());
}
bool MipsExpressionFunctions::parseReference(char* str, u64& referenceIndex)
{
for (int i = 0; i < 32; i++)
{
char reg[8];
std::snprintf(reg, std::size(reg), "r%d", i);
if (StringUtil::Strcasecmp(str, reg) == 0 || StringUtil::Strcasecmp(str, m_cpu->getRegisterName(0, i)) == 0)
{
referenceIndex = i;
return true;
}
std::snprintf(reg, std::size(reg), "f%d", i);
if (StringUtil::Strcasecmp(str, reg) == 0)
{
referenceIndex = i | REF_INDEX_FPU;
return true;
}
}
if (StringUtil::Strcasecmp(str, "pc") == 0)
{
referenceIndex = REF_INDEX_PC;
return true;
}
if (StringUtil::Strcasecmp(str, "hi") == 0)
{
referenceIndex = REF_INDEX_HI;
return true;
}
if (StringUtil::Strcasecmp(str, "lo") == 0)
{
referenceIndex = REF_INDEX_LO;
return true;
}
if (StringUtil::Strcasecmp(str, "target") == 0)
{
referenceIndex = REF_INDEX_OPTARGET;
return true;
}
if (StringUtil::Strcasecmp(str, "load") == 0)
{
referenceIndex = REF_INDEX_OPLOAD;
return true;
}
if (StringUtil::Strcasecmp(str, "store") == 0)
{
referenceIndex = REF_INDEX_OPSTORE;
return true;
}
return false;
}
bool MipsExpressionFunctions::parseSymbol(char* str, u64& symbolValue)
{
if (m_database)
return parseSymbol(str, symbolValue, *m_database);
bool success = false;
m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
success = parseSymbol(str, symbolValue, database);
});
return success;
}
bool MipsExpressionFunctions::parseSymbol(char* str, u64& symbolValue, const ccc::SymbolDatabase& database)
{
std::string name = str;
// Check for mangled function names.
auto function_iterator = m_mangled_function_names_to_handles.find(name);
if (function_iterator != m_mangled_function_names_to_handles.end())
{
const ccc::Function* function = database.functions.symbol_from_handle(function_iterator->second);
if (function && function->address().valid())
{
symbolValue = function->address().value;
return true;
}
}
// Check for mangled global variable names.
auto global_iterator = m_mangled_global_names_to_handles.find(name);
if (global_iterator != m_mangled_global_names_to_handles.end())
{
const ccc::GlobalVariable* global = database.global_variables.symbol_from_handle(global_iterator->second);
if (global && global->address().valid())
{
symbolValue = global->address().value;
return true;
}
}
// Check for regular unmangled names.
const ccc::Symbol* symbol = database.symbol_with_name(name);
if (symbol && symbol->address().valid())
{
symbolValue = symbol->address().value;
return true;
}
return false;
}
u64 MipsExpressionFunctions::getReferenceValue(u64 referenceIndex)
{
if (referenceIndex < 32)
return m_cpu->getRegister(0, referenceIndex)._u64[0];
if (referenceIndex == REF_INDEX_PC)
return m_cpu->getPC();
if (referenceIndex == REF_INDEX_HI)
return m_cpu->getHI()._u64[0];
if (referenceIndex == REF_INDEX_LO)
return m_cpu->getLO()._u64[0];
if (referenceIndex & REF_INDEX_IS_OPSL)
{
const u32 OP = m_cpu->read32(m_cpu->getPC());
const R5900::OPCODE& opcode = R5900::GetInstruction(OP);
if (opcode.flags & IS_MEMORY)
{
// Fetch the address in the base register
u32 target = cpuRegs.GPR.r[(OP >> 21) & 0x1F].UD[0];
// Add the offset (lower 16 bits)
target += static_cast<u16>(OP);
if (referenceIndex & REF_INDEX_OPTARGET)
{
return target;
}
else if (referenceIndex & REF_INDEX_OPLOAD)
{
return (opcode.flags & IS_LOAD) ? target : 0;
}
else if (referenceIndex & REF_INDEX_OPSTORE)
{
return (opcode.flags & IS_STORE) ? target : 0;
}
}
return 0;
}
if (referenceIndex & REF_INDEX_FPU)
{
return m_cpu->getRegister(EECAT_FPR, referenceIndex & 0x1F)._u64[0];
}
return -1;
}
ExpressionType MipsExpressionFunctions::getReferenceType(u64 referenceIndex)
{
if (referenceIndex & REF_INDEX_IS_FLOAT)
{
return EXPR_TYPE_FLOAT;
}
return EXPR_TYPE_UINT;
}
bool MipsExpressionFunctions::getMemoryValue(u32 address, int size, u64& dest, std::string& error)
{
switch (size)
{
case 1:
case 2:
case 4:
case 8:
break;
default:
error = StringUtil::StdStringFromFormat(
TRANSLATE("ExpressionParser", "Invalid memory access size %d."), size);
return false;
}
if (address % size)
{
error = TRANSLATE("ExpressionParser", "Invalid memory access (unaligned).");
return false;
}
switch (size)
{
case 1:
dest = m_cpu->read8(address);
break;
case 2:
dest = m_cpu->read16(address);
break;
case 4:
dest = m_cpu->read32(address);
break;
case 8:
dest = m_cpu->read64(address);
break;
}
return true;
}