/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see .
*/
#include "PrecompiledHeader.h"
#include "BreakpointModel.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/DisassemblyManager.h"
#include "QtHost.h"
#include "QtUtils.h"
#include
#include
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(&bp_mc))
{
switch (index.column())
{
case BreakpointColumns::TYPE:
return tr("Execute");
case BreakpointColumns::OFFSET:
return QtUtils::FilledQStringFromValue(bp->addr, 16);
case BreakpointColumns::SIZE_LABEL:
return m_cpu.GetSymbolMap().GetLabelString(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) : tr("No Condition");
case BreakpointColumns::HITS:
return tr("--");
case BreakpointColumns::ENABLED:
return bp->enabled ? tr("Enabled") : tr("Disabled");
}
}
else if (const auto* mc = std::get_if(&bp_mc))
{
switch (index.column())
{
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);
case BreakpointColumns::ENABLED:
return (mc->result & MEMCHECK_BREAK) ? tr("Enabled") : tr("Disabled");
}
}
}
else if (role == BreakpointModel::DataRole)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if(&bp_mc))
{
switch (index.column())
{
case BreakpointColumns::TYPE:
return MEMCHECK_INVALID;
case BreakpointColumns::OFFSET:
return bp->addr;
case BreakpointColumns::SIZE_LABEL:
return m_cpu.GetSymbolMap().GetLabelString(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) : tr("");
case BreakpointColumns::HITS:
return 0;
case BreakpointColumns::ENABLED:
return static_cast(bp->enabled);
}
}
else if (const auto* mc = std::get_if(&bp_mc))
{
switch (index.column())
{
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;
case BreakpointColumns::ENABLED:
return (mc->result & MEMCHECK_BREAK);
}
}
}
else if (role == BreakpointModel::ExportRole)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if(&bp_mc))
{
switch (index.column())
{
case BreakpointColumns::TYPE:
return MEMCHECK_INVALID;
case BreakpointColumns::OFFSET:
return QtUtils::FilledQStringFromValue(bp->addr, 16);
case BreakpointColumns::SIZE_LABEL:
return m_cpu.GetSymbolMap().GetLabelString(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) : tr("");
case BreakpointColumns::HITS:
return 0;
case BreakpointColumns::ENABLED:
return static_cast(bp->enabled);
}
}
else if (const auto* mc = std::get_if(&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() == 6)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if(&bp_mc))
{
return bp->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
}
else if (const auto* mc = std::get_if(&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("ENABLED");
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(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(&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(&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(bp_mc))
return false;
const auto bp = std::get(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);
for (int i = row; i < row + count; i++)
{
auto bp_mc = m_breakpoints.at(i);
if (const auto* bp = std::get_if(&bp_mc))
{
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), addr = bp->addr] {
CBreakPoints::RemoveBreakPoint(cpu, addr);
});
}
else if (const auto* mc = std::get_if(&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 breakpoints, const QModelIndex& index)
{
if (breakpoints.size() != static_cast(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(&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(&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();
}