Skip to content

Commit

Permalink
debugger: Add logging breakpoint + misc fixes (#927)
Browse files Browse the repository at this point in the history
  • Loading branch information
Crementif authored Aug 3, 2023
1 parent 1d1e1e7 commit 651e533
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 122 deletions.
83 changes: 61 additions & 22 deletions src/Cafe/HW/Espresso/Debugger/Debugger.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#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"
Expand Down Expand Up @@ -74,7 +75,7 @@ uint32 debugger_getAddressOriginalOpcode(uint32 address)
auto bpItr = debugger_getFirstBP(address);
while (bpItr)
{
if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_ONE_SHOT)
if (bpItr->isExecuteBP())
return bpItr->originalOpcodeValue;
bpItr = bpItr->next;
}
Expand Down Expand Up @@ -121,32 +122,23 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore)
}
}

void debugger_createExecuteBreakpoint(uint32 address)
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
{
// check if breakpoint already exists
auto existingBP = debugger_getFirstBP(address);
if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_NORMAL))
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, DEBUGGER_BP_T_NORMAL, true);
DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true);
debuggerBPChain_add(address, bp);
debugger_updateExecutionBreakpoint(address);
}

void debugger_createSingleShotExecuteBreakpoint(uint32 address)
void debugger_createExecuteBreakpoint(uint32 address)
{
// check if breakpoint already exists
auto existingBP = debugger_getFirstBP(address);
if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_ONE_SHOT))
return; // breakpoint already exists
// get original opcode at address
uint32 originalOpcode = debugger_getAddressOriginalOpcode(address);
// init breakpoint object
DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_ONE_SHOT, true);
debuggerBPChain_add(address, bp);
debugger_updateExecutionBreakpoint(address);
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}

namespace coreinit
Expand Down Expand Up @@ -218,7 +210,7 @@ void debugger_handleSingleStepException(uint64 dr6)
}
if (catchBP)
{
debugger_createSingleShotExecuteBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4);
debugger_createCodeBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT);
}
}

Expand Down Expand Up @@ -250,7 +242,7 @@ void debugger_handleEntryBreakpoint(uint32 address)
if (!debuggerState.breakOnEntry)
return;

debugger_createExecuteBreakpoint(address);
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}

void debugger_deleteBreakpoint(DebuggerBreakpoint* bp)
Expand Down Expand Up @@ -298,10 +290,12 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
{
// delete existing breakpoint
debugger_deleteBreakpoint(existingBP);
return;
}
// create new
debugger_createExecuteBreakpoint(address);
else
{
// create new breakpoint
debugger_createExecuteBreakpoint(address);
}
}

void debugger_forceBreak()
Expand All @@ -327,7 +321,7 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
{
if (bpItr == bp)
{
if (bpItr->bpType == DEBUGGER_BP_T_NORMAL)
if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING)
{
bp->enabled = state;
debugger_updateExecutionBreakpoint(address);
Expand Down Expand Up @@ -486,7 +480,7 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU)
return false;
}
// create one-shot breakpoint at next instruction
debugger_createSingleShotExecuteBreakpoint(initialIP +4);
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
// step over current instruction (to avoid breakpoint)
debugger_stepInto(hCPU);
debuggerWindow_moveIP();
Expand All @@ -506,8 +500,39 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
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::wstring logName = !bp->comment.empty() ? L"Breakpoint '"+bp->comment+L"'" : fmt::format(L"Breakpoint at 0x{:08X} (no comment)", bp->address);
std::wstring logContext = fmt::format(L"Thread: {:08x} LR: 0x{:08x}", coreinitThread_getCurrentThreadMPTRDepr(hCPU), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? L" Stack Trace:" : L"");
cemuLog_log(LogType::Force, L"[Debugger] {} was executed! {}", logName, logContext);
if (cemuLog_advancedPPCLoggingEnabled())
DebugLogStackTrace(coreinitThread_getCurrentThreadDepr(hCPU), 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 = coreinitThread_getCurrentThreadMPTRDepr(hCPU);
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
Expand Down Expand Up @@ -579,6 +604,20 @@ void debugger_shouldBreak(PPCInterpreter_t* hCPU)

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 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]);
}
6 changes: 4 additions & 2 deletions src/Cafe/HW/Espresso/Debugger/Debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping)
#define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint
#define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint
#define DEBUGGER_BP_T_LOGGING 4 // logging breakpoint, prints the breakpoint comment and stack trace whenever hit

#define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub
#define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger
Expand Down Expand Up @@ -42,7 +43,7 @@ struct DebuggerBreakpoint

bool isExecuteBP() const
{
return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_ONE_SHOT;
return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_LOGGING || bpType == DEBUGGER_BP_T_ONE_SHOT;
}

bool isMemBP() const
Expand Down Expand Up @@ -98,8 +99,9 @@ extern debuggerState_t debuggerState;

// new API
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createExecuteBreakpoint(uint32 address);
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);

void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
Expand Down
99 changes: 70 additions & 29 deletions src/gui/debugger/BreakpointWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

enum
{
MENU_ID_CREATE_MEM_BP_READ = 1,
MENU_ID_CREATE_CODE_BP_EXECUTION = 1,
MENU_ID_CREATE_CODE_BP_LOGGING,
MENU_ID_CREATE_MEM_BP_READ,
MENU_ID_CREATE_MEM_BP_WRITE,

MENU_ID_DELETE_BP,
};

enum ItemColumns
Expand Down Expand Up @@ -118,6 +120,8 @@ void BreakpointWindow::OnUpdateView()
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)
Expand Down Expand Up @@ -211,54 +215,91 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)

void BreakpointWindow::OnRightDown(wxMouseEvent& event)
{
wxMenu menu;
const auto position = event.GetPosition();
const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2;
if (index < 0 || index >= m_breakpoints->GetItemCount())
{
wxMenu menu;
menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint"));
menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint"));
menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));

menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this);
PopupMenu(&menu);
}
else
{
m_breakpoints->SetItemState(index, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
m_breakpoints->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);

menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));
wxMenu menu;
menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint"));

menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this);
PopupMenu(&menu);
menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClickSelected), nullptr, this);
PopupMenu(&menu);
}
}

void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt)
{
switch (evt.GetId())
if (evt.GetId() == MENU_ID_DELETE_BP)
{
case MENU_ID_CREATE_MEM_BP_READ:
MemoryBreakpointDialog(false);
return;
case MENU_ID_CREATE_MEM_BP_WRITE:
MemoryBreakpointDialog(true);
return;
long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (sel != -1)
{
if (sel >= debuggerState.breakpoints.size())
return;

auto it = debuggerState.breakpoints.begin();
std::advance(it, sel);

debugger_deleteBreakpoint(*it);

wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
}
}
}

void BreakpointWindow::MemoryBreakpointDialog(bool isWrite)
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
{
wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString);
wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
ExpressionParser parser;

auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);


uint32_t newBreakpointAddress = 0;
try
{
debugger_addParserSymbols(parser);
const auto result = (uint32)parser.Evaluate(value);
debug_printf("goto eval result: %x\n", result);

debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true);
this->OnUpdateView();
newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value);
}
catch (const std::exception& e)
catch (const std::exception& ex)
{
//ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString));
//return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR;
wxMessageBox(e.what(), "Invalid expression");
wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}


switch (evt.GetId())
{
case MENU_ID_CREATE_CODE_BP_EXECUTION:
debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL);
break;
case MENU_ID_CREATE_CODE_BP_LOGGING:
debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING);
break;
case MENU_ID_CREATE_MEM_BP_READ:
debugger_createMemoryBreakpoint(newBreakpointAddress, true, false);
break;
case MENU_ID_CREATE_MEM_BP_WRITE:
debugger_createMemoryBreakpoint(newBreakpointAddress, false, true);
break;
}

this->OnUpdateView();
}
}
}
3 changes: 1 addition & 2 deletions src/gui/debugger/BreakpointWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class BreakpointWindow : public wxFrame
void OnRightDown(wxMouseEvent& event);

void OnContextMenuClick(wxCommandEvent& evt);

void MemoryBreakpointDialog(bool isWrite);
void OnContextMenuClickSelected(wxCommandEvent& evt);

wxCheckedListCtrl* m_breakpoints;
};
11 changes: 7 additions & 4 deletions src/gui/debugger/DebuggerWindow2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)
const auto comment = element.get("Comment", "");

// calculate absolute address
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? 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->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data;
uint32 address = module_base_address + relative_address;

// don't change anything if there's already a breakpoint
Expand All @@ -127,7 +127,9 @@ void DebuggerModuleStorage::Load(XMLConfigParser& parser)

// register breakpoints in debugger
if (type == DEBUGGER_BP_T_NORMAL)
debugger_createExecuteBreakpoint(address);
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
else if (type == DEBUGGER_BP_T_LOGGING)
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
else if (type == DEBUGGER_BP_T_MEMORY_READ)
debugger_createMemoryBreakpoint(address, true, false);
else if (type == DEBUGGER_BP_T_MEMORY_WRITE)
Expand Down Expand Up @@ -173,7 +175,7 @@ void DebuggerModuleStorage::Save(XMLConfigParser& parser)

// check whether the breakpoint is part of the current module being saved
RPLModule* address_module;
if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
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;

Expand Down Expand Up @@ -259,7 +261,7 @@ void DebuggerWindow2::LoadModuleStorage(const RPLModule* module)
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; });
if (!path.empty() && !already_loaded)
{
m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false })));
m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load();
}
}

Expand Down Expand Up @@ -522,6 +524,7 @@ void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event)
{
m_breakpoint_window->OnUpdateView();
m_disasm_ctrl->RefreshControl();
UpdateModuleLabel();
}

Expand Down
Loading

0 comments on commit 651e533

Please sign in to comment.