mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-02 10:45:14 +02:00
Add GPR write breakpoints (break when a register is written, anywhere)
New debugging primitive: break whenever any instruction writes to a
given general-purpose register (0-31), regardless of which address
executes the write. Requested for continuing the reboot.bin trace,
where the actual blocker is "what sets $s3 to this bad value", not
"what happens at a specific address" - existing address/memory
breakpoints can't express that directly.
- GPRBreakpoint (Core/Debugger/Breakpoints.h) mirrors the existing
BreakPoint/MemCheck shape (result/condition/logFormat/hit count),
keyed by register index instead of address/range.
- BreakpointManager keeps a u32 bitmask (bit i = register i has an
active breakpoint) alongside the GPRBreakpoint vector, so the
interpreter loop can test "would this write trip anything" with a
single shift+and against a value already cached in a local.
- RunUntilDowncountZeroWithChecks (Core/MIPS/MIPSTables.cpp) computes
the about-to-be-written register from the current instruction's
OUT_RT/OUT_RD/OUT_RA flags (GetGPRWriteTarget()) and checks it
against the mask, same convention as the existing memcheck handling
right above it (checked before the instruction executes, bails via
CORE_STEPPING_CPU without running it if tripped).
- New BreakReason::GPRBreakpoint ("cpu.gprBreakpoint") for Core_Break.
- WebSocket API: cpu.gprBreakpoint.add/update/remove/list, accepting
either a 0-31 'register' index or a case-insensitive 'name' (e.g.
"s3"), documented in docs/WebSocketDebugger.md.
Interpreter-only for now, deliberately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3f6743d40d
commit
75174af77b
@@ -94,6 +94,15 @@ size_t BreakpointManager::FindMemCheck(u32 start, u32 end) {
|
||||
return INVALID_MEMCHECK;
|
||||
}
|
||||
|
||||
size_t BreakpointManager::FindGPRBreakpoint(int reg) {
|
||||
for (size_t i = 0; i < gprBreakpoints_.size(); ++i) {
|
||||
if (gprBreakpoints_[i].reg == reg)
|
||||
return i;
|
||||
}
|
||||
|
||||
return INVALID_GPR_BREAKPOINT;
|
||||
}
|
||||
|
||||
bool BreakpointManager::IsAddressBreakPoint(u32 addr)
|
||||
{
|
||||
if (!anyBreakPoints_)
|
||||
@@ -493,6 +502,161 @@ BreakAction BreakpointManager::ExecOpMemCheck(u32 address, u32 pc) {
|
||||
return BREAK_ACTION_IGNORE;
|
||||
}
|
||||
|
||||
void BreakpointManager::RecomputeGPRBreakpointMask() {
|
||||
u32 mask = 0;
|
||||
for (const auto &bp : gprBreakpoints_) {
|
||||
if (bp.result != BREAK_ACTION_IGNORE)
|
||||
mask |= 1u << bp.reg;
|
||||
}
|
||||
gprBreakpointMask_ = mask;
|
||||
}
|
||||
|
||||
int BreakpointManager::AddGPRBreakpoint(int reg) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp == INVALID_GPR_BREAKPOINT) {
|
||||
GPRBreakpoint pt;
|
||||
pt.reg = reg;
|
||||
pt.result |= BREAK_ACTION_PAUSE;
|
||||
|
||||
gprBreakpoints_.push_back(pt);
|
||||
RecomputeGPRBreakpointMask();
|
||||
Update(INVALID_ADDRESS); // Not baked into JIT code, no cache invalidation needed.
|
||||
return (int)gprBreakpoints_.size() - 1;
|
||||
} else if (!gprBreakpoints_[bp].IsEnabled()) {
|
||||
gprBreakpoints_[bp].result |= BREAK_ACTION_PAUSE;
|
||||
gprBreakpoints_[bp].hasCond = false;
|
||||
RecomputeGPRBreakpointMask();
|
||||
Update(INVALID_ADDRESS);
|
||||
return (int)bp;
|
||||
} else {
|
||||
return (int)bp;
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::RemoveGPRBreakpoint(int reg) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
gprBreakpoints_.erase(gprBreakpoints_.begin() + bp);
|
||||
RecomputeGPRBreakpointMask();
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ChangeGPRBreakpoint(int reg, bool status) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
if (status)
|
||||
gprBreakpoints_[bp].result |= BREAK_ACTION_PAUSE;
|
||||
else
|
||||
gprBreakpoints_[bp].result = BreakAction(gprBreakpoints_[bp].result & ~BREAK_ACTION_PAUSE);
|
||||
RecomputeGPRBreakpointMask();
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ChangeGPRBreakpoint(int reg, BreakAction result) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
gprBreakpoints_[bp].result = result;
|
||||
RecomputeGPRBreakpointMask();
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ClearAllGPRBreakpoints() {
|
||||
if (!gprBreakpoints_.empty()) {
|
||||
gprBreakpoints_.clear();
|
||||
gprBreakpointMask_ = 0;
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ChangeGPRBreakpointAddCond(int reg, const BreakPointCond &cond) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
gprBreakpoints_[bp].hasCond = true;
|
||||
gprBreakpoints_[bp].cond = cond;
|
||||
// No need to update jit for a condition add/remove, they're not baked in.
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ChangeGPRBreakpointRemoveCond(int reg) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
gprBreakpoints_[bp].hasCond = false;
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
BreakPointCond *BreakpointManager::GetGPRBreakpointCondition(int reg) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT && gprBreakpoints_[bp].hasCond)
|
||||
return &gprBreakpoints_[bp].cond;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BreakpointManager::ChangeGPRBreakpointLogFormat(int reg, const std::string &fmt) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
gprBreakpoints_[bp].logFormat = fmt;
|
||||
Update(INVALID_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
bool BreakpointManager::IsGPRBreakpoint(int reg) {
|
||||
return (gprBreakpointMask_ & (1u << reg)) != 0;
|
||||
}
|
||||
|
||||
bool BreakpointManager::GetGPRBreakpoint(int reg, GPRBreakpoint *check) {
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp != INVALID_GPR_BREAKPOINT) {
|
||||
*check = gprBreakpoints_[bp];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<GPRBreakpoint> BreakpointManager::GetGPRBreakpoints() {
|
||||
return gprBreakpoints_;
|
||||
}
|
||||
|
||||
BreakAction BreakpointManager::ExecGPRBreakpoint(int reg, u32 pc) {
|
||||
// Callers are expected to have already checked GetGPRBreakpointMask() themselves (that's
|
||||
// the whole point of exposing it - a single shift+and in the hot interpreter loop, skipping
|
||||
// a function call entirely in the overwhelmingly common no-breakpoint case), but check again
|
||||
// here too since this is also reachable directly.
|
||||
if ((gprBreakpointMask_ & (1u << reg)) == 0)
|
||||
return BREAK_ACTION_IGNORE;
|
||||
size_t bp = FindGPRBreakpoint(reg);
|
||||
if (bp == INVALID_GPR_BREAKPOINT)
|
||||
return BREAK_ACTION_IGNORE;
|
||||
|
||||
GPRBreakpoint &info = gprBreakpoints_[bp];
|
||||
if (info.result == BREAK_ACTION_IGNORE)
|
||||
return BREAK_ACTION_IGNORE;
|
||||
|
||||
if (info.hasCond && !info.cond.Evaluate())
|
||||
return BREAK_ACTION_IGNORE;
|
||||
|
||||
++info.numHits;
|
||||
|
||||
if (info.result & BREAK_ACTION_LOG) {
|
||||
if (info.logFormat.empty()) {
|
||||
NOTICE_LOG(Log::JIT, "BKP GPR write r%d, PC=%08x (%s)", reg, pc, g_symbolMap->GetDescription(pc).c_str());
|
||||
} else {
|
||||
std::string formatted;
|
||||
BreakpointManager::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted);
|
||||
NOTICE_LOG(Log::JIT, "BKP GPR write r%d, PC=%08x: %s", reg, pc, formatted.c_str());
|
||||
}
|
||||
}
|
||||
if (info.result & BREAK_ACTION_PAUSE) {
|
||||
Core_Break(BreakReason::GPRBreakpoint, pc);
|
||||
}
|
||||
|
||||
return info.result;
|
||||
}
|
||||
|
||||
void BreakpointManager::SetSkipFirst(u32 pc) {
|
||||
breakSkipFirstAt_ = pc;
|
||||
breakSkipFirstTicks_ = CoreTiming::GetTicks(currentMIPS);
|
||||
|
||||
Reference in New Issue
Block a user