mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 17:55:23 +02:00
Prototype of the frame-gated run-to-cursor idea. "Run to here" stops at the first hit, which isn't what you want for an address hit many times per frame - you end up stepping through the rest of the current frame to reach the state you actually care about. Built on machinery that was already there rather than a new stepping mode: the one-shot breakpoint behind run-to-cursor already takes a condition (step-into uses it to pin a step to one thread), and a hit that fails the condition leaves it armed for the next one. So "the next frame" is just a condition that isn't true yet - here "flipcount > <now>". Counting presented frames rather than vblanks matters for a game that doesn't render at the full refresh rate: at 30fps there are two vblanks per frame, so a vblank-based condition would let you through halfway into the frame you were trying to skip. The flip side is that the counter only advances when the framebuffer actually changed, so if the game has stopped drawing - or is wedged in the loop you're trying to debug - this never trips and the core keeps running. Both counters are exposed to the expression parser, next to threadid/moduleid/usec/ticks, so they're usable in ordinary breakpoint conditions and cpu.evaluate too, not just from this menu item: "flipcount" for presented frames and "vcount" for the PSP's own vblank counter, which is what sceDisplayGetVcount returns and is the one a game's own timing is written against. Verified with a headless session: across a second of emulated time flipcount went 120 -> 172 and vcount 119 -> 172 (a game rendering every vblank, so they track). pspautotests 314/314, UnitTest 55/55. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
315 lines
9.0 KiB
C++
315 lines
9.0 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 <string>
|
|
#include <cstring>
|
|
|
|
#ifndef _MSC_VER
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/HW/Display.h"
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
#include "Core/Debugger/SymbolMap.h"
|
|
#include "Core/Debugger/DebugInterface.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/MIPS/MIPSVFPUUtils.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/System.h"
|
|
|
|
enum ReferenceIndexType {
|
|
REF_INDEX_PC = 32,
|
|
REF_INDEX_HI = 33,
|
|
REF_INDEX_LO = 34,
|
|
REF_INDEX_FPU = 0x1000,
|
|
REF_INDEX_FPU_INT = 0x2000,
|
|
REF_INDEX_VFPU = 0x4000,
|
|
REF_INDEX_VFPU_INT = 0x8000,
|
|
REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU,
|
|
REF_INDEX_HLE = 0x10000,
|
|
REF_INDEX_THREAD = REF_INDEX_HLE | 0,
|
|
REF_INDEX_MODULE = REF_INDEX_HLE | 1,
|
|
REF_INDEX_USEC = REF_INDEX_HLE | 2,
|
|
REF_INDEX_TICKS = REF_INDEX_HLE | 3,
|
|
REF_INDEX_VCOUNT = REF_INDEX_HLE | 4,
|
|
REF_INDEX_FLIPS = REF_INDEX_HLE | 5,
|
|
};
|
|
|
|
|
|
class MipsExpressionFunctions : public IExpressionFunctions {
|
|
public:
|
|
MipsExpressionFunctions(const DebugInterface *_cpu): cpu(_cpu) {}
|
|
|
|
bool parseReference(char* str, uint32_t& referenceIndex) override
|
|
{
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
char reg[8];
|
|
snprintf(reg, sizeof(reg), "r%d", i);
|
|
|
|
if (strcasecmp(str, reg) == 0 || strcasecmp(str, MIPSDebugInterface::GetRegName(0, i).c_str()) == 0)
|
|
{
|
|
referenceIndex = i;
|
|
return true;
|
|
}
|
|
else if (strcasecmp(str, MIPSDebugInterface::GetRegName(1, i).c_str()) == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_FPU | i;
|
|
return true;
|
|
}
|
|
|
|
snprintf(reg, sizeof(reg), "fi%d", i);
|
|
if (strcasecmp(str, reg) == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_FPU_INT | i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 128; i++)
|
|
{
|
|
if (strcasecmp(str, MIPSDebugInterface::GetRegName(2, i).c_str()) == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_VFPU | i;
|
|
return true;
|
|
}
|
|
|
|
char reg[8];
|
|
snprintf(reg, sizeof(reg), "vi%d", i);
|
|
if (strcasecmp(str, reg) == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_VFPU_INT | i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (strcasecmp(str, "pc") == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_PC;
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(str, "hi") == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_HI;
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(str, "lo") == 0)
|
|
{
|
|
referenceIndex = REF_INDEX_LO;
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(str, "threadid") == 0) {
|
|
referenceIndex = REF_INDEX_THREAD;
|
|
return true;
|
|
}
|
|
if (strcasecmp(str, "moduleid") == 0) {
|
|
referenceIndex = REF_INDEX_MODULE;
|
|
return true;
|
|
}
|
|
if (strcasecmp(str, "usec") == 0) {
|
|
referenceIndex = REF_INDEX_USEC;
|
|
return true;
|
|
}
|
|
if (strcasecmp(str, "ticks") == 0) {
|
|
referenceIndex = REF_INDEX_TICKS;
|
|
return true;
|
|
}
|
|
// The PSP's own vblank counter, as returned by sceDisplayGetVcount.
|
|
if (strcasecmp(str, "vcount") == 0) {
|
|
referenceIndex = REF_INDEX_VCOUNT;
|
|
return true;
|
|
}
|
|
// Frames actually presented. Unlike vcount this tracks what reached the screen, so
|
|
// "flipcount > N" is how a breakpoint condition says "only from the next frame onwards".
|
|
if (strcasecmp(str, "flipcount") == 0) {
|
|
referenceIndex = REF_INDEX_FLIPS;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool parseSymbol(char* str, uint32_t& symbolValue) override
|
|
{
|
|
return g_symbolMap->GetLabelValue(str,symbolValue);
|
|
}
|
|
|
|
uint32_t getReferenceValue(uint32_t referenceIndex) override
|
|
{
|
|
if (referenceIndex < 32)
|
|
return cpu->GetRegValue(0, referenceIndex);
|
|
if (referenceIndex == REF_INDEX_PC)
|
|
return cpu->GetPC();
|
|
if (referenceIndex == REF_INDEX_HI)
|
|
return cpu->GetHi();
|
|
if (referenceIndex == REF_INDEX_LO)
|
|
return cpu->GetLo();
|
|
if (referenceIndex == REF_INDEX_THREAD)
|
|
return __KernelGetCurThread();
|
|
if (referenceIndex == REF_INDEX_MODULE)
|
|
return __KernelGetCurThreadModuleId();
|
|
if (referenceIndex == REF_INDEX_USEC)
|
|
return (uint32_t)CoreTiming::GetGlobalTimeUs(); // Loses information
|
|
if (referenceIndex == REF_INDEX_TICKS)
|
|
return (uint32_t)CoreTiming::GetTicks(currentMIPS);
|
|
if (referenceIndex == REF_INDEX_VCOUNT)
|
|
return (uint32_t)__DisplayGetVCount();
|
|
if (referenceIndex == REF_INDEX_FLIPS)
|
|
return (uint32_t)__DisplayGetFlipCount();
|
|
if ((referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)) < 32)
|
|
return cpu->GetRegValue(1, referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT));
|
|
if ((referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT)) < 128)
|
|
return cpu->GetRegValue(2, referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT));
|
|
return -1;
|
|
}
|
|
|
|
ExpressionType getReferenceType(uint32_t referenceIndex) override {
|
|
if (referenceIndex & REF_INDEX_IS_FLOAT) {
|
|
return EXPR_TYPE_FLOAT;
|
|
}
|
|
return EXPR_TYPE_UINT;
|
|
}
|
|
|
|
bool getMemoryValue(uint32_t address, int size, uint32_t& dest, std::string *error) override {
|
|
// We allow, but ignore, bad access.
|
|
// If we didn't, log/condition statements that reference registers couldn't be configured.
|
|
uint32_t valid = Memory::ClampValidSizeAt(address, size);
|
|
uint8_t buf[4]{};
|
|
if (valid != 0)
|
|
memcpy(buf, Memory::GetPointerUnchecked(address), valid);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
dest = buf[0];
|
|
return true;
|
|
case 2:
|
|
dest = (buf[1] << 8) | buf[0];
|
|
return true;
|
|
case 4:
|
|
dest = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
|
return true;
|
|
}
|
|
|
|
*error = StringFromFormat("Unexpected memory access size %d", size);
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
const DebugInterface *cpu;
|
|
};
|
|
|
|
unsigned int MIPSDebugInterface::readMemory(unsigned int address) const {
|
|
if (Memory::IsValidRange(address, 4))
|
|
return Memory::ReadUnchecked_Instruction(address).encoding;
|
|
return 0;
|
|
}
|
|
|
|
bool MIPSDebugInterface::isAlive()
|
|
{
|
|
return PSP_GetBootState() == BootState::Complete && coreState != CORE_RUNTIME_ERROR && coreState != CORE_POWERDOWN;
|
|
}
|
|
|
|
bool MIPSDebugInterface::isBreakpoint(unsigned int address)
|
|
{
|
|
return g_breakpoints.IsAddressBreakPoint(address);
|
|
}
|
|
|
|
void MIPSDebugInterface::setBreakpoint(unsigned int address) {
|
|
g_breakpoints.AddBreakPoint(address);
|
|
}
|
|
|
|
void MIPSDebugInterface::clearBreakpoint(unsigned int address) {
|
|
g_breakpoints.RemoveBreakPoint(address);
|
|
}
|
|
|
|
void MIPSDebugInterface::clearAllBreakpoints() {}
|
|
|
|
void MIPSDebugInterface::toggleBreakpoint(unsigned int address) {
|
|
if (g_breakpoints.IsAddressBreakPoint(address)) {
|
|
g_breakpoints.RemoveBreakPoint(address);
|
|
} else {
|
|
g_breakpoints.AddBreakPoint(address);
|
|
}
|
|
}
|
|
|
|
int MIPSDebugInterface::getColor(unsigned int address, bool darkMode) const {
|
|
uint32_t colors[6] = { 0xFFe0FFFF, 0xFFFFe0e0, 0xFFe8e8FF, 0xFFFFe0FF, 0xFFe0FFe0, 0xFFFFFFe0 };
|
|
uint32_t colorsDark[6] = { 0xFF301010, 0xFF103030, 0xFF403010, 0xFF103000, 0xFF301030, 0xFF101030 };
|
|
|
|
int n = g_symbolMap->GetFunctionNum(address);
|
|
if (n == -1) {
|
|
return darkMode ? 0xFF101010 : 0xFFFFFFFF;
|
|
} else if (darkMode) {
|
|
return colorsDark[n % ARRAY_SIZE(colorsDark)];
|
|
} else {
|
|
return colors[n % ARRAY_SIZE(colors)];
|
|
}
|
|
}
|
|
|
|
std::string MIPSDebugInterface::GetRegName(int cat, int index) {
|
|
static const char * const regName[32] = {
|
|
"zero", "at", "v0", "v1",
|
|
"a0", "a1", "a2", "a3",
|
|
"t0", "t1", "t2", "t3",
|
|
"t4", "t5", "t6", "t7",
|
|
"s0", "s1", "s2", "s3",
|
|
"s4", "s5", "s6", "s7",
|
|
"t8", "t9", "k0", "k1",
|
|
"gp", "sp", "fp", "ra"
|
|
};
|
|
static const char * const fpRegName[32] = {
|
|
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
|
|
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
|
|
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
|
|
"f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31",
|
|
};
|
|
|
|
if (cat == 0 && (unsigned)index < ARRAY_SIZE(regName)) {
|
|
return regName[index];
|
|
} else if (cat == 1 && (unsigned)index < ARRAY_SIZE(fpRegName)) {
|
|
return fpRegName[index];
|
|
} else if (cat == 2) {
|
|
return GetVectorNotation(index, V_Single);
|
|
}
|
|
return "???";
|
|
}
|
|
|
|
bool initExpression(const DebugInterface *debug, const char* exp, PostfixExpression& dest) {
|
|
MipsExpressionFunctions funcs(debug);
|
|
return initPostfixExpression(exp, &funcs, dest);
|
|
}
|
|
|
|
bool parseExpression(const DebugInterface *debug, PostfixExpression& exp, u32& dest) {
|
|
MipsExpressionFunctions funcs(debug);
|
|
return parsePostfixExpression(exp, &funcs, dest);
|
|
}
|
|
|
|
void DisAsm(u32 pc, char *out, size_t outSize) {
|
|
if (Memory::IsValid4AlignedAddress(pc))
|
|
MIPSDisAsm(Memory::Read_Opcode_JIT(pc), pc, out, outSize);
|
|
else
|
|
truncate_cpy(out, outSize, "-");
|
|
}
|