Duckstation/src/duckstation-qt/debuggermodels.cpp
2023-01-15 15:13:54 +10:00

435 lines
12 KiB
C++

// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "debuggermodels.h"
#include "core/cpu_core.h"
#include "core/cpu_core_private.h"
#include "core/cpu_disasm.h"
#include <QtGui/QColor>
#include <QtGui/QIcon>
#include <QtGui/QPalette>
#include <QtWidgets/QApplication>
static constexpr int NUM_COLUMNS = 5;
static constexpr int STACK_RANGE = 128;
static constexpr u32 STACK_VALUE_SIZE = sizeof(u32);
DebuggerCodeModel::DebuggerCodeModel(QObject* parent /*= nullptr*/) : QAbstractTableModel(parent)
{
resetCodeView(0);
m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(QSize(12, 12));
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(QSize(12, 12));
}
DebuggerCodeModel::~DebuggerCodeModel() {}
int DebuggerCodeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE);
}
int DebuggerCodeModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return NUM_COLUMNS;
}
int DebuggerCodeModel::getRowForAddress(VirtualMemoryAddress address) const
{
return static_cast<int>((address - m_code_region_start) / CPU::INSTRUCTION_SIZE);
}
VirtualMemoryAddress DebuggerCodeModel::getAddressForRow(int row) const
{
return m_code_region_start + (static_cast<u32>(row) * CPU::INSTRUCTION_SIZE);
}
VirtualMemoryAddress DebuggerCodeModel::getAddressForIndex(QModelIndex index) const
{
return getAddressForRow(index.row());
}
int DebuggerCodeModel::getRowForPC() const
{
return getRowForAddress(m_last_pc);
}
QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
if (index.column() < 0 || index.column() >= NUM_COLUMNS)
return QVariant();
if (role == Qt::DisplayRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
switch (index.column())
{
case 0:
// breakpoint
return QVariant();
case 1:
{
// Address
return QVariant(QString::asprintf("0x%08X", address));
}
case 2:
{
// Bytes
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return tr("<invalid>");
return QString::asprintf("%08X", instruction_bits);
}
case 3:
{
// Instruction
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return tr("<invalid>");
SmallString str;
CPU::DisassembleInstruction(&str, address, instruction_bits);
return QString::fromUtf8(str.GetCharArray(), static_cast<int>(str.GetLength()));
}
case 4:
{
// Comment
if (address != m_last_pc)
return QVariant();
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return tr("<invalid>");
TinyString str;
CPU::DisassembleInstructionComment(&str, address, instruction_bits, &CPU::g_state.regs);
return QString::fromUtf8(str.GetCharArray(), static_cast<int>(str.GetLength()));
}
default:
return QVariant();
}
}
else if (role == Qt::DecorationRole)
{
if (index.column() == 0)
{
// breakpoint
const VirtualMemoryAddress address = getAddressForRow(index.row());
if (m_last_pc == address)
return m_pc_pixmap;
else if (hasBreakpointAtAddress(address))
return m_breakpoint_pixmap;
}
return QVariant();
}
else if (role == Qt::BackgroundRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
// breakpoint
if (hasBreakpointAtAddress(address))
return QVariant(QColor(171, 97, 107));
// if (address == m_last_pc)
// return QApplication::palette().toolTipBase();
if (address == m_last_pc)
return QColor(100, 100, 0);
else
return QVariant();
}
else if (role == Qt::ForegroundRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
// if (address == m_last_pc)
// return QApplication::palette().toolTipText();
if (address == m_last_pc || hasBreakpointAtAddress(address))
return QColor(Qt::white);
else
return QVariant();
}
else
{
return QVariant();
}
}
QVariant DebuggerCodeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
switch (section)
{
case 1:
return tr("Address");
case 2:
return tr("Bytes");
case 3:
return tr("Instruction");
case 4:
return tr("Comment");
case 0:
default:
return QVariant();
}
}
bool DebuggerCodeModel::updateRegion(VirtualMemoryAddress address)
{
CPU::Segment segment = CPU::GetSegmentForAddress(address);
std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address));
if (!region.has_value() || (address >= m_code_region_start && address < m_code_region_end))
return false;
static constexpr unsigned NUM_INSTRUCTIONS_BEFORE = 4096;
static constexpr unsigned NUM_INSTRUCTIONS_AFTER = 4096;
static constexpr unsigned NUM_BYTES_BEFORE = NUM_INSTRUCTIONS_BEFORE * sizeof(CPU::Instruction);
static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(CPU::Instruction);
const VirtualMemoryAddress start_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment);
const VirtualMemoryAddress end_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment);
beginResetModel();
m_code_region_start = ((address - start_address) < NUM_BYTES_BEFORE) ? start_address : (address - NUM_BYTES_BEFORE);
m_code_region_end = ((end_address - address) < NUM_BYTES_AFTER) ? end_address : (address + NUM_BYTES_AFTER);
m_current_segment = segment;
m_current_code_region = region.value();
endResetModel();
return true;
}
bool DebuggerCodeModel::emitDataChangedForAddress(VirtualMemoryAddress address)
{
CPU::Segment segment = CPU::GetSegmentForAddress(address);
std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address));
if (!region.has_value() || segment != m_current_segment || region != m_current_code_region)
return false;
const int row = getRowForAddress(address);
emit dataChanged(index(row, 0), index(row, NUM_COLUMNS - 1));
return true;
}
bool DebuggerCodeModel::hasBreakpointAtAddress(VirtualMemoryAddress address) const
{
return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end();
}
void DebuggerCodeModel::resetCodeView(VirtualMemoryAddress start_address)
{
updateRegion(start_address);
}
void DebuggerCodeModel::setPC(VirtualMemoryAddress pc)
{
const VirtualMemoryAddress prev_pc = m_last_pc;
m_last_pc = pc;
if (!updateRegion(pc))
{
emitDataChangedForAddress(prev_pc);
emitDataChangedForAddress(pc);
}
}
void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address)
{
updateRegion(address);
}
void DebuggerCodeModel::setBreakpointList(std::vector<VirtualMemoryAddress> bps)
{
clearBreakpoints();
m_breakpoints = std::move(bps);
for (VirtualMemoryAddress bp : m_breakpoints)
emitDataChangedForAddress(bp);
}
void DebuggerCodeModel::clearBreakpoints()
{
std::vector<VirtualMemoryAddress> old_bps(std::move(m_breakpoints));
for (VirtualMemoryAddress old_bp : old_bps)
emitDataChangedForAddress(old_bp);
}
void DebuggerCodeModel::setBreakpointState(VirtualMemoryAddress address, bool enabled)
{
if (enabled)
{
if (std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end())
return;
m_breakpoints.push_back(address);
emitDataChangedForAddress(address);
}
else
{
auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address);
if (it == m_breakpoints.end())
return;
m_breakpoints.erase(it);
emitDataChangedForAddress(address);
}
}
DebuggerRegistersModel::DebuggerRegistersModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) {}
DebuggerRegistersModel::~DebuggerRegistersModel() {}
int DebuggerRegistersModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return static_cast<int>(CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES);
}
int DebuggerRegistersModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return 2;
}
QVariant DebuggerRegistersModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
u32 reg_index = static_cast<u32>(index.row());
if (reg_index >= CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES)
return QVariant();
if (index.column() < 0 || index.column() > 1)
return QVariant();
switch (index.column())
{
case 0: // address
{
if (role == Qt::DisplayRole)
return QString::fromUtf8(CPU::g_debugger_register_list[reg_index].name);
}
break;
case 1: // data
{
if (role == Qt::DisplayRole)
{
return QString::asprintf("0x%08X", *CPU::g_debugger_register_list[reg_index].value_ptr);
}
else if (role == Qt::ForegroundRole)
{
if (*CPU::g_debugger_register_list[reg_index].value_ptr != m_old_reg_values[reg_index])
return QColor(255, 50, 50);
}
}
break;
default:
break;
}
return QVariant();
}
QVariant DebuggerRegistersModel::headerData(int section, Qt::Orientation orientation,
int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
switch (section)
{
case 0:
return tr("Register");
case 1:
return tr("Value");
default:
return QVariant();
}
}
void DebuggerRegistersModel::invalidateView()
{
beginResetModel();
endResetModel();
}
void DebuggerRegistersModel::saveCurrentValues()
{
for (u32 i = 0; i < static_cast<u32>(CPU::Reg::count); i++)
m_old_reg_values[i] = CPU::g_state.regs.r[i];
}
DebuggerStackModel::DebuggerStackModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) {}
DebuggerStackModel::~DebuggerStackModel() {}
int DebuggerStackModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return STACK_RANGE * 2;
}
int DebuggerStackModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return 2;
}
QVariant DebuggerStackModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
if (index.column() < 0 || index.column() > 1)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
const u32 sp = CPU::g_state.regs.sp;
const VirtualMemoryAddress address =
(sp - static_cast<u32>(STACK_RANGE * STACK_VALUE_SIZE)) + static_cast<u32>(index.row()) * STACK_VALUE_SIZE;
if (index.column() == 0)
return QString::asprintf("0x%08X", address);
u32 value;
if (!CPU::SafeReadMemoryWord(address, &value))
return tr("<invalid>");
return QString::asprintf("0x%08X", ZeroExtend32(value));
}
QVariant DebuggerStackModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
switch (section)
{
case 0:
return tr("Address");
case 1:
return tr("Value");
default:
return QVariant();
}
}
void DebuggerStackModel::invalidateView()
{
beginResetModel();
endResetModel();
}