Debugger: Copy as CSV. Breakpoint import from CSV

This commit is contained in:
Ty Lamontagne 2023-10-09 18:27:24 -04:00 committed by Connor McLaughlin
parent ade2b4baea
commit 4f825641ce
7 changed files with 183 additions and 10 deletions

View File

@ -282,6 +282,18 @@ void CpuWidget::onBPListContextMenu(QPoint pos)
contextMenu->addAction(deleteAction);
}
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList);
connect(actionExport, &QAction::triggered, [this]() {
// It's important to use the User Role here to allow pasting to be translation agnostic
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), Qt::UserRole));
});
contextMenu->addAction(actionExport);
QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList);
connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV);
contextMenu->addAction(actionImport);
contextMenu->popup(m_ui.breakpointList->mapToGlobal(pos));
}
@ -335,6 +347,109 @@ void CpuWidget::contextBPListEdit()
bpDialog->show();
}
void CpuWidget::contextBPListPasteCSV()
{
QString csv = QGuiApplication::clipboard()->text();
// Skip header
csv = csv.mid(csv.indexOf('\n') + 1);
for (const QString& line : csv.split('\n'))
{
const QStringList fields = line.split(',');
if (fields.size() != BreakpointModel::BreakpointColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger CSV Import: Invalid number of columns, skipping");
continue;
}
bool ok;
int type = fields[0].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse type '%s', skipping", fields[0].toUtf8().constData());
continue;
}
// This is how we differentiate between breakpoints and memchecks
if (type == MEMCHECK_INVALID)
{
BreakPoint bp;
// Address
bp.addr = fields[1].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[1].toUtf8().constData());
continue;
}
// Condition
if (fields[4] != "No Condition")
{
PostfixExpression expr;
bp.hasCond = true;
bp.cond.debug = &m_cpu;
if (!m_cpu.initExpression(fields[4].toUtf8().constData(), expr))
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond '%s', skipping", fields[4].toUtf8().constData());
continue;
}
bp.cond.expression = expr;
strncpy(&bp.cond.expressionString[0], fields[4].toUtf8().constData(), sizeof(bp.cond.expressionString));
}
// Enabled
bp.enabled = fields[6].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse enable flag '%s', skipping", fields[1].toUtf8().constData());
continue;
}
m_bpModel.insertBreakpointRows(0, 1, {bp});
}
else
{
MemCheck mc;
// Mode
if (type >= MEMCHECK_INVALID)
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond type '%s', skipping", fields[0].toUtf8().constData());
continue;
}
mc.cond = static_cast<MemCheckCondition>(type);
// Address
mc.start = fields[1].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[1].toUtf8().constData());
continue;
}
// Size
mc.end = fields[2].toUInt(&ok, 16) + mc.start;
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse length '%s', skipping", fields[1].toUtf8().constData());
continue;
}
// Result
int result = fields[6].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse result flag '%s', skipping", fields[1].toUtf8().constData());
continue;
}
mc.result = static_cast<MemCheckResult>(result);
m_bpModel.insertBreakpointRows(0, 1, {mc});
}
}
}
void CpuWidget::updateFunctionList(bool whenEmpty)
{
if (!m_cpu.isAlive())
@ -396,6 +511,14 @@ void CpuWidget::onThreadListContextMenu(QPoint pos)
});
contextMenu->addAction(actionCopy);
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.threadList);
connect(actionExport, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model()));
});
contextMenu->addAction(actionExport);
contextMenu->popup(m_ui.threadList->mapToGlobal(pos));
}
@ -499,6 +622,14 @@ void CpuWidget::onStackListContextMenu(QPoint pos)
});
contextMenu->addAction(actionCopy);
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.stackList);
connect(actionExport, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model()));
});
contextMenu->addAction(actionExport);
contextMenu->popup(m_ui.stackList->mapToGlobal(pos));
}

View File

@ -60,6 +60,7 @@ public slots:
void contextBPListDelete();
void contextBPListNew();
void contextBPListEdit();
void contextBPListPasteCSV();
void updateThreads();
void onThreadListDoubleClick(const QModelIndex& index);

View File

@ -107,7 +107,7 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
switch (index.column())
{
case BreakpointColumns::TYPE:
return 0;
return MEMCHECK_INVALID;
case BreakpointColumns::OFFSET:
return bp->addr;
case BreakpointColumns::SIZE_LABEL:
@ -120,7 +120,7 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
case BreakpointColumns::HITS:
return 0;
case BreakpointColumns::ENABLED:
return bp->enabled;
return static_cast<int>(bp->enabled);
}
}
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
@ -330,7 +330,7 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp = *bp] {
CBreakPoints::AddBreakPoint(cpu, bp.addr);
CBreakPoints::AddBreakPoint(cpu, bp.addr, false, bp.enabled);
if (bp.hasCond)
{

View File

@ -95,8 +95,8 @@ namespace QtUtils
const int min_column_width = header->minimumSectionSize();
const int scrollbar_width = ((view->verticalScrollBar() && view->verticalScrollBar()->isVisible()) ||
view->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOn) ?
view->verticalScrollBar()->width() :
0;
view->verticalScrollBar()->width() :
0;
int num_flex_items = 0;
int total_width = 0;
int column_index = 0;
@ -115,8 +115,8 @@ namespace QtUtils
const int flex_width =
(num_flex_items > 0) ?
std::max((view->contentsRect().width() - total_width - scrollbar_width) / num_flex_items, 1) :
0;
std::max((view->contentsRect().width() - total_width - scrollbar_width) / num_flex_items, 1) :
0;
column_index = 0;
for (const int spec_width : widths)
@ -269,4 +269,40 @@ namespace QtUtils
return wi;
}
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role)
{
QString csv;
// Header
for (int col = 0; col < model->columnCount(); col++)
{
csv += model->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
if (col < model->columnCount() - 1)
csv += ",";
}
csv += "\n";
// Data
for (int row = 0; row < model->rowCount(); row++)
{
for (int col = 0; col < model->columnCount(); col++)
{
switch(model->data(model->index(row, col), role).metaType().id())
{
case QMetaType::Int:
case QMetaType::UInt:
csv += QString::number(model->data(model->index(row, col), role).toUInt(nullptr), 16);
break;
default:
csv += model->data(model->index(row, col), role).toString();
break;
}
if (col < model->columnCount() - 1)
csv += ",";
}
csv += "\n";
}
return csv;
}
} // namespace QtUtils

View File

@ -20,6 +20,7 @@
#include <QtCore/QByteArray>
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include <QtCore/QAbstractItemModel>
#include <functional>
#include <initializer_list>
#include <string_view>
@ -93,4 +94,7 @@ namespace QtUtils
{
return QString("%1").arg(QString::number(val, base), sizeof(val) * 2, '0').toUpper();
};
/// Converts an abstract item model to a CSV string.
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole);
} // namespace QtUtils

View File

@ -185,13 +185,13 @@ bool CBreakPoints::IsTempBreakPoint(BreakPointCpu cpu, u32 addr)
return bp != INVALID_BREAKPOINT;
}
void CBreakPoints::AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp)
void CBreakPoints::AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp, bool enabled)
{
size_t bp = FindBreakpoint(cpu, addr, true, temp);
if (bp == INVALID_BREAKPOINT)
{
BreakPoint pt;
pt.enabled = true;
pt.enabled = enabled;
pt.temporary = temp;
pt.addr = addr;
pt.cpu = cpu;

View File

@ -69,6 +69,7 @@ enum MemCheckCondition
MEMCHECK_WRITE_ONCHANGE = 0x04,
MEMCHECK_READWRITE = 0x03,
MEMCHECK_INVALID = 0x08, // Invalid condition, used by the CSV parser to know if the line is for a memcheck
};
enum MemCheckResult
@ -119,7 +120,7 @@ public:
static bool IsAddressBreakPoint(BreakPointCpu cpu, u32 addr);
static bool IsAddressBreakPoint(BreakPointCpu cpu, u32 addr, bool* enabled);
static bool IsTempBreakPoint(BreakPointCpu cpu, u32 addr);
static void AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp = false);
static void AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp = false, bool enabled = true);
static void RemoveBreakPoint(BreakPointCpu cpu, u32 addr);
static void ChangeBreakPoint(BreakPointCpu cpu, u32 addr, bool enable);
static void ClearAllBreakPoints();