debugger: Rework PPC debugger

This is a first pass for now, more work is needed mostly for UI polish.
Changes in this commit:
- Debugger now exposes a thread-safe API. Previously everything would just access internal debugger state directly without regard for race conditions
- Reworked stepping logic to be more reliable. For example previously Cemu could get forever stuck in the middle of stepping to the next instruction
- Support for debugging while using multi-threaded CPU emulation. Originally the debugger was added when only single core support existed in Cemu. It was possible to debug multi-threaded before but it was very very prone to state corruptions.
- Debugger should now remember breakpoints correctly across sessions
This commit is contained in:
Exzap 2026-03-27 20:55:49 +01:00
parent 1c2b7d787e
commit e2a69becf1
14 changed files with 659 additions and 489 deletions

View File

@ -13,13 +13,50 @@
#include <Windows.h>
#endif
void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false);
DebuggerDispatcher g_debuggerDispatcher;
debuggerState_t debuggerState{ };
struct DebuggerState
{
bool breakOnEntry{false};
bool logOnlyMemoryBreakpoints{false};
// breakpoints
std::recursive_mutex breakpointsMtx;
std::vector<DebuggerBreakpoint*> breakpoints;
// patches
std::vector<DebuggerPatch*> patches;
// active memory breakpoint (only one for now)
DebuggerBreakpoint* activeMemoryBreakpoint{};
// debugging session
struct
{
std::mutex debugSessionMtx;
std::atomic_bool shouldBreak; // request any thread to break asap
std::atomic_bool isTrapped{false};
uint32 debuggedThreadMPTR{};
PPCInterpreter_t* hCPU{};
// step control
std::atomic<DebuggerStepCommand> stepCommand{DebuggerStepCommand::None};
}debugSession;
};
DebuggerState s_debuggerState{};
std::vector<DebuggerBreakpoint*>& debugger_lockBreakpoints()
{
s_debuggerState.breakpointsMtx.lock();
return s_debuggerState.breakpoints;
}
void debugger_unlockBreakpoints()
{
s_debuggerState.breakpointsMtx.unlock();
}
DebuggerBreakpoint* debugger_getFirstBP(uint32 address)
{
for (auto& it : debuggerState.breakpoints)
for (auto& it : s_debuggerState.breakpoints)
{
if (it->address == address)
return it;
@ -29,7 +66,7 @@ DebuggerBreakpoint* debugger_getFirstBP(uint32 address)
DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType)
{
for (auto& it : debuggerState.breakpoints)
for (auto& it : s_debuggerState.breakpoints)
{
if (it->address == address)
{
@ -46,6 +83,21 @@ DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType)
return nullptr;
}
DebuggerBreakpoint* debugger_getBreakpointById(BreakpointId bpId)
{
for (auto& it : s_debuggerState.breakpoints)
{
DebuggerBreakpoint* chain = it;
while (chain)
{
if (chain->id == bpId)
return chain;
chain = chain->next;
}
}
return nullptr;
}
bool debuggerBPChain_hasType(DebuggerBreakpoint* bp, uint8 bpType)
{
while (bp)
@ -69,7 +121,7 @@ void debuggerBPChain_add(uint32 address, DebuggerBreakpoint* bp)
return;
}
// no breakpoint chain exists for this address
debuggerState.breakpoints.push_back(bp);
s_debuggerState.breakpoints.push_back(bp);
}
uint32 debugger_getAddressOriginalOpcode(uint32 address)
@ -148,7 +200,7 @@ void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp)
std::vector<std::thread::native_handle_type> schedulerThreadHandles = coreinit::OSGetSchedulerThreads();
#if BOOST_OS_WINDOWS
debuggerState.activeMemoryBreakpoint = bp;
s_debuggerState.activeMemoryBreakpoint = bp;
for (auto& hThreadNH : schedulerThreadHandles)
{
HANDLE hThread = (HANDLE)hThreadNH;
@ -156,7 +208,7 @@ void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp)
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SuspendThread(hThread);
GetThreadContext(hThread, &ctx);
if (debuggerState.activeMemoryBreakpoint)
if (s_debuggerState.activeMemoryBreakpoint)
{
ctx.Dr0 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address);
ctx.Dr1 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address);
@ -196,24 +248,24 @@ void debugger_handleSingleStepException(uint64 dr6)
if (triggeredDR0 && triggeredDR1)
{
// write (and read) access
if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
if (s_debuggerState.activeMemoryBreakpoint && s_debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
catchBP = true;
}
else
{
// read access
if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ)
if (s_debuggerState.activeMemoryBreakpoint && s_debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ)
catchBP = true;
}
if (catchBP)
{
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if (debuggerState.logOnlyMemoryBreakpoints)
if (s_debuggerState.logOnlyMemoryBreakpoints)
{
float memValueF = memory_readFloat(debuggerState.activeMemoryBreakpoint->address);
uint32 memValue = memory_readU32(debuggerState.activeMemoryBreakpoint->address);
float memValueF = memory_readFloat(s_debuggerState.activeMemoryBreakpoint->address);
uint32 memValue = memory_readU32(s_debuggerState.activeMemoryBreakpoint->address);
cemuLog_log(LogType::Force, "[Debugger] 0x{:08X} was read/written! New Value: 0x{:08X} (float {}) IP: {:08X} LR: {:08X}",
debuggerState.activeMemoryBreakpoint->address,
s_debuggerState.activeMemoryBreakpoint->address,
memValue,
memValueF,
hCPU->instructionPointer,
@ -243,10 +295,10 @@ void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite)
DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, 0xFFFFFFFF, bpType, true);
debuggerBPChain_add(address, bp);
// disable any already existing memory breakpoint
if (debuggerState.activeMemoryBreakpoint)
if (s_debuggerState.activeMemoryBreakpoint)
{
debuggerState.activeMemoryBreakpoint->enabled = false;
debuggerState.activeMemoryBreakpoint = nullptr;
s_debuggerState.activeMemoryBreakpoint->enabled = false;
s_debuggerState.activeMemoryBreakpoint = nullptr;
}
// activate new breakpoint
debugger_updateMemoryBreakpoint(bp);
@ -254,42 +306,44 @@ void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite)
void debugger_handleEntryBreakpoint(uint32 address)
{
if (!debuggerState.breakOnEntry)
if (!s_debuggerState.breakOnEntry)
return;
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
void debugger_deleteBreakpoint(BreakpointId bpId)
{
for (auto& it : debuggerState.breakpoints)
std::unique_lock _l(s_debuggerState.breakpointsMtx);
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (!bp)
return;
for (auto& it : s_debuggerState.breakpoints)
{
if (it->address == bp->address)
{
// for execution breakpoints make sure the instruction is restored
if (bp->isExecuteBP())
if (it->isExecuteBP())
{
bp->enabled = false;
debugger_updateExecutionBreakpoint(bp->address);
it->enabled = false;
debugger_updateExecutionBreakpoint(it->address);
}
// remove
if (it == bp)
{
// remove first in list
debuggerState.breakpoints.erase(std::remove(debuggerState.breakpoints.begin(), debuggerState.breakpoints.end(), bp), debuggerState.breakpoints.end());
s_debuggerState.breakpoints.erase(std::remove(s_debuggerState.breakpoints.begin(), s_debuggerState.breakpoints.end(), bp), s_debuggerState.breakpoints.end());
DebuggerBreakpoint* nextBP = bp->next;
if (nextBP)
debuggerState.breakpoints.push_back(nextBP);
s_debuggerState.breakpoints.push_back(nextBP);
}
else
{
// remove from list
DebuggerBreakpoint* bpItr = it;
while (bpItr->next != bp)
{
bpItr = bpItr->next;
}
cemu_assert_debug(bpItr->next != bp);
cemu_assert_debug(bpItr->next == bp);
bpItr->next = bp->next;
}
delete bp;
@ -300,11 +354,12 @@ void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
void debugger_toggleExecuteBreakpoint(uint32 address)
{
std::unique_lock _l(s_debuggerState.breakpointsMtx);
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_NORMAL);
if (existingBP)
{
{
// delete existing breakpoint
debugger_deleteBreakpoint(existingBP);
debugger_deleteBreakpoint(existingBP->id);
}
else
{
@ -315,11 +370,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
void debugger_toggleLoggingBreakpoint(uint32 address)
{
std::unique_lock _l(s_debuggerState.breakpointsMtx);
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
if (existingBP)
{
// delete existing breakpoint
debugger_deleteBreakpoint(existingBP);
debugger_deleteBreakpoint(existingBP->id);
}
else
{
@ -328,20 +384,55 @@ void debugger_toggleLoggingBreakpoint(uint32 address)
}
}
void debugger_forceBreak()
{
debuggerState.debugSession.shouldBreak = true;
}
bool debugger_isTrapped()
{
return debuggerState.debugSession.isTrapped;
return s_debuggerState.debugSession.isTrapped;
}
PPCInterpreter_t* debugger_lockDebugSession()
{
s_debuggerState.debugSession.debugSessionMtx.lock();
if (!s_debuggerState.debugSession.isTrapped)
{
s_debuggerState.debugSession.debugSessionMtx.unlock();
return nullptr;
}
return s_debuggerState.debugSession.hCPU;
}
void debugger_unlockDebugSession(PPCInterpreter_t* hCPU)
{
cemu_assert_debug(s_debuggerState.debugSession.isTrapped);
cemu_assert_debug(s_debuggerState.debugSession.hCPU == hCPU);
s_debuggerState.debugSession.debugSessionMtx.unlock();
}
PPCSnapshot debugger_getSnapshotFromSession(PPCInterpreter_t* hCPU)
{
cemu_assert_debug(s_debuggerState.debugSession.isTrapped);
cemu_assert_debug(s_debuggerState.debugSession.hCPU == hCPU);
PPCSnapshot snapshot{};
memcpy(snapshot.gpr, hCPU->gpr, sizeof(uint32) * 32);
memcpy(snapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32);
snapshot.spr_lr = hCPU->spr.LR;
for (uint32 i = 0; i < 32; i++)
snapshot.cr[i] = hCPU->cr[i];
return snapshot;
}
void debugger_resume()
{
// if there is a breakpoint on the current instruction then do a single 'step into' to skip it
debuggerState.debugSession.run = true;
s_debuggerState.debugSession.stepCommand = DebuggerStepCommand::Run;
}
void debugger_stepCommand(DebuggerStepCommand stepCommand)
{
s_debuggerState.debugSession.stepCommand = stepCommand;
}
void debugger_requestBreak()
{
s_debuggerState.debugSession.shouldBreak = true;
}
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp)
@ -351,6 +442,8 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
{
if (bpItr == bp)
{
if (bp->enabled == state)
return;
if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING)
{
bp->enabled = state;
@ -360,13 +453,13 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
else if (bpItr->isMemBP())
{
// disable other memory breakpoints
for (auto& it : debuggerState.breakpoints)
for (auto& it : s_debuggerState.breakpoints)
{
DebuggerBreakpoint* bpItr2 = it;
while (bpItr2)
{
if (bpItr2->isMemBP() && bpItr2 != bp)
{
{
bpItr2->enabled = false;
}
bpItr2 = bpItr2->next;
@ -410,9 +503,9 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
}
}
// merge with existing patches if the ranges touch
for(sint32 i=0; i<debuggerState.patches.size(); i++)
for(sint32 i=0; i<s_debuggerState.patches.size(); i++)
{
auto& patchItr = debuggerState.patches[i];
auto& patchItr = s_debuggerState.patches[i];
if (address + patchData.size() >= patchItr->address && address <= patchItr->address + patchItr->length)
{
uint32 newAddress = std::min(patch->address, patchItr->address);
@ -434,11 +527,11 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
patch = newPatch;
delete patchItr;
// remove currently iterated patch
debuggerState.patches.erase(debuggerState.patches.begin()+i);
s_debuggerState.patches.erase(s_debuggerState.patches.begin()+i);
i--;
}
}
debuggerState.patches.push_back(patch);
s_debuggerState.patches.push_back(patch);
// apply patch (if breakpoints exist then update those instead of actual data)
if ((address & 3) != 0)
cemu_assert_debug(false);
@ -468,7 +561,7 @@ void debugger_createPatch(uint32 address, std::span<uint8> patchData)
bool debugger_hasPatch(uint32 address)
{
for (auto& patch : debuggerState.patches)
for (auto& patch : s_debuggerState.patches)
{
if (address + 4 > patch->address && address < patch->address + patch->length)
return true;
@ -478,15 +571,15 @@ bool debugger_hasPatch(uint32 address)
void debugger_removePatch(uint32 address)
{
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
for (sint32 i = 0; i < s_debuggerState.patches.size(); i++)
{
auto& patch = debuggerState.patches[i];
auto& patch = s_debuggerState.patches[i];
if (address < patch->address || address >= (patch->address + patch->length))
continue;
MPTR startAddress = patch->address;
MPTR endAddress = patch->address + patch->length;
// remove any breakpoints overlapping with the patch
for (auto& bp : debuggerState.breakpoints)
for (auto& bp : s_debuggerState.breakpoints)
{
if (bp->address + 4 > startAddress && bp->address < endAddress)
{
@ -499,228 +592,205 @@ void debugger_removePatch(uint32 address)
PPCRecompiler_invalidateRange(startAddress, endAddress);
// remove patch
delete patch;
debuggerState.patches.erase(debuggerState.patches.begin() + i);
s_debuggerState.patches.erase(s_debuggerState.patches.begin() + i);
return;
}
}
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
bool debugger_CanStepOverInstruction(MPTR address)
{
bool isRecEnabled = ppcRecompilerEnabled;
ppcRecompilerEnabled = false;
uint32 initialIP = debuggerState.debugSession.instructionPointer;
debugger_updateExecutionBreakpoint(initialIP, true);
PPCInterpreterSlim_executeInstruction(hCPU);
debugger_updateExecutionBreakpoint(initialIP);
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
if(updateDebuggerWindow)
g_debuggerDispatcher.MoveIP();
ppcRecompilerEnabled = isRecEnabled;
}
bool debugger_stepOver(PPCInterpreter_t* hCPU)
{
bool isRecEnabled = ppcRecompilerEnabled;
ppcRecompilerEnabled = false;
// disassemble current instruction
PPCDisassembledInstruction disasmInstr = { 0 };
uint32 initialIP = debuggerState.debugSession.instructionPointer;
debugger_updateExecutionBreakpoint(initialIP, true);
ppcAssembler_disassemble(initialIP, memory_readU32(initialIP), &disasmInstr);
if (disasmInstr.ppcAsmCode != PPCASM_OP_BL &&
disasmInstr.ppcAsmCode != PPCASM_OP_BCTRL)
ppcAssembler_disassemble(address, debugger_getAddressOriginalOpcode(address), &disasmInstr);
return disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA || disasmInstr.ppcAsmCode == PPCASM_OP_BCTRL;
}
void debugger_handleLoggingBreakpoint(PPCInterpreter_t* hCPU, DebuggerBreakpoint* bp)
{
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
{
// nothing to skip, use step-into
debugger_stepInto(hCPU);
debugger_updateExecutionBreakpoint(initialIP);
g_debuggerDispatcher.MoveIP();
ppcRecompilerEnabled = isRecEnabled;
return false;
}
// create one-shot breakpoint at next instruction
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
// step over current instruction (to avoid breakpoint)
debugger_stepInto(hCPU);
g_debuggerDispatcher.MoveIP();
// restore breakpoints
debugger_updateExecutionBreakpoint(initialIP);
// run
size_t pos = 0;
while ((pos = comment.find(prefix, pos)) != std::string::npos)
{
size_t endPos = comment.find('}', pos);
if (endPos == std::string::npos)
break;
try
{
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
{
std::string replacement = formatFunc(regNum);
comment.replace(pos, endPos - pos + 1, replacement);
pos += replacement.length();
}
else
{
pos = endPos + 1;
}
}
catch (...)
{
pos = endPos + 1;
}
}
};
// Replace integer register placeholders {rX}
replacePlaceholders("{r", [&](int regNum) {
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
});
// Replace floating point register placeholders {fX}
replacePlaceholders("{f", [&](int regNum) {
return fmt::format("{}", hCPU->fpr[regNum].fpr);
});
std::string logName = "Breakpoint '" + comment + "'";
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
if (cemuLog_advancedPPCLoggingEnabled())
DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]);
}
// used to escape the breakpoint on the current instruction
void debugger_stepOverCurrentBreakpoint(PPCInterpreter_t* hCPU)
{
std::unique_lock _l(s_debuggerState.breakpointsMtx);
bool isRecEnabled = ppcRecompilerEnabled;
ppcRecompilerEnabled = false;
MPTR bpAddress = hCPU->instructionPointer;
debugger_updateExecutionBreakpoint(bpAddress, true);
PPCInterpreterSlim_executeInstruction(hCPU);
debugger_updateExecutionBreakpoint(bpAddress);
ppcRecompilerEnabled = isRecEnabled;
return true;
}
void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
void debugger_enterTW(PPCInterpreter_t* hCPU, bool isSingleStep)
{
memcpy(debuggerState.debugSession.ppcSnapshot.gpr, hCPU->gpr, sizeof(uint32) * 32);
memcpy(debuggerState.debugSession.ppcSnapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32);
debuggerState.debugSession.ppcSnapshot.spr_lr = hCPU->spr.LR;
for (uint32 i = 0; i < 32; i++)
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
}
void debugger_enterTW(PPCInterpreter_t* hCPU)
{
// Currently, we don't support multiple threads inside the debugger. Spin loop a thread if we already paused for another breakpoint hit.
while (debuggerState.debugSession.isTrapped)
s_debuggerState.debugSession.debugSessionMtx.lock();
while ( true )
{
bool trappedExpectedVal = false;
if ( s_debuggerState.debugSession.isTrapped.compare_exchange_strong(trappedExpectedVal, true) )
break;
s_debuggerState.debugSession.debugSessionMtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
s_debuggerState.debugSession.debugSessionMtx.lock();
}
// init debug state
s_debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
s_debuggerState.debugSession.hCPU = hCPU;
s_debuggerState.debugSession.stepCommand = DebuggerStepCommand::None;
s_debuggerState.debugSession.debugSessionMtx.unlock();
// handle logging points
DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer);
bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT);
while (bp)
{
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
{
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
{
size_t pos = 0;
while ((pos = comment.find(prefix, pos)) != std::string::npos)
{
size_t endPos = comment.find('}', pos);
if (endPos == std::string::npos)
break;
try
{
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
{
std::string replacement = formatFunc(regNum);
comment.replace(pos, endPos - pos + 1, replacement);
pos += replacement.length();
}
else
{
pos = endPos + 1;
}
}
catch (...)
{
pos = endPos + 1;
}
}
};
// Replace integer register placeholders {rX}
replacePlaceholders("{r", [&](int regNum) {
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
});
// Replace floating point register placeholders {fX}
replacePlaceholders("{f", [&](int regNum) {
return fmt::format("{}", hCPU->fpr[regNum].fpr);
});
std::string logName = "Breakpoint '" + comment + "'";
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
if (cemuLog_advancedPPCLoggingEnabled())
DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]);
break;
}
debugger_handleLoggingBreakpoint(hCPU, bp);
bp = bp->next;
}
// return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates
if (!shouldBreak)
// for logging breakpoints skip the waiting and notifying of the UI
DebuggerStepCommand stepCommand = DebuggerStepCommand::Run;
if (shouldBreak || isSingleStep)
{
uint32 backupIP = debuggerState.debugSession.instructionPointer;
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
debugger_stepInto(hCPU, false);
PPCInterpreterSlim_executeInstruction(hCPU);
debuggerState.debugSession.instructionPointer = backupIP;
return;
}
// handle breakpoints
debuggerState.debugSession.isTrapped = true;
debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
debuggerState.debugSession.hCPU = hCPU;
debugger_createPPCStateSnapshot(hCPU);
// remove one-shot breakpoint if it exists
DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(debuggerState.debugSession.instructionPointer, DEBUGGER_BP_T_ONE_SHOT);
if (singleshotBP)
debugger_deleteBreakpoint(singleshotBP);
g_debuggerDispatcher.NotifyDebugBreakpointHit();
g_debuggerDispatcher.UpdateViewThreadsafe();
// reset step control
debuggerState.debugSession.stepInto = false;
debuggerState.debugSession.stepOver = false;
debuggerState.debugSession.run = false;
while (debuggerState.debugSession.isTrapped)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// check for step commands
if (debuggerState.debugSession.stepOver)
// remove one-shot breakpoint if it exists
DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(hCPU->instructionPointer, DEBUGGER_BP_T_ONE_SHOT);
if (singleshotBP)
debugger_deleteBreakpoint(singleshotBP->id);
g_debuggerDispatcher.NotifyDebugBreakpointHit();
g_debuggerDispatcher.UpdateViewThreadsafe();
while (s_debuggerState.debugSession.isTrapped)
{
if (debugger_stepOver(hCPU))
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// check for step commands from UI
stepCommand = s_debuggerState.debugSession.stepCommand.exchange(DebuggerStepCommand::None);
if (stepCommand != DebuggerStepCommand::None)
break;
if (!coreinit::OSIsSchedulerActive())
{
debugger_createPPCStateSnapshot(hCPU);
break; // if true is returned, continue with execution
// quit if scheduler is being shutdown
stepCommand = DebuggerStepCommand::Run;
break;
}
debugger_createPPCStateSnapshot(hCPU);
g_debuggerDispatcher.UpdateViewThreadsafe();
debuggerState.debugSession.stepOver = false;
}
if (debuggerState.debugSession.stepInto)
if (stepCommand == DebuggerStepCommand::StepOver && !debugger_CanStepOverInstruction(hCPU->instructionPointer))
{
debugger_stepInto(hCPU);
debugger_createPPCStateSnapshot(hCPU);
g_debuggerDispatcher.UpdateViewThreadsafe();
debuggerState.debugSession.stepInto = false;
continue;
}
if (debuggerState.debugSession.run)
{
debugger_createPPCStateSnapshot(hCPU);
debugger_stepInto(hCPU, false);
PPCInterpreterSlim_executeInstruction(hCPU);
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
debuggerState.debugSession.run = false;
break;
stepCommand = DebuggerStepCommand::StepInto;
}
g_debuggerDispatcher.UpdateViewThreadsafe();
g_debuggerDispatcher.NotifyRun();
}
s_debuggerState.debugSession.debugSessionMtx.lock();
s_debuggerState.debugSession.hCPU = nullptr;
s_debuggerState.debugSession.isTrapped = false;
s_debuggerState.debugSession.debugSessionMtx.unlock();
// leave the breakpointed instruction
MPTR initialIP = hCPU->instructionPointer;
debugger_stepOverCurrentBreakpoint(hCPU);
debuggerState.debugSession.isTrapped = false;
debuggerState.debugSession.hCPU = nullptr;
g_debuggerDispatcher.UpdateViewThreadsafe();
g_debuggerDispatcher.NotifyRun();
}
// todo - if the current instruction is a HLE gateway, then we need to handle it with special logic (instead of generic debugger_stepOverCurrentBreakpoint):
// First grab the HLE index from the original opcode and look up the function pointer
// And then call the HLE func (must happen last, since it may not return immediately or ever)
void debugger_shouldBreak(PPCInterpreter_t* hCPU)
{
if(debuggerState.debugSession.shouldBreak
// exclude emulator trampoline area
&& (hCPU->instructionPointer < MEMORY_CODE_TRAMPOLINE_AREA_ADDR || hCPU->instructionPointer > MEMORY_CODE_TRAMPOLINE_AREA_ADDR + MEMORY_CODE_TRAMPOLINE_AREA_SIZE))
if (stepCommand == DebuggerStepCommand::StepInto)
{
debuggerState.debugSession.shouldBreak = false;
const uint32 address = (uint32)hCPU->instructionPointer;
assert_dbg();
//debugger_createBreakpoint(address, DEBUGGER_BP_TYPE_ONE_SHOT);
debugger_enterTW(hCPU, true);
}
else if (stepCommand == DebuggerStepCommand::StepOver)
{
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
}
}
void debugger_addParserSymbols(class ExpressionParser& ep)
{
const auto module_count = RPLLoader_GetModuleCount();
const auto module_list = RPLLoader_GetModuleList();
std::vector<double> module_tmp(module_count);
for (int i = 0; i < module_count; i++)
const auto moduleCount = RPLLoader_GetModuleCount();
const auto moduleList = RPLLoader_GetModuleList();
std::vector<double> moduleTmp(moduleCount);
for (sint32 i = 0; i < moduleCount; i++)
{
const auto module = module_list[i];
const auto module = moduleList[i];
if (module)
{
module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
ep.AddConstant(module->moduleName2, module_tmp[i]);
moduleTmp[i] = (double)module->regionMappingBase_text.GetMPTR();
ep.AddConstant(module->moduleName2, moduleTmp[i]);
}
}
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
if (hCPU)
{
PPCSnapshot snapshot = debugger_getSnapshotFromSession(hCPU);
for (sint32 i = 0; i < 32; i++)
ep.AddConstant(fmt::format("r{}", i), snapshot.gpr[i]);
debugger_unlockDebugSession(hCPU);
}
}
for (sint32 i = 0; i < 32; i++)
ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]);
}
void debugger_jumpToAddressInDisasm(MPTR address)
{
g_debuggerDispatcher.MoveToAddressInDisassembly(address);
}
void debugger_setOptionBreakOnEntry(bool isEnabled)
{
s_debuggerState.breakOnEntry = isEnabled;
}
void debugger_setOptionLogOnlyMemoryBreakpoints(bool isEnabled)
{
s_debuggerState.logOnlyMemoryBreakpoints = isEnabled;
}
bool debugger_getOptionBreakOnEntry()
{
return s_debuggerState.breakOnEntry;
}
bool debugger_getOptionLogOnlyMemoryBreakpoints()
{
return s_debuggerState.logOnlyMemoryBreakpoints;
}

View File

@ -21,9 +21,9 @@ class DebuggerCallbacks
virtual void UpdateViewThreadsafe() {}
virtual void NotifyDebugBreakpointHit() {}
virtual void NotifyRun() {}
virtual void MoveIP() {}
virtual void NotifyModuleLoaded(void* module) {}
virtual void NotifyModuleUnloaded(void* module) {}
virtual void MoveToAddressInDisassembly(MPTR address) {}
virtual void NotifyModuleLoaded(struct RPLModule* module) {}
virtual void NotifyModuleUnloaded(struct RPLModule* module) {}
virtual void NotifyGraphicPacksModified() {}
virtual ~DebuggerCallbacks() = default;
};
@ -64,17 +64,17 @@ class DebuggerDispatcher
m_callbacks->NotifyRun();
}
void MoveIP()
void MoveToAddressInDisassembly(MPTR address)
{
m_callbacks->MoveIP();
m_callbacks->MoveToAddressInDisassembly(address);
}
void NotifyModuleLoaded(void* module)
void NotifyModuleLoaded(struct RPLModule* module)
{
m_callbacks->NotifyModuleLoaded(module);
}
void NotifyModuleUnloaded(void* module)
void NotifyModuleUnloaded(struct RPLModule* module)
{
m_callbacks->NotifyModuleUnloaded(module);
}
@ -85,8 +85,11 @@ class DebuggerDispatcher
}
} extern g_debuggerDispatcher;
using BreakpointId = uint64;
struct DebuggerBreakpoint
{
BreakpointId id;
uint32 address;
uint32 originalOpcodeValue;
mutable uint8 bpType;
@ -97,10 +100,11 @@ struct DebuggerBreakpoint
DebuggerBreakpoint(uint32 address, uint32 originalOpcode, uint8 bpType = 0, bool enabled = true, std::wstring comment = std::wstring())
:address(address), originalOpcodeValue(originalOpcode), bpType(bpType), enabled(enabled), comment(std::move(comment))
{
static std::atomic<uint64_t> bpIdGen{1};
id = bpIdGen.fetch_add(1);
next = nullptr;
}
bool operator<(const DebuggerBreakpoint& rhs) const
{
return address < rhs.address;
@ -133,64 +137,53 @@ struct DebuggerPatch
struct PPCSnapshot
{
uint32 gpr[32];
FPR_t fpr[32];
uint8 cr[32];
uint32 spr_lr;
uint32 gpr[32]{};
FPR_t fpr[32]{};
uint8 cr[32]{};
uint32 spr_lr{};
};
typedef struct
enum class DebuggerStepCommand : uint8
{
bool breakOnEntry;
bool logOnlyMemoryBreakpoints;
// breakpoints
std::vector<DebuggerBreakpoint*> breakpoints;
std::vector<DebuggerPatch*> patches;
DebuggerBreakpoint* activeMemoryBreakpoint;
// debugging state
struct
{
volatile bool shouldBreak; // debugger window requests a break asap
volatile bool isTrapped; // if set, breakpoint is active and stepping through the code is possible
uint32 debuggedThreadMPTR;
volatile uint32 instructionPointer;
PPCInterpreter_t* hCPU;
// step control
volatile bool stepOver;
volatile bool stepInto;
volatile bool run;
// snapshot of PPC state
PPCSnapshot ppcSnapshot;
}debugSession;
None,
StepInto,
StepOver,
Run
};
}debuggerState_t;
extern debuggerState_t debuggerState;
// new API
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
// breakpoint API
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
void debugger_deleteBreakpoint(BreakpointId bpId);
std::vector<DebuggerBreakpoint*>& debugger_lockBreakpoints();
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
DebuggerBreakpoint* debugger_getBreakpointById(BreakpointId bpId);
void debugger_unlockBreakpoints();
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
void debugger_handleEntryBreakpoint(uint32 address);
void debugger_deleteBreakpoint(DebuggerBreakpoint* bp);
void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false);
// patch API
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
bool debugger_hasPatch(uint32 address);
void debugger_removePatch(uint32 address);
void debugger_forceBreak(); // force breakpoint at the next possible instruction
// debug session
PPCInterpreter_t* debugger_lockDebugSession();
void debugger_unlockDebugSession(PPCInterpreter_t* hCPU);
PPCSnapshot debugger_getSnapshotFromSession(PPCInterpreter_t* hCPU);
void debugger_stepCommand(DebuggerStepCommand stepCommand);
// misc
void debugger_requestBreak(); // break at the next possible instruction (any thread)
bool debugger_isTrapped();
void debugger_resume();
void debugger_handleEntryBreakpoint(uint32 address);
void debugger_enterTW(PPCInterpreter_t* hCPU, bool isSingleStep = false);
void debugger_jumpToAddressInDisasm(MPTR address);
void debugger_addParserSymbols(class ExpressionParser& ep);
void debugger_enterTW(PPCInterpreter_t* hCPU);
void debugger_shouldBreak(PPCInterpreter_t* hCPU);
void debugger_addParserSymbols(class ExpressionParser& ep);
// options
void debugger_setOptionBreakOnEntry(bool isEnabled);
bool debugger_getOptionBreakOnEntry();
void debugger_setOptionLogOnlyMemoryBreakpoints(bool isEnabled);
bool debugger_getOptionLogOnlyMemoryBreakpoints();

View File

@ -1483,6 +1483,11 @@ namespace coreinit
s_threadToFiber.clear();
}
bool OSIsSchedulerActive()
{
return sSchedulerActive;
}
SysAllocator<OSThread_t, PPC_CORE_COUNT> s_defaultThreads;
SysAllocator<uint8, PPC_CORE_COUNT * 1024 * 1024> s_stack;

View File

@ -610,6 +610,7 @@ namespace coreinit
// scheduler
void OSSchedulerBegin(sint32 numCPUEmulationThreads);
void OSSchedulerEnd();
bool OSIsSchedulerActive();
// internal
void __OSAddReadyThreadToRunQueue(OSThread_t* thread);

View File

@ -436,7 +436,7 @@ int CemuApp::FilterEvent(wxEvent& event)
}
// track if debugger window or its child windows are focused
if (g_debugger_window && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
if (s_debuggerWindow && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
{
wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow);
@ -449,12 +449,12 @@ int CemuApp::FilterEvent(wxEvent& event)
wxWindow* window_it = target_window;
while (window_it)
{
if (window_it == g_debugger_window) g_window_info.debugger_focused = true;
if (window_it == s_debuggerWindow) g_window_info.debugger_focused = true;
window_it = window_it->GetParent();
}
}
}
else if (!g_debugger_window)
else if (!s_debuggerWindow)
{
g_window_info.debugger_focused = false;
}

View File

@ -443,6 +443,12 @@ wxString MainWindow::GetInitialWindowTitle()
void MainWindow::OnClose(wxCloseEvent& event)
{
if (m_debugger_window)
{
m_debugger_window->CleanupForDestroy();
m_debugger_window->Destroy();
m_debugger_window = nullptr;
}
if(m_game_list)
m_game_list->OnClose(event);

View File

@ -101,42 +101,40 @@ void BreakpointWindow::OnUpdateView()
Freeze();
m_breakpoints->DeleteAllItems();
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
if (!debuggerState.breakpoints.empty())
uint32 i = 0;
for (const auto bpBase : breakpoints)
{
uint32_t i = 0;
for (const auto bpBase : debuggerState.breakpoints)
DebuggerBreakpoint* bp = bpBase;
while (bp)
{
DebuggerBreakpoint* bp = bpBase;
while (bp)
{
wxListItem item = {};
item.SetId(i++);
wxListItem item = {};
item.SetId(i++);
const auto index = m_breakpoints->InsertItem(item);
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
const char* typeName = "UKN";
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
typeName = "X";
else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
typeName = "LOG";
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
typeName = "XS";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
typeName = "R";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
typeName = "W";
const auto index = m_breakpoints->InsertItem(item);
m_breakpoints->SetItemPtrData(index, (uintptr_t)bp->id);
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
const char* typeName = "UKN";
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
typeName = "X";
else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
typeName = "LOG";
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
typeName = "XS";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
typeName = "R";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
typeName = "W";
m_breakpoints->SetItem(index, ColumnType, typeName);
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
m_breakpoints->CheckItem(index, bp->enabled);
m_breakpoints->SetItemPtrData(index, (wxUIntPtr)bp);
m_breakpoints->SetItem(index, ColumnType, typeName);
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
m_breakpoints->CheckItem(index, bp->enabled);
bp = bp->next;
}
bp = bp->next;
}
}
debugger_unlockBreakpoints();
Thaw();
}
@ -147,15 +145,16 @@ void BreakpointWindow::OnGameLoaded()
void BreakpointWindow::OnBreakpointToggled(wxListEvent& event)
{
const int32_t index = event.GetIndex();
if (0 <= index && index < m_breakpoints->GetItemCount())
const sint32 index = event.GetIndex();
uint64 bpId = (uint64)event.GetData();
debugger_lockBreakpoints();
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (bp)
{
const bool state = m_breakpoints->IsItemChecked(index);
wxString line = m_breakpoints->GetItemText(index, ColumnAddress);
DebuggerBreakpoint* bp = (DebuggerBreakpoint*)m_breakpoints->GetItemData(index);
const uint32 address = std::stoul(line.ToStdString(), nullptr, 16);
debugger_toggleBreakpoint(address, state, bp);
debugger_toggleBreakpoint(bp->address, state, bp);
}
debugger_unlockBreakpoints();
}
void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
@ -178,8 +177,7 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
{
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
debuggerState.debugSession.instructionPointer = address;
g_debuggerDispatcher.MoveIP();
g_debuggerDispatcher.MoveToAddressInDisassembly(address);
return;
}
@ -192,22 +190,37 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment);
if (x <= comment_width)
{
if (index >= debuggerState.breakpoints.size())
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
if (index >= breakpoints.size())
{
debugger_unlockBreakpoints();
return;
}
DebuggerBreakpoint* bp = breakpoints[index];
auto bpId = bp->id;
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
auto it = debuggerState.breakpoints.begin();
std::advance(it, index);
const wxString dialogTitle = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Enter a new logging message") : _("Enter a new comment");
const wxString dialogMessage = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, (*it)->comment);
bool isLoggingBP = bp->bpType == DEBUGGER_BP_T_LOGGING;
const wxString dialogTitle = isLoggingBP ? _("Enter a new logging message") : _("Enter a new comment");
const wxString dialogMessage = isLoggingBP ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
const wxString existingComment = bp->comment;
debugger_unlockBreakpoints();
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, existingComment);
if (set_comment_dialog.ShowModal() == wxID_OK)
{
(*it)->comment = set_comment_dialog.GetValue().ToStdWstring();
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
if (isLoggingBP)
{
debugger_lockBreakpoints();
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (bp)
{
bp->comment = set_comment_dialog.GetValue().ToStdWstring();
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
}
debugger_unlockBreakpoints();
}
}
}
}
@ -242,20 +255,27 @@ void BreakpointWindow::OnRightDown(wxMouseEvent& event)
void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt)
{
auto& breakpoints = debugger_lockBreakpoints();
long selectedIndex = m_breakpoints->GetFirstSelected();
if (selectedIndex == wxNOT_FOUND || selectedIndex < 0 || selectedIndex >= (long)breakpoints.size())
{
debugger_unlockBreakpoints();
return;
}
BreakpointId bpId = (uint64)m_breakpoints->GetItemData(selectedIndex);
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (!bp)
{
debugger_unlockBreakpoints();
return;
}
if (evt.GetId() == MENU_ID_DELETE_BP)
{
long sel = m_breakpoints->GetFirstSelected();
if (sel == wxNOT_FOUND || sel < 0 || sel >= m_breakpoints->GetItemCount())
return;
auto it = debuggerState.breakpoints.begin();
std::advance(it, sel);
debugger_deleteBreakpoint(*it);
debugger_deleteBreakpoint(bpId);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
}
debugger_unlockBreakpoints();
}
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)

View File

@ -54,7 +54,7 @@ wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent);
wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
@ -65,7 +65,7 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
EVT_CLOSE(DebuggerWindow2::OnClose)
EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView)
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange)
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_IP, DebuggerWindow2::OnMoveIP)
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_TO_DISASM_ADDR, DebuggerWindow2::OnMoveToDisasmAddr)
EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked)
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit)
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
@ -79,8 +79,25 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu)
wxEND_EVENT_TABLE()
DebuggerModuleInfo::DebuggerModuleInfo(RPLModule* module)
{
moduleName = module->moduleName2;
patchCRC = module->patchCRC;
textArea.base = module->regionMappingBase_text.GetMPTR();
textArea.size = module->regionSize_text;
dataArea.base = module->regionMappingBase_data;
dataArea.size = module->regionSize_data;
DebuggerWindow2* g_debugger_window;
}
struct DebuggerModuleInfoNotify : public wxClientData
{
DebuggerModuleInfo moduleInfo;
DebuggerModuleInfoNotify(RPLModule* module) : moduleInfo(module) {};
};
DebuggerWindow2* s_debuggerWindow;
void DebuggerConfig::Load(XMLConfigParser& parser)
{
@ -130,7 +147,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
const auto comment = element.get("Comment", "");
// calculate absolute address
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data;
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->moduleInfo.textArea.base : this->moduleInfo.dataArea.base;
uint32 address = module_base_address + relative_address;
// don't change anything if there's already a breakpoint
@ -179,49 +196,52 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
void DebuggerModuleStorage::Save(XMLConfigParser& parser)
{
auto breakpoints_parser = parser.set("Breakpoints");
for (const auto& bp : debuggerState.breakpoints)
auto& breakpoints = debugger_lockBreakpoints();
for (const auto& bp : breakpoints)
{
// check breakpoint type
if (bp->dbType != DEBUGGER_BP_T_DEBUGGER)
continue;
// check whether the breakpoint is part of the current module being saved
RPLModule* address_module;
if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address);
else continue;
if (!address_module || !(address_module->moduleName2 == this->module_name && address_module->patchCRC == this->crc_hash))
uint32 relativeAddress;
if (moduleInfo.textArea.ContainsAddress(bp->address))
relativeAddress = bp->address - moduleInfo.textArea.base;
else if (moduleInfo.dataArea.ContainsAddress(bp->address))
relativeAddress = bp->address - moduleInfo.dataArea.base;
else
continue;
uint32_t relative_address = bp->address - (bp->isMemBP() ? address_module->regionMappingBase_data : address_module->regionMappingBase_text.GetMPTR());
auto entry = breakpoints_parser.set("Entry");
entry.set("Address", fmt::format("{:#10x}", relative_address));
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
entry.set("Comment", boost::nowide::narrow(bp->comment).c_str());
entry.set("Type", bp->bpType);
entry.set("Enabled", bp->enabled);
if (this->delete_breakpoints_after_saving) debugger_deleteBreakpoint(bp);
if (this->delete_breakpoints_after_saving)
debugger_deleteBreakpoint(bp->id);
this->delete_breakpoints_after_saving = false;
}
debugger_unlockBreakpoints();
auto comments_parser = parser.set("Comments");
for (const auto& comment_entry : rplDebugSymbol_getSymbols())
{
// check comment type
const auto comment_address = comment_entry.first;
const auto commentAddress = comment_entry.first;
const auto comment = static_cast<rplDebugSymbolComment*>(comment_entry.second);
if (!comment || comment->type != RplDebugSymbolComment)
continue;
// check whether it's part of the current module being saved
RPLModule* address_module = RPLLoader_FindModuleByCodeAddr(comment_entry.first);
if (!address_module || !(address_module->moduleName2 == module_name && address_module->patchCRC == this->crc_hash))
// check whether the comment is part of the current module being saved
uint32 relativeAddress;
if (moduleInfo.textArea.ContainsAddress(commentAddress))
relativeAddress = commentAddress - moduleInfo.textArea.base;
else
continue;
uint32_t relative_address = comment_address - address_module->regionMappingBase_text.GetMPTR();
auto entry = comments_parser.set("Entry");
entry.set("Address", fmt::format("{:#10x}", relative_address));
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
entry.set("Comment", boost::nowide::narrow(comment->comment).c_str());
}
}
@ -256,28 +276,29 @@ void DebuggerWindow2::CreateToolBar()
}
void DebuggerWindow2::SaveModuleStorage(const RPLModule* module, bool delete_breakpoints)
void DebuggerWindow2::LoadModuleStorage(const DebuggerModuleInfo& moduleInfo)
{
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
for (auto& module_storage : m_modules_storage)
{
if (module_storage->data().module_name == module->moduleName2 && module_storage->data().crc_hash == module->patchCRC)
{
module_storage->data().delete_breakpoints_after_saving = delete_breakpoints;
module_storage->Save(path);
if (delete_breakpoints) m_modules_storage.erase(std::find(m_modules_storage.begin(), m_modules_storage.end(), module_storage));
}
}
}
void DebuggerWindow2::LoadModuleStorage(const RPLModule* module)
{
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
bool already_loaded = std::any_of(m_modulesStorage.begin(), m_modulesStorage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
if (!path.empty() && !already_loaded)
{
m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load();
m_modulesStorage.emplace_back(new XMLDebuggerModuleConfig(path, { moduleInfo, false }))->Load();
}
}
void DebuggerWindow2::SaveModuleStorage(const DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage)
{
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
for (auto& moduleStorage : m_modulesStorage)
{
if (moduleStorage->data().moduleInfo.moduleName == moduleInfo.moduleName && moduleStorage->data().moduleInfo.patchCRC == moduleInfo.patchCRC)
{
moduleStorage->data().delete_breakpoints_after_saving = deleteModuleStorage; // make sure breakpoints are deleted when the storage is removed
moduleStorage->Save(path);
if (deleteModuleStorage)
m_modulesStorage.erase(std::find(m_modulesStorage.begin(), m_modulesStorage.end(), moduleStorage));
break;
}
}
}
@ -291,8 +312,8 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
m_config.SetFilename(file.generic_wstring());
m_config.Load();
debuggerState.breakOnEntry = m_config.data().break_on_start;
debuggerState.logOnlyMemoryBreakpoints = m_config.data().log_memory_breakpoints;
debugger_setOptionBreakOnEntry(m_config.data().break_on_start);
debugger_setOptionLogOnlyMemoryBreakpoints(m_config.data().log_memory_breakpoints);
m_main_position = parent.GetPosition();
m_main_size = parent.GetSize();
@ -305,21 +326,22 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// load configs for already loaded modules
// load configs and module storage for already loaded modules
const auto module_count = RPLLoader_GetModuleCount();
const auto module_list = RPLLoader_GetModuleList();
for (sint32 i = 0; i < module_count; i++)
{
const auto module = module_list[i];
LoadModuleStorage(module);
DebuggerModuleInfo moduleInfo(module);
LoadModuleStorage(moduleInfo);
}
wxString label_text = _("> no modules loaded");
if (module_count != 0)
{
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
if (current_rpl_module)
label_text = wxString::Format("> %s", current_rpl_module->moduleName2.c_str());
RPLModule* currentModule = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
if (currentModule)
label_text = wxString::Format("> %s", currentModule->moduleName2.c_str());
else
label_text = _("> unknown module");
}
@ -345,26 +367,16 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
OnParentMove(m_main_position, m_main_size);
m_config.data().pin_to_main = value;
g_debugger_window = this;
s_debuggerWindow = this;
}
DebuggerWindow2::~DebuggerWindow2()
{
g_debuggerDispatcher.ClearDebuggerCallbacks();
debuggerState.breakOnEntry = false;
g_debugger_window = nullptr;
s_debuggerWindow = nullptr;
// save configs for all modules that are still loaded
// doesn't delete breakpoints since that should (in the future) be done by unloading the rpl modules when exiting the current game
const auto module_count = RPLLoader_GetModuleCount();
const auto module_list = RPLLoader_GetModuleList();
for (sint32 i = 0; i < module_count; i++)
{
const auto module = module_list[i];
if (module)
SaveModuleStorage(module, false);
}
CleanupForDestroy();
if (m_register_window && m_register_window->IsShown())
m_register_window->Close(true);
@ -380,13 +392,23 @@ DebuggerWindow2::~DebuggerWindow2()
if (m_symbol_window && m_symbol_window->IsShown())
m_symbol_window->Close(true);
}
// note: Can be called twice in some circumstances where early cleanup is needed. Will also always be called from destructor
void DebuggerWindow2::CleanupForDestroy()
{
debugger_setOptionBreakOnEntry(false);
// save module storage
while (!m_modulesStorage.empty())
{
SaveModuleStorage(m_modulesStorage[0]->data().moduleInfo, true); // deleting the module storage will also delete breakpoints belonging to the module's data
}
m_config.Save();
}
void DebuggerWindow2::OnClose(wxCloseEvent& event)
{
debuggerState.breakOnEntry = false;
debugger_setOptionBreakOnEntry(false);
const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE);
wxPostEvent(m_parent, parentEvent);
@ -394,11 +416,13 @@ void DebuggerWindow2::OnClose(wxCloseEvent& event)
event.Skip();
}
void DebuggerWindow2::OnMoveIP(wxCommandEvent& event)
void DebuggerWindow2::OnMoveToDisasmAddr(wxCommandEvent& event)
{
const auto ip = debuggerState.debugSession.instructionPointer;
UpdateModuleLabel(ip);
m_disasm_ctrl->CenterOffset(ip);
MPTR address = (uint32)(uint64)event.GetClientData();
if (!address)
return;
UpdateModuleLabel(address);
m_disasm_ctrl->CenterOffset(address);
}
void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event)
@ -431,8 +455,8 @@ void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& m
void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
{
RPLModule* module = (RPLModule*)event.GetClientData();
LoadModuleStorage(module);
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
LoadModuleStorage(moduleNotif->moduleInfo);
m_module_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
m_disasm_ctrl->Init();
@ -440,8 +464,8 @@ void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
{
RPLModule* module = (RPLModule*)event.GetClientData(); // todo - the RPL module is already unloaded at this point. Find a better way to handle this
SaveModuleStorage(module, true);
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
SaveModuleStorage(moduleNotif->moduleInfo, true);
m_module_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
m_disasm_ctrl->Init();
@ -507,7 +531,12 @@ std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint
void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event)
{
const auto ip = debuggerState.debugSession.instructionPointer;
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
if (!hCPU)
return;
MPTR ip = hCPU->instructionPointer;
debugger_unlockDebugSession(hCPU);
UpdateModuleLabel(ip);
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)"));
@ -537,17 +566,17 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
break;
case TOOL_ID_PAUSE:
if (debugger_isTrapped())
debugger_resume();
debugger_stepCommand(DebuggerStepCommand::Run);
else
debugger_forceBreak();
debugger_requestBreak();
break;
case TOOL_ID_STEP_INTO:
if (debugger_isTrapped())
debuggerState.debugSession.stepInto = true;
debugger_stepCommand(DebuggerStepCommand::StepInto);
break;
case TOOL_ID_STEP_OVER:
if (debugger_isTrapped())
debuggerState.debugSession.stepOver = true;
debugger_stepCommand(DebuggerStepCommand::StepOver);
break;
}
}
@ -576,14 +605,14 @@ void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event)
{
const bool value = !m_config.data().break_on_start;
m_config.data().break_on_start = value;
debuggerState.breakOnEntry = value;
debugger_setOptionBreakOnEntry(value);
break;
}
case MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS:
{
const bool value = !m_config.data().log_memory_breakpoints;
m_config.data().log_memory_breakpoints = value;
debuggerState.logOnlyMemoryBreakpoints = value;
debugger_setOptionLogOnlyMemoryBreakpoints(value);
break;
}
case MENU_ID_OPTIONS_SWITCH_CPU_MODE:
@ -746,16 +775,10 @@ void DebuggerWindow2::NotifyRun()
wxQueueEvent(this, evt);
}
void DebuggerWindow2::MoveIP()
void DebuggerWindow2::MoveToAddressInDisassembly(MPTR address)
{
auto* evt = new wxCommandEvent(wxEVT_MOVE_IP);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyModuleLoaded(void* module)
{
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
evt->SetClientData(module);
auto* evt = new wxCommandEvent(wxEVT_MOVE_TO_DISASM_ADDR);
evt->SetClientData((void*)(uintptr_t)address);
wxQueueEvent(this, evt);
}
@ -765,9 +788,18 @@ void DebuggerWindow2::NotifyGraphicPacksModified()
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyModuleUnloaded(void* module)
void DebuggerWindow2::NotifyModuleLoaded(RPLModule* module)
{
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
evt->SetClientData(module);
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
evt->SetClientObject(notifData);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyModuleUnloaded(RPLModule* module)
{
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
evt->SetClientObject(notifData);
wxQueueEvent(this, evt);
}

View File

@ -21,12 +21,12 @@ wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent);
extern class DebuggerWindow2* g_debugger_window;
extern class DebuggerWindow2* s_debuggerWindow;
struct DebuggerConfig
{
@ -49,11 +49,30 @@ struct DebuggerConfig
};
typedef XMLDataConfig<DebuggerConfig> XMLDebuggerConfig;
struct DebuggerModuleInfo
{
struct ModuleArea
{
MPTR base;
uint32 size;
bool ContainsAddress(MPTR addr)
{
return addr >= base && (addr < (base+size));
}
};
std::string moduleName;
uint32 patchCRC;
ModuleArea textArea;
ModuleArea dataArea;
DebuggerModuleInfo(RPLModule* module);
};
struct DebuggerModuleStorage
{
std::string module_name;
uint32_t crc_hash;
const RPLModule* rpl_module;
DebuggerModuleInfo moduleInfo;
bool delete_breakpoints_after_saving;
void Load(XMLConfigParser& parser);
@ -73,10 +92,11 @@ class DebuggerWindow2 : public wxFrame, public DebuggerCallbacks
{
public:
void CreateToolBar();
void LoadModuleStorage(const RPLModule* module);
void SaveModuleStorage(const RPLModule* module, bool delete_breakpoints);
void LoadModuleStorage(const struct DebuggerModuleInfo& moduleInfo);
void SaveModuleStorage(const struct DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage);
DebuggerWindow2(wxFrame& parent, const wxRect& display_size);
~DebuggerWindow2();
void CleanupForDestroy();
void OnParentMove(const wxPoint& position, const wxSize& size);
void OnGameLoaded();
@ -97,7 +117,7 @@ private:
void OnExit(wxCommandEvent& event);
void OnShow(wxShowEvent& event);
void OnClose(wxCloseEvent& event);
void OnMoveIP(wxCommandEvent& event);
void OnMoveToDisasmAddr(wxCommandEvent& event);
void OnNotifyModuleLoaded(wxCommandEvent& event);
void OnNotifyModuleUnloaded(wxCommandEvent& event);
void OnNotifyGraphicPacksModified(wxCommandEvent& event);
@ -110,13 +130,13 @@ private:
void UpdateViewThreadsafe() override;
void NotifyDebugBreakpointHit() override;
void NotifyRun() override;
void MoveIP() override;
void NotifyModuleLoaded(void* module) override;
void MoveToAddressInDisassembly(MPTR address) override;
void NotifyGraphicPacksModified() override;
void NotifyModuleUnloaded(void* module) override;
void NotifyModuleLoaded(struct RPLModule* module) override;
void NotifyModuleUnloaded(struct RPLModule* module) override;
XMLDebuggerConfig m_config;
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modules_storage;
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modulesStorage;
wxPoint m_main_position;
wxSize m_main_size;

View File

@ -189,13 +189,19 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR
ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr);
const bool is_active_bp = debuggerState.debugSession.isTrapped && debuggerState.debugSession.instructionPointer == virtualAddress;
bool hasActiveBP = false;
PPCInterpreter_t* sessionCpu = debugger_lockDebugSession();
if (sessionCpu)
{
hasActiveBP = sessionCpu->instructionPointer == virtualAddress;
debugger_unlockDebugSession(sessionCpu);
}
// write virtual address
wxColour background_colour;
if (is_active_bp && bp != nullptr)
if (hasActiveBP && bp != nullptr)
background_colour = theme_lineBreakpointAndCurrentInstruction;
else if (is_active_bp)
else if (hasActiveBP)
background_colour = theme_lineCurrentInstruction;
else if (bp != nullptr)
background_colour = bp->bpType == DEBUGGER_BP_T_NORMAL ? theme_lineBreakpointSet : theme_lineLoggingBreakpointSet;
@ -219,7 +225,7 @@ void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR
position.x += OFFSET_ADDRESS_RELATIVE;
// draw arrow to clearly indicate instruction pointer
if(is_active_bp)
if(hasActiveBP)
dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false);
// handle data symbols
@ -604,36 +610,35 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
}
}
// debugger currently in break state
if (debuggerState.debugSession.isTrapped)
PPCInterpreter_t* debugCpu = debugger_lockDebugSession();
if (debugCpu)
{
switch (key_code)
{
case WXK_F5:
case WXK_F5:
{
debuggerState.debugSession.run = true;
return;
debugger_stepCommand(DebuggerStepCommand::Run);
break;
}
case WXK_F10:
case WXK_F10:
{
debuggerState.debugSession.stepOver = true;
return;
debugger_stepCommand(DebuggerStepCommand::StepOver);
break;
}
case WXK_F11:
case WXK_F11:
{
debuggerState.debugSession.stepInto = true;
debugger_stepCommand(DebuggerStepCommand::StepInto);
break;
}
}
debugger_unlockDebugSession(debugCpu);
}
else
{
switch (key_code)
if (key_code == WXK_F5)
{
case WXK_F5:
{
debuggerState.debugSession.shouldBreak = true;
}
debugger_requestBreak();
}
}
}

View File

@ -252,8 +252,6 @@ void DumpCtrl::CenterOffset(uint32 offset)
RefreshControl();
//RefreshLine(line);
debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer);
}
uint32 DumpCtrl::LineToOffset(uint32 line)

View File

@ -129,6 +129,5 @@ void ModuleWindow::OnLeftDClick(wxMouseEvent& event)
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
if (address == 0)
return;
debuggerState.debugSession.instructionPointer = address;
g_debuggerDispatcher.MoveIP();
debugger_jumpToAddressInDisasm(address);
}

View File

@ -231,10 +231,15 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value,
void RegisterWindow::OnUpdateView()
{
// m_register_ctrl->RefreshControl();
PPCSnapshot snapshot = {};
if (PPCInterpreter_t* hCPU = debugger_lockDebugSession(); hCPU)
{
snapshot = debugger_getSnapshotFromSession(hCPU);
debugger_unlockDebugSession(hCPU);
}
for (int i = 0; i < 32; ++i)
{
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.gpr[i];
const uint32 registerValue = snapshot.gpr[i];
const bool hasChanged = registerValue != m_prev_snapshot.gpr[i];
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueR0 + i));
wxASSERT(value);
@ -246,7 +251,7 @@ void RegisterWindow::OnUpdateView()
// update LR
{
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.spr_lr;
const uint32 registerValue = snapshot.spr_lr;
const bool hasChanged = registerValue != m_prev_snapshot.spr_lr;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueLR));
wxASSERT(value);
@ -257,7 +262,7 @@ void RegisterWindow::OnUpdateView()
for (int i = 0; i < 32; ++i)
{
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int;
const uint64_t register_value = snapshot.fpr[i].fp0int;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
wxASSERT(value);
@ -271,14 +276,14 @@ void RegisterWindow::OnUpdateView()
continue;
if(m_show_double_values)
value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp0));
else
value->ChangeValue(wxString::Format("%016llx", register_value));
}
for (int i = 0; i < 32; ++i)
{
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int;
const uint64_t register_value = snapshot.fpr[i].fp1int;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
wxASSERT(value);
@ -292,7 +297,7 @@ void RegisterWindow::OnUpdateView()
continue;
if (m_show_double_values)
value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp1));
else
value->ChangeValue(wxString::Format("%016llx", register_value));
}
@ -303,7 +308,7 @@ void RegisterWindow::OnUpdateView()
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueCR0 + i));
wxASSERT(value);
auto cr_bits_ptr = debuggerState.debugSession.ppcSnapshot.cr + i * 4;
auto cr_bits_ptr = snapshot.cr + i * 4;
auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4;
const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp);
@ -330,44 +335,53 @@ void RegisterWindow::OnUpdateView()
value->ChangeValue(fmt::format("{}", fmt::join(joinArray, ", ")));
}
memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot));
m_prev_snapshot = snapshot;
}
void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
{
if (!debuggerState.debugSession.isTrapped)
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
if (!debugSession)
{
event.Skip();
return;
}
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
debugger_unlockDebugSession(debugSession);
debugSession = nullptr;
const auto id = event.GetId();
if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32)
{
const uint32 register_index = id - kRegisterValueR0;
const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index];
const uint32 register_value = ppcSnapshot.gpr[register_index];
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("%08x", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
debuggerState.debugSession.hCPU->gpr[register_index] = new_value;
debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value;
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->gpr[register_index] = new_value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
}
if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32)
{
const uint32 register_index = id - kRegisterValueFPR0_0;
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0;
const double register_value = ppcSnapshot.fpr[register_index].fp0;
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP0_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value;
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value;
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp0 = new_value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
@ -377,16 +391,18 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32)
{
const uint32 register_index = id - kRegisterValueFPR1_0;
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1;
const double register_value = ppcSnapshot.fpr[register_index].fp1;
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP1_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value;
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value;
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp1 = new_value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
}
@ -396,7 +412,13 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
{
m_show_double_values = !m_show_double_values;
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
if (!debugSession)
return;
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
debugger_unlockDebugSession(debugSession);
for (int i = 0; i < 32; ++i)
{
const auto value0 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
@ -406,13 +428,13 @@ void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
if (m_show_double_values)
{
value0->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
value1->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
value0->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp0));
value1->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp1));
}
else
{
value0->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int));
value1->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int));
value0->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp0int));
value1->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp1int));
}
}
}

View File

@ -112,8 +112,7 @@ void SymbolListCtrl::OnLeftDClick(wxListEvent& event)
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
if (address == 0)
return;
debuggerState.debugSession.instructionPointer = address;
g_debuggerDispatcher.MoveIP();
debugger_jumpToAddressInDisasm(address);
}
void SymbolListCtrl::OnRightClick(wxListEvent& event)