#include "gui/guiWrapper.h" #include "Debugger.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #include "gui/debugger/DebuggerWindow2.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #if BOOST_OS_WINDOWS #include #endif debuggerState_t debuggerState{ }; DebuggerBreakpoint* debugger_getFirstBP(uint32 address) { for (auto& it : debuggerState.breakpoints) { if (it->address == address) return it; } return nullptr; } DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType) { for (auto& it : debuggerState.breakpoints) { if (it->address == address) { DebuggerBreakpoint* bpItr = it; while (bpItr) { if (bpItr->bpType == bpType) return bpItr; bpItr = bpItr->next; } return nullptr; } } return nullptr; } bool debuggerBPChain_hasType(DebuggerBreakpoint* bp, uint8 bpType) { while (bp) { if (bp->bpType == bpType) return true; bp = bp->next; } return false; } void debuggerBPChain_add(uint32 address, DebuggerBreakpoint* bp) { bp->next = nullptr; DebuggerBreakpoint* existingBP = debugger_getFirstBP(address); if (existingBP) { while (existingBP->next) existingBP = existingBP->next; existingBP->next = bp; return; } // no breakpoint chain exists for this address debuggerState.breakpoints.push_back(bp); } uint32 debugger_getAddressOriginalOpcode(uint32 address) { auto bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr->isExecuteBP()) return bpItr->originalOpcodeValue; bpItr = bpItr->next; } return memory_readU32(address); } void debugger_updateMemoryU32(uint32 address, uint32 newValue) { bool memChanged = false; if (newValue != memory_readU32(address)) memChanged = true; memory_writeU32(address, newValue); if(memChanged) PPCRecompiler_invalidateRange(address, address + 4); } void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore) { auto bpItr = debugger_getFirstBP(address); bool hasBP = false; uint32 originalOpcodeValue; while (bpItr) { if (bpItr->isExecuteBP()) { if (bpItr->enabled && forceRestore == false) { // write TW instruction to memory debugger_updateMemoryU32(address, DEBUGGER_BP_T_DEBUGGER_TW); return; } else { originalOpcodeValue = bpItr->originalOpcodeValue; hasBP = true; } } bpItr = bpItr->next; } if (hasBP) { // restore instruction debugger_updateMemoryU32(address, originalOpcodeValue); } } void debugger_createCodeBreakpoint(uint32 address, uint8 bpType) { // check if breakpoint already exists auto existingBP = debugger_getFirstBP(address); if (existingBP && debuggerBPChain_hasType(existingBP, bpType)) return; // breakpoint already exists // get original opcode at address uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); // init breakpoint object DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true); debuggerBPChain_add(address, bp); debugger_updateExecutionBreakpoint(address); } void debugger_createExecuteBreakpoint(uint32 address) { debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } namespace coreinit { std::vector& OSGetSchedulerThreads(); } void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp) { std::vector schedulerThreadHandles = coreinit::OSGetSchedulerThreads(); #if BOOST_OS_WINDOWS debuggerState.activeMemoryBreakpoint = bp; for (auto& hThreadNH : schedulerThreadHandles) { HANDLE hThread = (HANDLE)hThreadNH; CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SuspendThread(hThread); GetThreadContext(hThread, &ctx); if (debuggerState.activeMemoryBreakpoint) { ctx.Dr0 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); ctx.Dr1 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); // breakpoint 0 SetBits(ctx.Dr7, 0, 1, 1); // breakpoint #0 enabled: true SetBits(ctx.Dr7, 16, 2, 1); // breakpoint #0 condition: 1 (write) SetBits(ctx.Dr7, 18, 2, 3); // breakpoint #0 length: 3 (4 bytes) // breakpoint 1 SetBits(ctx.Dr7, 2, 1, 1); // breakpoint #1 enabled: true SetBits(ctx.Dr7, 20, 2, 3); // breakpoint #1 condition: 3 (read & write) SetBits(ctx.Dr7, 22, 2, 3); // breakpoint #1 length: 3 (4 bytes) } else { // breakpoint 0 SetBits(ctx.Dr7, 0, 1, 0); // breakpoint #0 enabled: false SetBits(ctx.Dr7, 16, 2, 0); // breakpoint #0 condition: 1 (write) SetBits(ctx.Dr7, 18, 2, 0); // breakpoint #0 length: 3 (4 bytes) // breakpoint 1 SetBits(ctx.Dr7, 2, 1, 0); // breakpoint #1 enabled: false SetBits(ctx.Dr7, 20, 2, 0); // breakpoint #1 condition: 3 (read & write) SetBits(ctx.Dr7, 22, 2, 0); // breakpoint #1 length: 3 (4 bytes) } SetThreadContext(hThread, &ctx); ResumeThread(hThread); } #else cemuLog_log(LogType::Force, "Debugger breakpoints are not supported"); #endif } void debugger_handleSingleStepException(uint64 dr6) { bool triggeredDR0 = GetBits(dr6, 0, 1); // write bool triggeredDR1 = GetBits(dr6, 1, 1); // read and write bool catchBP = false; if (triggeredDR0 && triggeredDR1) { // write (and read) access if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE) catchBP = true; } else { // read access if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ) catchBP = true; } if (catchBP) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); } } void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite) { // init breakpoint object uint8 bpType; if (onRead && onWrite) assert_dbg(); else if (onRead) bpType = DEBUGGER_BP_T_MEMORY_READ; else bpType = DEBUGGER_BP_T_MEMORY_WRITE; DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, 0xFFFFFFFF, bpType, true); debuggerBPChain_add(address, bp); // disable any already existing memory breakpoint if (debuggerState.activeMemoryBreakpoint) { debuggerState.activeMemoryBreakpoint->enabled = false; debuggerState.activeMemoryBreakpoint = nullptr; } // activate new breakpoint debugger_updateMemoryBreakpoint(bp); } void debugger_handleEntryBreakpoint(uint32 address) { if (!debuggerState.breakOnEntry) return; debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } void debugger_deleteBreakpoint(DebuggerBreakpoint* bp) { for (auto& it : debuggerState.breakpoints) { if (it->address == bp->address) { // for execution breakpoints make sure the instruction is restored if (bp->isExecuteBP()) { bp->enabled = false; debugger_updateExecutionBreakpoint(bp->address); } // remove if (it == bp) { // remove first in list debuggerState.breakpoints.erase(std::remove(debuggerState.breakpoints.begin(), debuggerState.breakpoints.end(), bp), debuggerState.breakpoints.end()); DebuggerBreakpoint* nextBP = bp->next; if (nextBP) 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); bpItr->next = bp->next; } delete bp; return; } } } void debugger_toggleExecuteBreakpoint(uint32 address) { auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_NORMAL); if (existingBP) { // delete existing breakpoint debugger_deleteBreakpoint(existingBP); } else { // create new breakpoint debugger_createExecuteBreakpoint(address); } } void debugger_forceBreak() { debuggerState.debugSession.shouldBreak = true; } bool debugger_isTrapped() { return debuggerState.debugSession.isTrapped; } 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; } void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr == bp) { if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING) { bp->enabled = state; debugger_updateExecutionBreakpoint(address); debuggerWindow_updateViewThreadsafe2(); } else if (bpItr->isMemBP()) { // disable other memory breakpoints for (auto& it : debuggerState.breakpoints) { DebuggerBreakpoint* bpItr2 = it; while (bpItr2) { if (bpItr2->isMemBP() && bpItr2 != bp) { bpItr2->enabled = false; } bpItr2 = bpItr2->next; } } bpItr->enabled = state; if (state) debugger_updateMemoryBreakpoint(bpItr); else debugger_updateMemoryBreakpoint(nullptr); debuggerWindow_updateViewThreadsafe2(); } return; } bpItr = bpItr->next; } } void debugger_createPatch(uint32 address, std::span patchData) { DebuggerPatch* patch = new DebuggerPatch(); patch->address = address; patch->length = patchData.size(); patch->data.resize(4); patch->origData.resize(4); memcpy(&patch->data.front(), patchData.data(), patchData.size()); memcpy(&patch->origData.front(), memory_getPointerFromVirtualOffset(address), patchData.size()); // get original data from breakpoints if ((address & 3) != 0) cemu_assert_debug(false); for (sint32 i = 0; i < patchData.size() / 4; i++) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr->isExecuteBP()) { *(uint32*)(&patch->origData.front() + i * 4) = _swapEndianU32(bpItr->originalOpcodeValue); } bpItr = bpItr->next; } } // merge with existing patches if the ranges touch for(sint32 i=0; i= patchItr->address && address <= patchItr->address + patchItr->length) { uint32 newAddress = std::min(patch->address, patchItr->address); uint32 newEndAddress = std::max(patch->address + patch->length, patchItr->address + patchItr->length); uint32 newLength = newEndAddress - newAddress; DebuggerPatch* newPatch = new DebuggerPatch(); newPatch->address = newAddress; newPatch->length = newLength; newPatch->data.resize(newLength); newPatch->origData.resize(newLength); memcpy(&newPatch->data.front() + (address - newAddress), &patch->data.front(), patch->length); memcpy(&newPatch->data.front() + (patchItr->address - newAddress), &patchItr->data.front(), patchItr->length); memcpy(&newPatch->origData.front() + (address - newAddress), &patch->origData.front(), patch->length); memcpy(&newPatch->origData.front() + (patchItr->address - newAddress), &patchItr->origData.front(), patchItr->length); delete patch; patch = newPatch; delete patchItr; // remove currently iterated patch debuggerState.patches.erase(debuggerState.patches.begin()+i); i--; } } 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); if ((patchData.size() & 3) != 0) cemu_assert_debug(false); for (sint32 i = 0; i < patchData.size() / 4; i++) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); bool hasActiveExecuteBP = false; while (bpItr) { if (bpItr->isExecuteBP()) { bpItr->originalOpcodeValue = *(uint32be*)(patchData.data() + i * 4); if (bpItr->enabled) hasActiveExecuteBP = true; } bpItr = bpItr->next; } if (hasActiveExecuteBP == false) { memcpy(memory_getPointerFromVirtualOffset(address + i * 4), patchData.data() + i * 4, 4); PPCRecompiler_invalidateRange(address, address + 4); } } } bool debugger_hasPatch(uint32 address) { for (auto& patch : debuggerState.patches) { if (address + 4 > patch->address && address < patch->address + patch->length) return true; } return false; } void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true) { 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) debuggerWindow_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) { // nothing to skip, use step-into debugger_stepInto(hCPU); debugger_updateExecutionBreakpoint(initialIP); debuggerWindow_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); debuggerWindow_moveIP(); // restore breakpoints debugger_updateExecutionBreakpoint(initialIP); // run ppcRecompilerEnabled = isRecEnabled; return true; } void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) { 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 DebugLogStackTrace(OSThread_t* thread, MPTR sp); void debugger_enterTW(PPCInterpreter_t* hCPU) { // 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 logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address); std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR(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; } 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) { 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(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); debuggerWindow_notifyDebugBreakpointHit2(); debuggerWindow_updateViewThreadsafe2(); // reset step control debuggerState.debugSession.stepInto = false; debuggerState.debugSession.stepOver = false; debuggerState.debugSession.run = false; while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); // check for step commands if (debuggerState.debugSession.stepOver) { if (debugger_stepOver(hCPU)) { debugger_createPPCStateSnapshot(hCPU); break; // if true is returned, continue with execution } debugger_createPPCStateSnapshot(hCPU); debuggerWindow_updateViewThreadsafe2(); debuggerState.debugSession.stepOver = false; } if (debuggerState.debugSession.stepInto) { debugger_stepInto(hCPU); debugger_createPPCStateSnapshot(hCPU); debuggerWindow_updateViewThreadsafe2(); 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; } } debuggerState.debugSession.isTrapped = false; debuggerState.debugSession.hCPU = nullptr; debuggerWindow_updateViewThreadsafe2(); debuggerWindow_notifyRun(); } 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)) { debuggerState.debugSession.shouldBreak = false; const uint32 address = (uint32)hCPU->instructionPointer; assert_dbg(); //debugger_createBreakpoint(address, DEBUGGER_BP_TYPE_ONE_SHOT); } } void debugger_addParserSymbols(class ExpressionParser& ep) { const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); std::vector module_tmp(module_count); for (int i = 0; i < module_count; i++) { const auto module = module_list[i]; if (module) { module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); ep.AddConstant(module->moduleName2, module_tmp[i]); } } for (sint32 i = 0; i < 32; i++) ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); }