mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-10-11 22:49:56 +00:00
Debugger: Add instrumentation feature.
This commit is contained in:
@@ -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. "ra={ra} val={[4,sp]}". Use "{{" and "}}" 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;
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user