mirror of
https://github.com/PCSX2/pcsx2.git
synced 2025-12-16 04:08:48 +00:00
* Debugger: Fix Importing Breakpoints CSV functionality This resolves a few issues with the Breakpoint CSV importing functionality. Makes the following changes/fixes: - Assembly instructions with commas caused part of the instruction text to be considered the start of a new column (comma is the CSV delimiter), resulting in "too many columns errors" and failing to import. Now puts values in quotes and detects the true start and end of the values, so that no extra columns are created. - The import function was using the incorrect columns to load since the Enabled column was moved to be the first one but the import function had not been updated. Fixed this by using the column enum values instead of hardcoded numbers, so if ordering in the model changes it will still import fine. - Updates the beginRemoveRows function to not remove an extra row. With this function you would want a "count" of 0 to remove 1, so "row, row" deletes one, "row, row+1" deletes 2. Updates it to subtract one so that the range is not including one more than it is intended to. - * Misc: Cleanup regex string & add explicit imports Uses raw string literal to make the regular expression easier to read since regex is ugly enough as it is, we really don't need escaping characters in a string on top. Also explicitly includes used Qt classes.
405 lines
12 KiB
C++
405 lines
12 KiB
C++
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: LGPL-3.0+
|
|
|
|
#include "BreakpointModel.h"
|
|
|
|
#include "DebugTools/DebugInterface.h"
|
|
#include "DebugTools/Breakpoints.h"
|
|
#include "DebugTools/DisassemblyManager.h"
|
|
|
|
#include "QtHost.h"
|
|
#include "QtUtils.h"
|
|
#include <QtWidgets/QMessageBox>
|
|
|
|
#include <algorithm>
|
|
|
|
BreakpointModel::BreakpointModel(DebugInterface& cpu, QObject* parent)
|
|
: QAbstractTableModel(parent)
|
|
, m_cpu(cpu)
|
|
{
|
|
}
|
|
|
|
int BreakpointModel::rowCount(const QModelIndex&) const
|
|
{
|
|
return m_breakpoints.size();
|
|
}
|
|
|
|
int BreakpointModel::columnCount(const QModelIndex&) const
|
|
{
|
|
return BreakpointColumns::COLUMN_COUNT;
|
|
}
|
|
|
|
QVariant BreakpointModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::ENABLED:
|
|
return "";
|
|
case BreakpointColumns::TYPE:
|
|
return tr("Execute");
|
|
case BreakpointColumns::OFFSET:
|
|
return QtUtils::FilledQStringFromValue(bp->addr, 16);
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return m_cpu.GetSymbolMap().GetLabelName(bp->addr).c_str();
|
|
case BreakpointColumns::OPCODE:
|
|
// Note: Fix up the disassemblymanager so we can use it here, instead of calling a function through the disassemblyview (yuck)
|
|
return m_cpu.disasm(bp->addr, true).c_str();
|
|
case BreakpointColumns::CONDITION:
|
|
return bp->hasCond ? QString::fromLocal8Bit(bp->cond.expressionString) : "";
|
|
case BreakpointColumns::HITS:
|
|
return tr("--");
|
|
}
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::ENABLED:
|
|
return (mc->result & MEMCHECK_BREAK) ? tr("Enabled") : tr("Disabled");
|
|
case BreakpointColumns::TYPE:
|
|
{
|
|
QString type("");
|
|
type += (mc->cond & MEMCHECK_READ) ? tr("Read") : "";
|
|
type += ((mc->cond & MEMCHECK_READWRITE) == MEMCHECK_READWRITE) ? ", " : " ";
|
|
//: (C) = changes, as in "look for changes".
|
|
type += (mc->cond & MEMCHECK_WRITE) ? (mc->cond & MEMCHECK_WRITE_ONCHANGE) ? tr("Write(C)") : tr("Write") : "";
|
|
return type;
|
|
}
|
|
case BreakpointColumns::OFFSET:
|
|
return QtUtils::FilledQStringFromValue(mc->start, 16);
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return QString::number(mc->end - mc->start, 16);
|
|
case BreakpointColumns::OPCODE:
|
|
return tr("--"); // Our address is going to point to memory, no purpose in printing the op
|
|
case BreakpointColumns::CONDITION:
|
|
return tr("--"); // No condition on memchecks
|
|
case BreakpointColumns::HITS:
|
|
return QString::number(mc->numHits);
|
|
}
|
|
}
|
|
}
|
|
else if (role == BreakpointModel::DataRole)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::ENABLED:
|
|
return static_cast<int>(bp->enabled);
|
|
case BreakpointColumns::TYPE:
|
|
return MEMCHECK_INVALID;
|
|
case BreakpointColumns::OFFSET:
|
|
return bp->addr;
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return m_cpu.GetSymbolMap().GetLabelName(bp->addr).c_str();
|
|
case BreakpointColumns::OPCODE:
|
|
// Note: Fix up the disassemblymanager so we can use it here, instead of calling a function through the disassemblyview (yuck)
|
|
return m_cpu.disasm(bp->addr, true).c_str();
|
|
case BreakpointColumns::CONDITION:
|
|
return bp->hasCond ? QString::fromLocal8Bit(bp->cond.expressionString) : "";
|
|
case BreakpointColumns::HITS:
|
|
return 0;
|
|
}
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::ENABLED:
|
|
return (mc->result & MEMCHECK_BREAK);
|
|
case BreakpointColumns::TYPE:
|
|
return mc->cond;
|
|
case BreakpointColumns::OFFSET:
|
|
return mc->start;
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return mc->end - mc->start;
|
|
case BreakpointColumns::OPCODE:
|
|
return "";
|
|
case BreakpointColumns::CONDITION:
|
|
return "";
|
|
case BreakpointColumns::HITS:
|
|
return mc->numHits;
|
|
}
|
|
}
|
|
}
|
|
else if (role == BreakpointModel::ExportRole)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::ENABLED:
|
|
return static_cast<int>(bp->enabled);
|
|
case BreakpointColumns::TYPE:
|
|
return MEMCHECK_INVALID;
|
|
case BreakpointColumns::OFFSET:
|
|
return QtUtils::FilledQStringFromValue(bp->addr, 16);
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return m_cpu.GetSymbolMap().GetLabelName(bp->addr).c_str();
|
|
case BreakpointColumns::OPCODE:
|
|
// Note: Fix up the disassemblymanager so we can use it here, instead of calling a function through the disassemblyview (yuck)
|
|
return m_cpu.disasm(bp->addr, true).c_str();
|
|
case BreakpointColumns::CONDITION:
|
|
return bp->hasCond ? QString::fromLocal8Bit(bp->cond.expressionString) : "";
|
|
case BreakpointColumns::HITS:
|
|
return 0;
|
|
}
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::TYPE:
|
|
return mc->cond;
|
|
case BreakpointColumns::OFFSET:
|
|
return QtUtils::FilledQStringFromValue(mc->start, 16);
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return mc->end - mc->start;
|
|
case BreakpointColumns::OPCODE:
|
|
return "";
|
|
case BreakpointColumns::CONDITION:
|
|
return "";
|
|
case BreakpointColumns::HITS:
|
|
return mc->numHits;
|
|
case BreakpointColumns::ENABLED:
|
|
return (mc->result & MEMCHECK_BREAK);
|
|
}
|
|
}
|
|
}
|
|
else if (role == Qt::CheckStateRole)
|
|
{
|
|
if (index.column() == 0)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
return bp->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
return (mc->result & MEMCHECK_BREAK) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
|
|
}
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
|
|
{
|
|
switch (section)
|
|
{
|
|
case BreakpointColumns::TYPE:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("TYPE");
|
|
case BreakpointColumns::OFFSET:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("OFFSET");
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("SIZE / LABEL");
|
|
case BreakpointColumns::OPCODE:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("INSTRUCTION");
|
|
case BreakpointColumns::CONDITION:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("CONDITION");
|
|
case BreakpointColumns::HITS:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("HITS");
|
|
case BreakpointColumns::ENABLED:
|
|
//: Warning: limited space available. Abbreviate if needed.
|
|
return tr("X");
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
Qt::ItemFlags BreakpointModel::flags(const QModelIndex& index) const
|
|
{
|
|
volatile const int row = index.row();
|
|
|
|
bool is_breakpoint = std::holds_alternative<BreakPoint>(m_breakpoints.at(row));
|
|
|
|
switch (index.column())
|
|
{
|
|
case BreakpointColumns::CONDITION:
|
|
if (is_breakpoint)
|
|
return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEditable;
|
|
[[fallthrough]];
|
|
case BreakpointColumns::TYPE:
|
|
case BreakpointColumns::OPCODE:
|
|
case BreakpointColumns::HITS:
|
|
case BreakpointColumns::OFFSET:
|
|
case BreakpointColumns::SIZE_LABEL:
|
|
return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable;
|
|
case BreakpointColumns::ENABLED:
|
|
return Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable;
|
|
}
|
|
|
|
return index.flags();
|
|
}
|
|
|
|
bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (role == Qt::CheckStateRole && index.column() == BreakpointColumns::ENABLED)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = this->m_cpu.getCpuType(), bp = *bp, enabled = value.toBool()] {
|
|
CBreakPoints::ChangeBreakPoint(cpu, bp.addr, enabled);
|
|
});
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = this->m_cpu.getCpuType(), mc = *mc] {
|
|
CBreakPoints::ChangeMemCheck(cpu, mc.start, mc.end, mc.cond,
|
|
MemCheckResult(mc.result ^ MEMCHECK_BREAK));
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
else if (role == Qt::EditRole && index.column() == BreakpointColumns::CONDITION)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(index.row());
|
|
|
|
if (std::holds_alternative<MemCheck>(bp_mc))
|
|
return false;
|
|
|
|
const auto bp = std::get<BreakPoint>(bp_mc);
|
|
|
|
const QString condValue = value.toString();
|
|
|
|
if (condValue.isEmpty())
|
|
{
|
|
if (bp.hasCond)
|
|
{
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp] {
|
|
CBreakPoints::ChangeBreakPointRemoveCond(cpu, bp.addr);
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PostfixExpression expr;
|
|
|
|
if (!m_cpu.initExpression(condValue.toLocal8Bit().constData(), expr))
|
|
{
|
|
QMessageBox::warning(nullptr, "Condition Error", QString(getExpressionError()));
|
|
return false;
|
|
}
|
|
|
|
BreakPointCond cond;
|
|
cond.debug = &m_cpu;
|
|
cond.expression = expr;
|
|
strcpy(&cond.expressionString[0], condValue.toLocal8Bit().constData());
|
|
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp, cond] {
|
|
CBreakPoints::ChangeBreakPointAddCond(cpu, bp.addr, cond);
|
|
});
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BreakpointModel::removeRows(int row, int count, const QModelIndex& index)
|
|
{
|
|
beginRemoveRows(index, row, row + count - 1);
|
|
|
|
for (int i = row; i < row + count; i++)
|
|
{
|
|
auto bp_mc = m_breakpoints.at(i);
|
|
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), addr = bp->addr] {
|
|
CBreakPoints::RemoveBreakPoint(cpu, addr);
|
|
});
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), start = mc->start, end = mc->end] {
|
|
CBreakPoints::RemoveMemCheck(cpu, start, end);
|
|
});
|
|
}
|
|
}
|
|
|
|
endRemoveRows();
|
|
return true;
|
|
}
|
|
|
|
bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<BreakpointMemcheck> breakpoints, const QModelIndex& index)
|
|
{
|
|
if (breakpoints.size() != static_cast<size_t>(count))
|
|
return false;
|
|
|
|
beginInsertRows(index, row, row + (count - 1));
|
|
|
|
// After endInsertRows, Qt will try and validate our new rows
|
|
// Because we add the breakpoints off of the UI thread, our new rows may not be visible yet
|
|
// To prevent the (seemingly harmless?) warning emitted by enderInsertRows, add the breakpoints manually here as well
|
|
m_breakpoints.insert(m_breakpoints.begin(), breakpoints.begin(), breakpoints.end());
|
|
for (const auto& bp_mc : breakpoints)
|
|
{
|
|
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp = *bp] {
|
|
CBreakPoints::AddBreakPoint(cpu, bp.addr, false, bp.enabled);
|
|
|
|
if (bp.hasCond)
|
|
{
|
|
CBreakPoints::ChangeBreakPointAddCond(cpu, bp.addr, bp.cond);
|
|
}
|
|
});
|
|
}
|
|
else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
|
|
{
|
|
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), mc = *mc] {
|
|
CBreakPoints::AddMemCheck(cpu, mc.start, mc.end, mc.cond, mc.result);
|
|
});
|
|
}
|
|
}
|
|
|
|
endInsertRows();
|
|
return true;
|
|
}
|
|
|
|
void BreakpointModel::refreshData()
|
|
{
|
|
beginResetModel();
|
|
|
|
m_breakpoints.clear();
|
|
|
|
auto breakpoints = CBreakPoints::GetBreakpoints(m_cpu.getCpuType(), false);
|
|
for (const auto& bp : breakpoints)
|
|
{
|
|
m_breakpoints.push_back(bp);
|
|
}
|
|
|
|
auto memchecks = CBreakPoints::GetMemChecks(m_cpu.getCpuType());
|
|
for (const auto& mc : memchecks)
|
|
{
|
|
m_breakpoints.push_back(mc);
|
|
}
|
|
|
|
endResetModel();
|
|
}
|