Debugger: Add instrumentation feature.

This commit is contained in:
mmvest
2026-09-18 07:10:37 +02:00
committed by lightningterror
parent 0d8e1c0d44
commit 89feb5095f
7 changed files with 255 additions and 4 deletions
@@ -46,6 +46,10 @@ BreakpointDialog::BreakpointDialog(QWidget* parent, DebugInterface* cpu, Breakpo
m_ui.txtDescription->setText(QString::fromStdString(bp->description));
m_ui.spnMaxHitCount->setValue(static_cast<int>(bp->maxHits));
m_ui.grpInstrumentation->setChecked(bp->instrumentationEnabled);
m_ui.txtLogFormat->setText(QString::fromStdString(bp->logFormat));
m_ui.chkContinueOnHit->setChecked(bp->continueOnHit);
if (bp->hasCond)
m_ui.txtCondition->setText(QString::fromStdString(bp->cond.expressionString));
}
@@ -66,6 +70,10 @@ BreakpointDialog::BreakpointDialog(QWidget* parent, DebugInterface* cpu, Breakpo
m_ui.chkLog->setChecked(mc->result & MEMCHECK_LOG);
m_ui.spnMaxHitCount->setValue(static_cast<int>(mc->maxHits));
m_ui.grpInstrumentation->setChecked(mc->instrumentationEnabled);
m_ui.txtLogFormat->setText(QString::fromStdString(mc->logFormat));
m_ui.chkContinueOnHit->setChecked(mc->continueOnHit);
if (mc->hasCond)
m_ui.txtCondition->setText(QString::fromStdString(mc->cond.expressionString));
}
@@ -116,6 +124,17 @@ void BreakpointDialog::accept()
bp->maxHits = static_cast<u32>(m_ui.spnMaxHitCount->value());
bp->enabled = m_ui.chkEnable->isChecked();
const bool instrumentationEnabled = m_ui.grpInstrumentation->isChecked();
const std::string logFormat = m_ui.txtLogFormat->text().toStdString();
if (instrumentationEnabled && !logFormat.empty() && !ValidateInstrumentationLogFormat(*m_cpu, logFormat, error))
{
AsyncDialogs::warning(g_debugger_window, tr("Invalid Log Format"), QString::fromStdString(error));
return;
}
bp->instrumentationEnabled = instrumentationEnabled;
bp->logFormat = logFormat;
bp->continueOnHit = m_ui.chkContinueOnHit->isChecked();
if (!m_ui.txtCondition->text().isEmpty())
{
bp->hasCond = true;
@@ -161,6 +180,17 @@ void BreakpointDialog::accept()
mc->maxHits = static_cast<u32>(m_ui.spnMaxHitCount->value());
mc->description = m_ui.txtDescription->text().toStdString();
const bool instrumentationEnabled = m_ui.grpInstrumentation->isChecked();
const std::string logFormat = m_ui.txtLogFormat->text().toStdString();
if (instrumentationEnabled && !logFormat.empty() && !ValidateInstrumentationLogFormat(*m_cpu, logFormat, error))
{
AsyncDialogs::warning(g_debugger_window, tr("Invalid Log Format"), QString::fromStdString(error));
return;
}
mc->instrumentationEnabled = instrumentationEnabled;
mc->logFormat = logFormat;
mc->continueOnHit = m_ui.chkContinueOnHit->isChecked();
if (!m_ui.txtCondition->text().isEmpty())
{
mc->hasCond = true;
@@ -285,7 +285,7 @@
</widget>
<widget class="QGroupBox" name="grpInstrumentation">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="geometry">
<rect>
@@ -318,12 +318,12 @@
<item row="0" column="1">
<widget class="QLineEdit" name="txtLogFormat">
<property name="toolTip">
<string>Logged to the console when the breakpoint is hit. Wrap an expression in braces to substitute its value, e.g. &quot;ra={ra} val={[4,sp]}&quot;. Use &quot;{{&quot; and &quot;}}&quot; for literal braces.</string>
<string>Logged to the console when the breakpoint is hit. Wrap an expression in braces to substitute its value, e.g. "ra={ra} val={[sp,4]}". Use "{{" and "}}" for literal braces.</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="chkLogContinue">
<widget class="QCheckBox" name="chkContinueOnHit">
<property name="toolTip">
<string>Log the message and resume execution instead of pausing.</string>
</property>
@@ -434,7 +434,7 @@
<tabstop>txtSize</tabstop>
<tabstop>txtCondition</tabstop>
<tabstop>txtLogFormat</tabstop>
<tabstop>chkLogContinue</tabstop>
<tabstop>chkContinueOnHit</tabstop>
</tabstops>
<resources/>
<connections>
@@ -517,6 +517,7 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break
CBreakPoints::ChangeBreakPointDescription(cpu, bp.addr, bp.description);
CBreakPoints::ChangeBreakPointMaxHits(cpu, bp.addr, bp.maxHits);
CBreakPoints::ChangeBreakPointTotalHits(cpu, bp.addr, bp.totalHits);
CBreakPoints::ChangeBreakPointInstrumentation(cpu, bp.addr, bp.instrumentationEnabled, bp.logFormat, bp.continueOnHit);
if (bp.hasCond)
{
CBreakPoints::ChangeBreakPointAddCond(cpu, bp.addr, bp.cond);
@@ -530,6 +531,7 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break
CBreakPoints::ChangeMemCheckDescription(cpu, mc.start, mc.end, mc.description);
CBreakPoints::ChangeMemCheckMaxHits(cpu, mc.start, mc.end, mc.maxHits);
CBreakPoints::ChangeMemCheckTotalHits(cpu, mc.start, mc.end, mc.totalHits);
CBreakPoints::ChangeMemCheckInstrumentation(cpu, mc.start, mc.end, mc.instrumentationEnabled, mc.logFormat, mc.continueOnHit);
if (mc.hasCond)
{
CBreakPoints::ChangeMemCheckAddCond(cpu, mc.start, mc.end, mc.cond);
@@ -26,6 +26,36 @@ BreakpointView::BreakpointView(const DebuggerViewParameters& parameters)
this->resizeColumns();
connect(g_debugger_window, &DebuggerWindow::onVMActuallyPaused, m_model, &BreakpointModel::refreshData);
connect(m_model, &QAbstractTableModel::modelAboutToBeReset, this, [this]() {
const QItemSelectionModel* sel = m_ui.breakpointList->selectionModel();
if (sel->hasSelection())
{
m_selectedRowOnRefresh = sel->selectedIndexes().first().row();
m_selectedColOnRefresh = sel->selectedIndexes().first().column();
}
else
{
m_selectedRowOnRefresh = -1;
m_selectedColOnRefresh = -1;
}
});
connect(m_model, &QAbstractTableModel::modelReset, this, [this]() {
if (m_selectedRowOnRefresh >= 0 && m_selectedRowOnRefresh < m_model->rowCount())
{
const QModelIndex idx = m_model->index(m_selectedRowOnRefresh, m_selectedColOnRefresh);
m_ui.breakpointList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
}
});
// Continue-on-hit breakpoints update their hit counts without ever pausing the VM,
// so onVMActuallyPaused never fires. Refresh periodically off the debugger's
// refresh timer so the UI can update regularly.
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
if (!QtHost::IsVMPaused())
m_model->refreshData();
return true;
});
}
void BreakpointView::onDoubleClicked(const QModelIndex& index)
@@ -40,4 +40,6 @@ private:
Ui::BreakpointView m_ui;
BreakpointModel* m_model;
int m_selectedRowOnRefresh = -1;
int m_selectedColOnRefresh = -1;
};
+174
View File
@@ -7,6 +7,7 @@
#include <cstdio>
#include "R5900.h"
#include "R3000A.h"
#include "common/Console.h"
std::vector<BreakPoint> CBreakPoints::breakPoints_;
u32 CBreakPoints::breakSkipFirstAtEE_ = 0;
@@ -35,6 +36,129 @@ u32 standardizeBreakpointAddress(u32 addr)
return addr;
}
// Parses a format string. Calls onLiteral for strings of literal characters and calls
// onExpression for escaped expressions. An escaped expression is an expression that appears
// between curly braces (e.g. {v0}). Can interpret literal curly braces by double escaping,
// e.g. "{{v0}}" would call onLiteral on "{v0}" rather than onExpression. Ideally this is
// meant to be used how we use it in EvaluateInstrumentationLogFormat, building a literal
// string out of the literal segments and evaluating the expressions for the expression statements.
// Returns false if the brackets are unbalanced (e.g. "{{}" contains an unbalanced set of brackets).
template <typename LiteralFn, typename ExpressionFn>
static bool ParseInstrumentationLogFormat(const std::string& format, std::string& error, LiteralFn onLiteral, ExpressionFn onExpression)
{
std::string literal;
for (size_t i = 0; i < format.size(); i++)
{
const char c = format[i];
if (c == '{')
{
if (i + 1 < format.size() && format[i + 1] == '{')
{
literal += '{';
i++;
continue;
}
const size_t close = format.find('}', i + 1);
if (close == std::string::npos)
{
error = "Unmatched '{' in log format.";
return false;
}
if (!literal.empty())
{
onLiteral(literal);
literal.clear();
}
onExpression(format.substr(i + 1, close - (i + 1)));
i = close;
}
else if (c == '}')
{
if (i + 1 < format.size() && format[i + 1] == '}')
{
literal += '}';
i++;
continue;
}
error = "Unmatched '}' in log format.";
return false;
}
else
{
literal += c;
}
}
if (!literal.empty())
onLiteral(literal);
return true;
}
// Example of evaluated strings:
// format: "hit at PC={pc} ra={ra} a0={a0} a1={a1} v0={v0} sp={sp} mem=[{sp}]={{[{sp}]}} lit={{braces}}"
// out: "hit at PC=0x438AB4 ra=0x438AAC a0=0x0 a1=0x0 v0=0x0 sp=0x1FFFA70 mem=[0x1FFFA70]={[0x1FFFA70]} lit={braces}"
std::string EvaluateInstrumentationLogFormat(DebugInterface& debug, const std::string& format)
{
std::string out;
std::string error;
ParseInstrumentationLogFormat(
format,
error,
[&out](const std::string& literal) { out += literal; },
[&out, &debug](const std::string& expression) {
PostfixExpression expr;
std::string err;
u64 value;
if (debug.initExpression(expression.c_str(), expr, err) && debug.parseExpression(expr, value, err))
{
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "0x%llX", static_cast<unsigned long long>(value));
out += buffer;
}
else
{
out += "<err>";
}
});
return out;
}
bool ValidateInstrumentationLogFormat(DebugInterface& debug, const std::string& format, std::string& error)
{
bool areExpressionsValid = true;
const bool areBracketsBalanced = ParseInstrumentationLogFormat(
format,
error,
[](const std::string&) {},
[&areExpressionsValid, &error, &debug](const std::string& expression) {
if (!areExpressionsValid)
return;
PostfixExpression expr;
u64 value;
if (!debug.initExpression(expression.c_str(), expr, error) ||
!debug.parseExpression(expr, value, error))
areExpressionsValid = false;
});
return areBracketsBalanced && areExpressionsValid;
}
// Logs an instrumentation message to the console for the breakpoint/memcheck that was just hit.
static void LogInstrumentation(BreakPointCpu cpu, const std::string& logFormat)
{
if (logFormat.empty())
return;
DebugInterface& debug = (cpu == BREAKPOINT_IOP) ? static_cast<DebugInterface&>(r3000Debug)
: static_cast<DebugInterface&>(r5900Debug);
const ConsoleColors color = (cpu == BREAKPOINT_IOP) ? Color_Yellow : Color_Cyan;
Console.WriteLn(color, "%s", EvaluateInstrumentationLogFormat(debug, logFormat).c_str());
}
MemCheck::MemCheck()
: start(0)
, end(0)
@@ -45,6 +169,8 @@ MemCheck::MemCheck()
, maxHits(0)
, hitsSinceEnabled(0)
, totalHits(0)
, instrumentationEnabled(false)
, continueOnHit(false)
, lastPC(0)
, lastAddr(0)
, lastSize(0)
@@ -318,8 +444,24 @@ void CBreakPoints::ChangeBreakPointTotalHits(BreakPointCpu cpu, u32 addr, u32 to
}
}
void CBreakPoints::ChangeBreakPointInstrumentation(BreakPointCpu cpu, u32 addr, bool enabled, const std::string& logFormat, bool continueOnHit)
{
const size_t bp = FindBreakpoint(cpu, addr, true, false);
if (bp != INVALID_BREAKPOINT)
{
breakPoints_[bp].instrumentationEnabled = enabled;
breakPoints_[bp].logFormat = logFormat;
breakPoints_[bp].continueOnHit = continueOnHit;
Update();
}
}
bool CBreakPoints::HandleBreakpointHit(BreakPointCpu cpu, u32 addr)
{
if (IsTempBreakPoint(cpu, addr))
return true;
const size_t breakpointIndex = FindBreakpoint(cpu, addr, true, false);
if (breakpointIndex == INVALID_BREAKPOINT)
return false;
@@ -328,11 +470,21 @@ bool CBreakPoints::HandleBreakpointHit(BreakPointCpu cpu, u32 addr)
breakpoint.hitsSinceEnabled++;
breakpoint.totalHits++;
const bool instrumentationEnabled = breakpoint.instrumentationEnabled;
const std::string logFormat = breakpoint.logFormat;
const bool continueOnHit = breakpoint.continueOnHit;
if (instrumentationEnabled)
LogInstrumentation(cpu, logFormat);
if(breakpoint.maxHits > 0 && breakpoint.hitsSinceEnabled >= breakpoint.maxHits)
{
ChangeBreakPoint(cpu, addr, false);
}
if (instrumentationEnabled && continueOnHit)
return false;
return true;
}
@@ -346,11 +498,21 @@ bool CBreakPoints::HandleMemCheckHit(BreakPointCpu cpu, u32 start, u32 end)
memCheck.hitsSinceEnabled++;
memCheck.totalHits++;
const bool instrumentationEnabled = memCheck.instrumentationEnabled;
const std::string logFormat = memCheck.logFormat;
const bool continueOnHit = memCheck.continueOnHit;
if (instrumentationEnabled)
LogInstrumentation(cpu, logFormat);
if(memCheck.maxHits > 0 && memCheck.hitsSinceEnabled >= memCheck.maxHits)
{
ChangeMemCheck(cpu, start, end, memCheck.memCond, MemCheckResult(memCheck.result & ~MEMCHECK_BREAK));
}
if (instrumentationEnabled && continueOnHit)
return false;
return true;
}
@@ -456,6 +618,18 @@ void CBreakPoints::ChangeMemCheckTotalHits(BreakPointCpu cpu, u32 start, u32 end
}
}
void CBreakPoints::ChangeMemCheckInstrumentation(BreakPointCpu cpu, u32 start, u32 end, bool enabled, const std::string& logFormat, bool continueOnHit)
{
const size_t mc = FindMemCheck(cpu, start, end);
if (mc != INVALID_MEMCHECK)
{
memChecks_[mc].instrumentationEnabled = enabled;
memChecks_[mc].logFormat = logFormat;
memChecks_[mc].continueOnHit = continueOnHit;
Update(cpu);
}
}
void CBreakPoints::ClearAllMemChecks()
{
// This will ruin any pending memchecks.
+13
View File
@@ -41,6 +41,10 @@ struct BreakPoint
u32 hitsSinceEnabled = 0;
u32 totalHits = 0;
bool instrumentationEnabled = false;
std::string logFormat;
bool continueOnHit = false;
std::string description;
bool operator==(const BreakPoint& other) const
@@ -90,6 +94,10 @@ struct MemCheck
u32 hitsSinceEnabled;
u32 totalHits;
bool instrumentationEnabled;
std::string logFormat;
bool continueOnHit;
u32 lastPC;
u32 lastAddr;
int lastSize;
@@ -132,6 +140,7 @@ public:
static void ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, const std::string& description);
static void ChangeBreakPointMaxHits(BreakPointCpu cpu, u32 addr, u32 maxHits);
static void ChangeBreakPointTotalHits(BreakPointCpu cpu, u32 addr, u32 totalHits);
static void ChangeBreakPointInstrumentation(BreakPointCpu cpu, u32 addr, bool enabled, const std::string& logFormat, bool continueOnHit);
static bool HandleBreakpointHit(BreakPointCpu cpu, u32 addr);
@@ -143,6 +152,7 @@ public:
static void ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, const std::string& description);
static void ChangeMemCheckMaxHits(BreakPointCpu cpu, u32 start, u32 end, u32 maxHits);
static void ChangeMemCheckTotalHits(BreakPointCpu cpu, u32 start, u32 end, u32 totalHits);
static void ChangeMemCheckInstrumentation(BreakPointCpu cpu, u32 start, u32 end, bool enabled, const std::string& logFormat, bool continueOnHit);
static void ClearAllMemChecks();
static bool HandleMemCheckHit(BreakPointCpu cpu, u32 start, u32 end);
@@ -200,3 +210,6 @@ private:
// called from the dynarec
u32 standardizeBreakpointAddress(u32 addr);
std::string EvaluateInstrumentationLogFormat(DebugInterface& debug, const std::string& format);
bool ValidateInstrumentationLogFormat(DebugInterface& debug, const std::string& format, std::string& error);