mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-19 04:44:33 +00:00
Qt: Add custom code view for debugger
Branch arrows, syntax highlighting.
This commit is contained in:
@@ -57,6 +57,8 @@ set(SRCS
|
||||
coverdownloadwindow.h
|
||||
coverdownloadwindow.ui
|
||||
debuggeraddbreakpointdialog.ui
|
||||
debuggercodeview.cpp
|
||||
debuggercodeview.h
|
||||
debuggermodels.cpp
|
||||
debuggermodels.h
|
||||
debuggerwindow.cpp
|
||||
|
||||
690
src/duckstation-qt/debuggercodeview.cpp
Normal file
690
src/duckstation-qt/debuggercodeview.cpp
Normal file
@@ -0,0 +1,690 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "debuggercodeview.h"
|
||||
|
||||
#include "core/bus.h"
|
||||
#include "core/cpu_core.h"
|
||||
#include "core/cpu_core_private.h"
|
||||
#include "core/cpu_disasm.h"
|
||||
#include "core/cpu_types.h"
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/small_string.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QFontDatabase>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QPainterPath>
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QStyleOption>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "moc_debuggercodeview.cpp"
|
||||
|
||||
LOG_CHANNEL(Host);
|
||||
|
||||
DebuggerCodeView::DebuggerCodeView(QWidget* parent) : QAbstractScrollArea(parent)
|
||||
{
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
|
||||
updateRowHeight();
|
||||
|
||||
// Load icons
|
||||
m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(12);
|
||||
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(12);
|
||||
|
||||
// Connect scroll bar
|
||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) {
|
||||
const VirtualMemoryAddress new_top = getAddressForRow(value);
|
||||
// Clamp to valid range to prevent wrapping
|
||||
if (new_top >= m_code_region_start && new_top < m_code_region_end)
|
||||
{
|
||||
m_top_address = new_top;
|
||||
updateVisibleRange();
|
||||
// Recalculate arrows when scroll position changes
|
||||
calculateBranchArrows();
|
||||
viewport()->update();
|
||||
}
|
||||
});
|
||||
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
// Initialize code region
|
||||
resetCodeView(0);
|
||||
}
|
||||
|
||||
DebuggerCodeView::~DebuggerCodeView() = default;
|
||||
|
||||
void DebuggerCodeView::updateRowHeight()
|
||||
{
|
||||
const QFontMetrics font_metrics(fontMetrics());
|
||||
m_row_height = font_metrics.height() + 2;
|
||||
m_char_width = font_metrics.horizontalAdvance('M');
|
||||
}
|
||||
|
||||
void DebuggerCodeView::resetCodeView(VirtualMemoryAddress start_address)
|
||||
{
|
||||
updateRegion(start_address);
|
||||
calculateBranchArrows(); // Recalculate arrows when region changes
|
||||
}
|
||||
|
||||
void DebuggerCodeView::setPC(VirtualMemoryAddress pc)
|
||||
{
|
||||
if (m_last_pc == pc)
|
||||
return;
|
||||
|
||||
m_last_pc = pc;
|
||||
|
||||
if (!updateRegion(pc))
|
||||
{
|
||||
// Just update the display if region didn't change
|
||||
viewport()->update();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Region changed, recalculate arrows
|
||||
calculateBranchArrows();
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerCodeView::ensureAddressVisible(VirtualMemoryAddress address)
|
||||
{
|
||||
const bool region_changed = updateRegion(address);
|
||||
if (region_changed)
|
||||
{
|
||||
calculateBranchArrows();
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerCodeView::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);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address);
|
||||
if (it == m_breakpoints.end())
|
||||
return;
|
||||
|
||||
m_breakpoints.erase(it);
|
||||
}
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void DebuggerCodeView::clearBreakpoints()
|
||||
{
|
||||
m_breakpoints.clear();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
bool DebuggerCodeView::hasBreakpointAtAddress(VirtualMemoryAddress address) const
|
||||
{
|
||||
return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end();
|
||||
}
|
||||
|
||||
int DebuggerCodeView::getRowForAddress(VirtualMemoryAddress address) const
|
||||
{
|
||||
if (address < m_code_region_start)
|
||||
return 0;
|
||||
if (address >= m_code_region_end)
|
||||
return static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE) - 1;
|
||||
|
||||
return static_cast<int>((address - m_code_region_start) / CPU::INSTRUCTION_SIZE);
|
||||
}
|
||||
|
||||
VirtualMemoryAddress DebuggerCodeView::getAddressForRow(int row) const
|
||||
{
|
||||
const VirtualMemoryAddress address = m_code_region_start + (static_cast<u32>(row) * CPU::INSTRUCTION_SIZE);
|
||||
return std::clamp(address, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE);
|
||||
}
|
||||
|
||||
bool DebuggerCodeView::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(u32);
|
||||
static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(u32);
|
||||
|
||||
const VirtualMemoryAddress start_address =
|
||||
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment);
|
||||
const VirtualMemoryAddress end_address =
|
||||
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment);
|
||||
|
||||
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();
|
||||
|
||||
updateScrollBars();
|
||||
viewport()->update();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebuggerCodeView::scrollToAddress(VirtualMemoryAddress address, bool center)
|
||||
{
|
||||
ensureAddressVisible(address);
|
||||
|
||||
VirtualMemoryAddress old_top = m_top_address;
|
||||
|
||||
if (center)
|
||||
{
|
||||
const int visible_rows = getVisibleRowCount();
|
||||
const int target_row = getRowForAddress(address);
|
||||
const int top_row = std::max(0, target_row - visible_rows / 2);
|
||||
m_top_address = getAddressForRow(top_row);
|
||||
}
|
||||
else
|
||||
{
|
||||
const VirtualMemoryAddress first_visible = getFirstVisibleAddress();
|
||||
const VirtualMemoryAddress last_visible = getLastVisibleAddress();
|
||||
|
||||
if (address < first_visible || address > last_visible)
|
||||
{
|
||||
m_top_address = address;
|
||||
}
|
||||
}
|
||||
|
||||
// Only recalculate arrows if scroll position actually changed
|
||||
if (m_top_address != old_top)
|
||||
{
|
||||
calculateBranchArrows();
|
||||
}
|
||||
|
||||
updateScrollBars();
|
||||
updateVisibleRange();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
std::optional<VirtualMemoryAddress> DebuggerCodeView::getSelectedAddress() const
|
||||
{
|
||||
if (!m_has_selection)
|
||||
return std::nullopt;
|
||||
return m_selected_address;
|
||||
}
|
||||
|
||||
void DebuggerCodeView::setSelectedAddress(VirtualMemoryAddress address)
|
||||
{
|
||||
m_selected_address = address;
|
||||
m_has_selection = true;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
VirtualMemoryAddress DebuggerCodeView::getAddressAtPoint(const QPoint& point) const
|
||||
{
|
||||
const int top_row = getRowForAddress(m_top_address);
|
||||
const int clicked_row = top_row + (point.y() / m_row_height);
|
||||
return getAddressForRow(clicked_row);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(viewport());
|
||||
painter.setFont(font());
|
||||
|
||||
const QRect visible_rect = event->rect();
|
||||
|
||||
// Calculate which rows are visible based on scroll position
|
||||
const int top_row = getRowForAddress(m_top_address);
|
||||
const int first_visible_row = top_row + (visible_rect.top() / m_row_height);
|
||||
const int last_visible_row = top_row + ((visible_rect.bottom() + m_row_height - 1) / m_row_height);
|
||||
|
||||
painter.fillRect(visible_rect, palette().base());
|
||||
|
||||
for (int row = first_visible_row; row <= last_visible_row; ++row)
|
||||
{
|
||||
const VirtualMemoryAddress address = getAddressForRow(row);
|
||||
|
||||
// Calculate y position relative to scroll position
|
||||
const int y = (row - top_row) * m_row_height;
|
||||
|
||||
if (y + m_row_height < visible_rect.top() || y > visible_rect.bottom())
|
||||
continue;
|
||||
|
||||
const bool is_selected = (m_has_selection && address == m_selected_address);
|
||||
const bool is_pc = (address == m_last_pc);
|
||||
|
||||
drawInstruction(painter, address, y, is_selected, is_pc);
|
||||
}
|
||||
|
||||
drawBranchArrows(painter, visible_rect);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected,
|
||||
bool is_pc)
|
||||
{
|
||||
const bool has_breakpoint = hasBreakpointAtAddress(address);
|
||||
|
||||
// Draw background
|
||||
if (has_breakpoint || is_pc || is_selected)
|
||||
{
|
||||
const QRect row_rect(0, y, viewport()->width(), m_row_height);
|
||||
|
||||
QColor bg_color;
|
||||
if (has_breakpoint)
|
||||
bg_color = QColor(171, 97, 107);
|
||||
else if (is_pc)
|
||||
bg_color = QColor(100, 100, 0);
|
||||
else if (is_selected)
|
||||
bg_color = palette().highlight().color();
|
||||
else
|
||||
bg_color = palette().base().color();
|
||||
|
||||
painter.fillRect(row_rect, bg_color);
|
||||
}
|
||||
|
||||
// Set text color
|
||||
QColor address_color;
|
||||
QColor bytes_color;
|
||||
QColor instruction_color;
|
||||
QColor register_color;
|
||||
QColor immediate_color;
|
||||
QColor comment_color;
|
||||
if (is_pc || has_breakpoint)
|
||||
{
|
||||
address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color = Qt::white;
|
||||
}
|
||||
else if (is_selected)
|
||||
{
|
||||
address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color =
|
||||
palette().highlightedText().color();
|
||||
}
|
||||
else
|
||||
{
|
||||
instruction_color = palette().text().color();
|
||||
bytes_color = bytes_color.darker(200);
|
||||
address_color = instruction_color.darker(230);
|
||||
register_color = QColor(0, 150, 255); // Blue for registers
|
||||
immediate_color = QColor(255, 150, 0); // Orange for immediates
|
||||
comment_color = QColor(150, 150, 150); // Gray for comments
|
||||
}
|
||||
|
||||
// Start from the left edge of the arrow column
|
||||
int x = ARROW_COLUMN_WIDTH + 2;
|
||||
|
||||
// Draw breakpoint/PC icon
|
||||
if (is_pc)
|
||||
{
|
||||
const int icon_y = y + 2 + (m_row_height - m_pc_pixmap.height()) / 2;
|
||||
painter.drawPixmap(x, icon_y, m_pc_pixmap);
|
||||
}
|
||||
else if (has_breakpoint)
|
||||
{
|
||||
const int icon_y = y + 2 + (m_row_height - m_breakpoint_pixmap.height()) / 2;
|
||||
painter.drawPixmap(x, icon_y, m_breakpoint_pixmap);
|
||||
}
|
||||
x += BREAKPOINT_COLUMN_WIDTH;
|
||||
|
||||
// Draw address
|
||||
const QFontMetrics font_metrics = painter.fontMetrics();
|
||||
y += font_metrics.ascent() + 1;
|
||||
|
||||
painter.setPen(address_color);
|
||||
const QString address_text = QString::asprintf("0x%08X", address);
|
||||
painter.drawText(x, y, address_text);
|
||||
x += ADDRESS_COLUMN_WIDTH;
|
||||
|
||||
// Draw instruction bytes
|
||||
if (u32 instruction_bits; CPU::SafeReadInstruction(address, &instruction_bits))
|
||||
{
|
||||
const QString bytes_text = QString::asprintf("%08X", instruction_bits);
|
||||
painter.setPen(address_color);
|
||||
painter.drawText(x, y, bytes_text);
|
||||
x += BYTES_COLUMN_WIDTH;
|
||||
|
||||
SmallString str;
|
||||
CPU::DisassembleInstruction(&str, address, instruction_bits);
|
||||
const QString disasm_text = QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
|
||||
painter.setPen(instruction_color);
|
||||
|
||||
// Highlight registers and immediates in the disassembly
|
||||
qsizetype start_pos = 0;
|
||||
while (start_pos >= 0)
|
||||
{
|
||||
qsizetype end_pos = disasm_text.indexOf(' ', start_pos);
|
||||
if (end_pos > 0)
|
||||
end_pos++; // Include the space in the highlight
|
||||
|
||||
const QString token = disasm_text.mid(start_pos, end_pos - start_pos);
|
||||
QColor color;
|
||||
if (start_pos == 0)
|
||||
color = instruction_color; // Instruction mnemonic
|
||||
else if (token[0].isDigit() || (token.length() > 1 && token[0] == '-' && token[1].isDigit()))
|
||||
color = immediate_color; // Immediate value
|
||||
else
|
||||
color = register_color; // Register
|
||||
|
||||
painter.setPen(color);
|
||||
painter.drawText(x, y, token);
|
||||
x += font_metrics.horizontalAdvance(token);
|
||||
|
||||
start_pos = end_pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
painter.setPen(instruction_color);
|
||||
painter.drawText(x, y, QStringLiteral("<invalid>"));
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerCodeView::calculateBranchArrows()
|
||||
{
|
||||
m_branch_arrows.clear();
|
||||
|
||||
const VirtualMemoryAddress first_visible = getFirstVisibleAddress();
|
||||
const VirtualMemoryAddress last_visible = getLastVisibleAddress();
|
||||
|
||||
// Expand search range to include arrows that might be partially visible
|
||||
const VirtualMemoryAddress search_start =
|
||||
(first_visible > 64 * CPU::INSTRUCTION_SIZE) ? first_visible - (64 * CPU::INSTRUCTION_SIZE) : m_code_region_start;
|
||||
const VirtualMemoryAddress search_end = std::min(last_visible + (64 * CPU::INSTRUCTION_SIZE), m_code_region_end);
|
||||
|
||||
for (VirtualMemoryAddress addr = search_start; addr < search_end; addr += CPU::INSTRUCTION_SIZE)
|
||||
{
|
||||
u32 instruction_bits;
|
||||
if (!CPU::SafeReadInstruction(addr, &instruction_bits))
|
||||
continue;
|
||||
|
||||
const CPU::Instruction instruction{instruction_bits};
|
||||
|
||||
if (CPU::IsDirectBranchInstruction(instruction) && !CPU::IsCallInstruction(instruction))
|
||||
{
|
||||
const VirtualMemoryAddress target = CPU::GetDirectBranchTarget(instruction, addr);
|
||||
|
||||
// Only include arrows where at least one end (source or target) is in the current region
|
||||
if ((addr >= search_start && addr < search_end) && (target >= search_start && target < search_end))
|
||||
{
|
||||
BranchArrow arrow;
|
||||
arrow.source = addr;
|
||||
arrow.target = target;
|
||||
arrow.is_conditional = !CPU::IsUnconditionalBranchInstruction(instruction);
|
||||
arrow.is_forward = (target > addr);
|
||||
m_branch_arrows.push_back(arrow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerCodeView::drawBranchArrows(QPainter& painter, const QRect& visible_rect)
|
||||
{
|
||||
if (m_branch_arrows.empty())
|
||||
return;
|
||||
|
||||
const int top_row = getRowForAddress(m_top_address);
|
||||
const int arrow_left = 2;
|
||||
const int arrow_right = ARROW_COLUMN_WIDTH - 2;
|
||||
const int viewport_height = viewport()->height();
|
||||
|
||||
static constexpr const QColor colors[] = {
|
||||
QColor(0, 150, 255), QColor(255, 0, 150), QColor(0, 255, 0), QColor(255, 255, 0),
|
||||
QColor(150, 0, 255), QColor(150, 255, 255), QColor(0, 255, 0), QColor(0, 150, 255),
|
||||
};
|
||||
|
||||
for (const BranchArrow& arrow : m_branch_arrows)
|
||||
{
|
||||
const int source_row = getRowForAddress(arrow.source);
|
||||
const int target_row = getRowForAddress(arrow.target);
|
||||
|
||||
// Calculate y positions relative to scroll position
|
||||
const int source_y = (source_row - top_row) * m_row_height + m_row_height / 2;
|
||||
const int target_y = (target_row - top_row) * m_row_height + m_row_height / 2;
|
||||
|
||||
// Only draw if at least part of the arrow is visible
|
||||
const int min_y = std::min(source_y, target_y);
|
||||
const int max_y = std::max(source_y, target_y);
|
||||
|
||||
// Skip if completely outside viewport
|
||||
if (max_y < 0 || min_y > viewport_height)
|
||||
continue;
|
||||
|
||||
// Clamp to viewport bounds for drawing
|
||||
const int clamped_source_y = std::clamp(source_y, 0, viewport_height);
|
||||
const int clamped_target_y = std::clamp(target_y, 0, viewport_height);
|
||||
|
||||
// Choose color based on arrow type
|
||||
QColor arrow_color = colors[source_row % std::size(colors)];
|
||||
painter.setPen(QPen(arrow_color, 1)); // Extra thick for debugging
|
||||
|
||||
int nest_level = 8 - (source_row % 8); // Adjust nesting level based on row
|
||||
|
||||
#if 0
|
||||
for (const BranchArrow& other : m_branch_arrows)
|
||||
{
|
||||
if (&other == &arrow) // Skip self-comparison
|
||||
break;
|
||||
|
||||
const int other_source_row = getRowForAddress(other.source);
|
||||
const int other_target_row = getRowForAddress(other.target);
|
||||
if ((other_source_row < top_row || other_source_row >= end_row) &&
|
||||
(other_target_row < top_row || other_target_row >= end_row))
|
||||
{
|
||||
continue; // Skip arrows outside the visible range
|
||||
}
|
||||
|
||||
const int min_row = std::min(source_row, target_row);
|
||||
const int max_row = std::max(source_row, target_row);
|
||||
const int other_min_row = std::min(other_source_row, other_target_row);
|
||||
const int other_max_row = std::max(other_source_row, other_target_row);
|
||||
|
||||
// Check if ranges overlap
|
||||
if (!(max_row < other_min_row || min_row > other_max_row))
|
||||
{
|
||||
nest_level = std::max(nest_level - 1, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const int arrow_x = arrow_left + (nest_level * 4);
|
||||
|
||||
// Draw debug circles at source and target positions
|
||||
painter.setBrush(arrow_color);
|
||||
painter.drawEllipse(arrow_right - 1, source_y - 1, 3, 3);
|
||||
|
||||
// Draw straight line arrow with right angles
|
||||
if (source_y == target_y)
|
||||
{
|
||||
// Horizontal line for same-row branches
|
||||
painter.drawLine(arrow_x, source_y, arrow_x + 15, target_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Horizontal line from left edge to source
|
||||
if (source_y >= 0 && source_y < viewport_height)
|
||||
painter.drawLine(arrow_x, source_y, arrow_right, source_y);
|
||||
|
||||
// Vertical line from source to target
|
||||
painter.drawLine(arrow_x, clamped_source_y, arrow_x, clamped_target_y);
|
||||
|
||||
// Horizontal line to target
|
||||
if (target_y >= 0 && target_y < viewport_height)
|
||||
painter.drawLine(arrow_x, target_y, arrow_right, target_y);
|
||||
|
||||
// Draw arrowhead with simple lines
|
||||
const int arrow_size = 3;
|
||||
const bool pointing_down = (target_y > source_y);
|
||||
|
||||
if (pointing_down)
|
||||
{
|
||||
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size);
|
||||
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size);
|
||||
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerCodeView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
|
||||
setSelectedAddress(address);
|
||||
}
|
||||
|
||||
QAbstractScrollArea::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton)
|
||||
{
|
||||
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
|
||||
setSelectedAddress(address);
|
||||
}
|
||||
QAbstractScrollArea::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
|
||||
|
||||
const int x = event->pos().x();
|
||||
|
||||
if ((x >= BREAKPOINT_COLUMN_START && x < ADDRESS_COLUMN_START) ||
|
||||
(x >= INSTRUCTION_COLUMN_START && x < COMMENT_COLUMN_START))
|
||||
{
|
||||
emit toggleBreakpointActivated(address);
|
||||
}
|
||||
else if (x >= ADDRESS_COLUMN_START && x < INSTRUCTION_COLUMN_START)
|
||||
{
|
||||
emit addressActivated(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
emit commentActivated(address);
|
||||
}
|
||||
}
|
||||
|
||||
QAbstractScrollArea::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::contextMenuEvent(QContextMenuEvent* event)
|
||||
{
|
||||
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
|
||||
emit contextMenuRequested(event->pos(), address);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (!m_has_selection)
|
||||
{
|
||||
QAbstractScrollArea::keyPressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualMemoryAddress new_address = m_selected_address;
|
||||
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
new_address -= CPU::INSTRUCTION_SIZE;
|
||||
break;
|
||||
case Qt::Key_Down:
|
||||
new_address += CPU::INSTRUCTION_SIZE;
|
||||
break;
|
||||
case Qt::Key_PageUp:
|
||||
new_address -= CPU::INSTRUCTION_SIZE * getVisibleRowCount();
|
||||
break;
|
||||
case Qt::Key_PageDown:
|
||||
new_address += CPU::INSTRUCTION_SIZE * getVisibleRowCount();
|
||||
break;
|
||||
default:
|
||||
QAbstractScrollArea::keyPressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
scrollToAddress(new_address, false);
|
||||
setSelectedAddress(new_address);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
const int delta = event->angleDelta().y();
|
||||
const int steps = delta / 120; // Standard wheel step
|
||||
|
||||
VirtualMemoryAddress old_top = m_top_address;
|
||||
VirtualMemoryAddress new_top = m_top_address - (steps * CPU::INSTRUCTION_SIZE * 3);
|
||||
|
||||
// Clamp to valid range
|
||||
new_top = std::clamp(new_top, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE);
|
||||
m_top_address = new_top;
|
||||
|
||||
// Only recalculate arrows if scroll position actually changed
|
||||
if (m_top_address != old_top)
|
||||
{
|
||||
calculateBranchArrows();
|
||||
}
|
||||
|
||||
updateScrollBars();
|
||||
updateVisibleRange();
|
||||
viewport()->update();
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void DebuggerCodeView::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QAbstractScrollArea::resizeEvent(event);
|
||||
updateScrollBars();
|
||||
updateVisibleRange();
|
||||
// Recalculate arrows on resize since visible range changes
|
||||
calculateBranchArrows();
|
||||
}
|
||||
|
||||
void DebuggerCodeView::updateScrollBars()
|
||||
{
|
||||
const int total_rows = static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE);
|
||||
const int visible_rows = getVisibleRowCount();
|
||||
const int max_value = std::max(0, total_rows - visible_rows);
|
||||
|
||||
verticalScrollBar()->setRange(0, max_value);
|
||||
verticalScrollBar()->setPageStep(visible_rows);
|
||||
|
||||
const int current_row = getRowForAddress(m_top_address);
|
||||
verticalScrollBar()->setValue(current_row);
|
||||
}
|
||||
|
||||
void DebuggerCodeView::updateVisibleRange()
|
||||
{
|
||||
m_visible_rows = getVisibleRowCount();
|
||||
}
|
||||
|
||||
int DebuggerCodeView::getVisibleRowCount() const
|
||||
{
|
||||
return viewport()->height() / m_row_height;
|
||||
}
|
||||
|
||||
VirtualMemoryAddress DebuggerCodeView::getFirstVisibleAddress() const
|
||||
{
|
||||
return m_top_address;
|
||||
}
|
||||
|
||||
VirtualMemoryAddress DebuggerCodeView::getLastVisibleAddress() const
|
||||
{
|
||||
const int visible_rows = getVisibleRowCount();
|
||||
return m_top_address + (visible_rows * CPU::INSTRUCTION_SIZE);
|
||||
}
|
||||
123
src/duckstation-qt/debuggercodeview.h
Normal file
123
src/duckstation-qt/debuggercodeview.h
Normal file
@@ -0,0 +1,123 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/bus.h"
|
||||
#include "core/cpu_types.h"
|
||||
#include "core/types.h"
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
#include <QtGui/QPixmap>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QAbstractScrollArea>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class DebuggerCodeView : public QAbstractScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DebuggerCodeView(QWidget* parent = nullptr);
|
||||
~DebuggerCodeView();
|
||||
|
||||
// Call when font or theme changes
|
||||
void updateRowHeight();
|
||||
|
||||
void scrollToAddress(VirtualMemoryAddress address, bool center = false);
|
||||
std::optional<VirtualMemoryAddress> getSelectedAddress() const;
|
||||
void setSelectedAddress(VirtualMemoryAddress address);
|
||||
VirtualMemoryAddress getAddressAtPoint(const QPoint& point) const;
|
||||
|
||||
// Code model functionality integrated
|
||||
void resetCodeView(VirtualMemoryAddress start_address);
|
||||
void setPC(VirtualMemoryAddress pc);
|
||||
void ensureAddressVisible(VirtualMemoryAddress address);
|
||||
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
|
||||
void clearBreakpoints();
|
||||
VirtualMemoryAddress getPC() const { return m_last_pc; }
|
||||
bool hasBreakpointAtAddress(VirtualMemoryAddress address) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void toggleBreakpointActivated(VirtualMemoryAddress address);
|
||||
void addressActivated(VirtualMemoryAddress address);
|
||||
void commentActivated(VirtualMemoryAddress address);
|
||||
void contextMenuRequested(const QPoint& point, VirtualMemoryAddress address);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void contextMenuEvent(QContextMenuEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
// Column positions
|
||||
static constexpr int ARROW_COLUMN_WIDTH = 60;
|
||||
static constexpr int BREAKPOINT_COLUMN_WIDTH = 20;
|
||||
static constexpr int ADDRESS_COLUMN_WIDTH = 100;
|
||||
static constexpr int BYTES_COLUMN_WIDTH = 90;
|
||||
static constexpr int INSTRUCTION_COLUMN_WIDTH = 250;
|
||||
|
||||
static constexpr int BREAKPOINT_COLUMN_START = ARROW_COLUMN_WIDTH;
|
||||
static constexpr int ADDRESS_COLUMN_START = BREAKPOINT_COLUMN_START + BREAKPOINT_COLUMN_WIDTH;
|
||||
static constexpr int BYTES_COLUMN_START = ADDRESS_COLUMN_START + ADDRESS_COLUMN_WIDTH;
|
||||
static constexpr int INSTRUCTION_COLUMN_START = BYTES_COLUMN_START + BYTES_COLUMN_WIDTH;
|
||||
static constexpr int COMMENT_COLUMN_START = INSTRUCTION_COLUMN_START + INSTRUCTION_COLUMN_WIDTH;
|
||||
|
||||
struct BranchArrow
|
||||
{
|
||||
VirtualMemoryAddress source;
|
||||
VirtualMemoryAddress target;
|
||||
bool is_conditional;
|
||||
bool is_forward;
|
||||
};
|
||||
|
||||
// Address/row conversion methods
|
||||
int getRowForAddress(VirtualMemoryAddress address) const;
|
||||
VirtualMemoryAddress getAddressForRow(int row) const;
|
||||
|
||||
// Memory region management
|
||||
bool updateRegion(VirtualMemoryAddress address);
|
||||
|
||||
// Drawing methods
|
||||
void updateScrollBars();
|
||||
void updateVisibleRange();
|
||||
void calculateBranchArrows();
|
||||
void drawBranchArrows(QPainter& painter, const QRect& visible_rect);
|
||||
void drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected, bool is_pc);
|
||||
|
||||
int getVisibleRowCount() const;
|
||||
VirtualMemoryAddress getFirstVisibleAddress() const;
|
||||
VirtualMemoryAddress getLastVisibleAddress() const;
|
||||
|
||||
int m_row_height = 1;
|
||||
int m_char_width = 0;
|
||||
|
||||
VirtualMemoryAddress m_selected_address = 0;
|
||||
bool m_has_selection = false;
|
||||
|
||||
std::vector<BranchArrow> m_branch_arrows;
|
||||
|
||||
QPixmap m_pc_pixmap;
|
||||
QPixmap m_breakpoint_pixmap;
|
||||
|
||||
// Scroll state
|
||||
VirtualMemoryAddress m_top_address = 0;
|
||||
int m_visible_rows = 0;
|
||||
|
||||
// Code region state (from DebuggerCodeModel)
|
||||
Bus::MemoryRegion m_current_code_region = Bus::MemoryRegion::Count;
|
||||
CPU::Segment m_current_segment = CPU::Segment::KUSEG;
|
||||
VirtualMemoryAddress m_code_region_start = 0;
|
||||
VirtualMemoryAddress m_code_region_end = 0;
|
||||
VirtualMemoryAddress m_last_pc = 0;
|
||||
std::vector<VirtualMemoryAddress> m_breakpoints;
|
||||
};
|
||||
@@ -18,285 +18,9 @@
|
||||
|
||||
#include "moc_debuggermodels.cpp"
|
||||
|
||||
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(12);
|
||||
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(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.c_str(), static_cast<int>(str.length()));
|
||||
}
|
||||
|
||||
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);
|
||||
return QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -15,47 +15,6 @@
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <map>
|
||||
|
||||
class DebuggerCodeModel final : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DebuggerCodeModel(QObject* parent = nullptr);
|
||||
~DebuggerCodeModel() override;
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Returns the row for this instruction pointer
|
||||
void resetCodeView(VirtualMemoryAddress start_address);
|
||||
int getRowForAddress(VirtualMemoryAddress address) const;
|
||||
int getRowForPC() const;
|
||||
VirtualMemoryAddress getAddressForRow(int row) const;
|
||||
VirtualMemoryAddress getAddressForIndex(QModelIndex index) const;
|
||||
void setPC(VirtualMemoryAddress pc);
|
||||
void ensureAddressVisible(VirtualMemoryAddress address);
|
||||
void setBreakpointList(std::vector<VirtualMemoryAddress> bps);
|
||||
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
|
||||
void clearBreakpoints();
|
||||
|
||||
private:
|
||||
bool updateRegion(VirtualMemoryAddress address);
|
||||
bool emitDataChangedForAddress(VirtualMemoryAddress address);
|
||||
bool hasBreakpointAtAddress(VirtualMemoryAddress address) const;
|
||||
|
||||
Bus::MemoryRegion m_current_code_region = Bus::MemoryRegion::Count;
|
||||
CPU::Segment m_current_segment = CPU::Segment::KUSEG;
|
||||
VirtualMemoryAddress m_code_region_start = 0;
|
||||
VirtualMemoryAddress m_code_region_end = 0;
|
||||
VirtualMemoryAddress m_last_pc = 0;
|
||||
std::vector<VirtualMemoryAddress> m_breakpoints;
|
||||
|
||||
QPixmap m_pc_pixmap;
|
||||
QPixmap m_breakpoint_pixmap;
|
||||
};
|
||||
|
||||
class DebuggerRegistersModel final : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -83,7 +83,7 @@ void DebuggerWindow::refreshAll()
|
||||
m_stack_model->invalidateView();
|
||||
m_ui.memoryView->forceRefresh();
|
||||
|
||||
m_code_model->setPC(CPU::g_state.pc);
|
||||
m_ui.codeView->setPC(CPU::g_state.pc);
|
||||
scrollToPC(false);
|
||||
}
|
||||
|
||||
@@ -94,25 +94,8 @@ void DebuggerWindow::scrollToPC(bool center)
|
||||
|
||||
void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address, bool center)
|
||||
{
|
||||
m_code_model->ensureAddressVisible(address);
|
||||
|
||||
const int row = m_code_model->getRowForAddress(address);
|
||||
if (row >= 0)
|
||||
{
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
const QModelIndex index = m_code_model->index(row, 0);
|
||||
const QRect rect = m_ui.codeView->visualRect(index);
|
||||
if (rect.left() < 0 || rect.top() < 0 || rect.right() > m_ui.codeView->viewport()->width() ||
|
||||
rect.bottom() > m_ui.codeView->viewport()->height())
|
||||
{
|
||||
center = true;
|
||||
}
|
||||
|
||||
m_ui.codeView->scrollTo(index, center ? QAbstractItemView::PositionAtCenter : QAbstractItemView::EnsureVisible);
|
||||
m_ui.codeView->selectionModel()->setCurrentIndex(index,
|
||||
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
}
|
||||
m_ui.codeView->scrollToAddress(address, center);
|
||||
m_ui.codeView->setSelectedAddress(address);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onPauseActionToggled(bool paused)
|
||||
@@ -128,7 +111,7 @@ void DebuggerWindow::onPauseActionToggled(bool paused)
|
||||
|
||||
void DebuggerWindow::onRunToCursorTriggered()
|
||||
{
|
||||
std::optional<VirtualMemoryAddress> addr = getSelectedCodeAddress();
|
||||
std::optional<VirtualMemoryAddress> addr = m_ui.codeView->getSelectedAddress();
|
||||
if (!addr.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, windowTitle(), tr("No address selected."));
|
||||
@@ -180,11 +163,6 @@ void DebuggerWindow::onTraceTriggered()
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::onFollowAddressTriggered()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void DebuggerWindow::onAddBreakpointTriggered()
|
||||
{
|
||||
DebuggerAddBreakpointDialog dlg(this);
|
||||
@@ -196,7 +174,7 @@ void DebuggerWindow::onAddBreakpointTriggered()
|
||||
|
||||
void DebuggerWindow::onToggleBreakpointTriggered()
|
||||
{
|
||||
std::optional<VirtualMemoryAddress> address = getSelectedCodeAddress();
|
||||
std::optional<VirtualMemoryAddress> address = m_ui.codeView->getSelectedAddress();
|
||||
if (!address.has_value())
|
||||
return;
|
||||
|
||||
@@ -281,37 +259,26 @@ void DebuggerWindow::onStepOutActionTriggered()
|
||||
g_emu_thread->setSystemPaused(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onCodeViewItemActivated(QModelIndex index)
|
||||
void DebuggerWindow::onCodeViewAddressActivated(VirtualMemoryAddress address)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
scrollToMemoryAddress(address);
|
||||
}
|
||||
|
||||
const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index);
|
||||
switch (index.column())
|
||||
{
|
||||
case 0: // breakpoint
|
||||
case 3: // disassembly
|
||||
toggleBreakpoint(address);
|
||||
break;
|
||||
void DebuggerWindow::onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address)
|
||||
{
|
||||
toggleBreakpoint(address);
|
||||
}
|
||||
|
||||
case 1: // address
|
||||
case 2: // bytes
|
||||
scrollToMemoryAddress(address);
|
||||
break;
|
||||
|
||||
case 4: // comment
|
||||
tryFollowLoadStore(address);
|
||||
break;
|
||||
}
|
||||
void DebuggerWindow::onCodeViewCommentActivated(VirtualMemoryAddress address)
|
||||
{
|
||||
if (!tryFollowLoadStore(address))
|
||||
toggleBreakpoint(address);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt)
|
||||
{
|
||||
const QModelIndex index = m_ui.codeView->indexAt(pt);
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index);
|
||||
const VirtualMemoryAddress address = m_ui.codeView->getAddressAtPoint(pt);
|
||||
m_ui.codeView->setSelectedAddress(address);
|
||||
|
||||
QMenu menu;
|
||||
menu.addAction(QStringLiteral("0x%1").arg(static_cast<uint>(address), 8, 16, QChar('0')))->setEnabled(false);
|
||||
@@ -484,6 +451,7 @@ void DebuggerWindow::setupAdditionalUi()
|
||||
#endif
|
||||
#endif
|
||||
m_ui.codeView->setFont(fixedFont);
|
||||
m_ui.codeView->updateRowHeight();
|
||||
m_ui.registerView->setFont(fixedFont);
|
||||
m_ui.memoryView->setFont(fixedFont);
|
||||
m_ui.stackView->setFont(fixedFont);
|
||||
@@ -518,8 +486,11 @@ void DebuggerWindow::connectSignals()
|
||||
connect(m_ui.actionToggleBreakpoint, &QAction::triggered, this, &DebuggerWindow::onToggleBreakpointTriggered);
|
||||
connect(m_ui.actionClearBreakpoints, &QAction::triggered, this, &DebuggerWindow::onClearBreakpointsTriggered);
|
||||
connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close);
|
||||
connect(m_ui.codeView, &QTreeView::activated, this, &DebuggerWindow::onCodeViewItemActivated);
|
||||
connect(m_ui.codeView, &QTreeView::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested);
|
||||
connect(m_ui.codeView, &DebuggerCodeView::addressActivated, this, &DebuggerWindow::onCodeViewAddressActivated);
|
||||
connect(m_ui.codeView, &DebuggerCodeView::toggleBreakpointActivated, this,
|
||||
&DebuggerWindow::onCodeViewToggleBreakpointActivated);
|
||||
connect(m_ui.codeView, &DebuggerCodeView::commentActivated, this, &DebuggerWindow::onCodeViewCommentActivated);
|
||||
connect(m_ui.codeView, &QWidget::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested);
|
||||
connect(m_ui.breakpointsWidget, &QTreeWidget::customContextMenuRequested, this,
|
||||
&DebuggerWindow::onBreakpointListContextMenuRequested);
|
||||
connect(m_ui.breakpointsWidget, &QTreeWidget::itemChanged, this, &DebuggerWindow::onBreakpointListItemChanged);
|
||||
@@ -545,22 +516,12 @@ void DebuggerWindow::disconnectSignals()
|
||||
|
||||
void DebuggerWindow::createModels()
|
||||
{
|
||||
m_code_model = std::make_unique<DebuggerCodeModel>();
|
||||
m_ui.codeView->setModel(m_code_model.get());
|
||||
|
||||
// set default column width in code view
|
||||
m_ui.codeView->setColumnWidth(0, 40);
|
||||
m_ui.codeView->setColumnWidth(1, 100);
|
||||
m_ui.codeView->setColumnWidth(2, 90);
|
||||
m_ui.codeView->setColumnWidth(3, 250);
|
||||
m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 100 + 90 + 250));
|
||||
|
||||
m_registers_model = std::make_unique<DebuggerRegistersModel>();
|
||||
m_ui.registerView->setModel(m_registers_model.get());
|
||||
m_registers_model = new DebuggerRegistersModel(this);
|
||||
m_ui.registerView->setModel(m_registers_model);
|
||||
// m_ui->registerView->resizeRowsToContents();
|
||||
|
||||
m_stack_model = std::make_unique<DebuggerStackModel>();
|
||||
m_ui.stackView->setModel(m_stack_model.get());
|
||||
m_stack_model = new DebuggerStackModel(this);
|
||||
m_ui.stackView->setModel(m_stack_model);
|
||||
|
||||
m_ui.breakpointsWidget->setColumnWidth(0, 50);
|
||||
m_ui.breakpointsWidget->setColumnWidth(1, 80);
|
||||
@@ -670,7 +631,7 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address)
|
||||
}
|
||||
|
||||
Host::RunOnUIThread([this, address, new_bp_state, bps = CPU::CopyBreakpointList()]() {
|
||||
m_code_model->setBreakpointState(address, new_bp_state);
|
||||
m_ui.codeView->setBreakpointState(address, new_bp_state);
|
||||
refreshBreakpointList(bps);
|
||||
});
|
||||
});
|
||||
@@ -678,20 +639,10 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address)
|
||||
|
||||
void DebuggerWindow::clearBreakpoints()
|
||||
{
|
||||
m_code_model->clearBreakpoints();
|
||||
m_ui.codeView->clearBreakpoints();
|
||||
Host::RunOnCPUThread(&CPU::ClearBreakpoints);
|
||||
}
|
||||
|
||||
std::optional<VirtualMemoryAddress> DebuggerWindow::getSelectedCodeAddress()
|
||||
{
|
||||
QItemSelectionModel* sel_model = m_ui.codeView->selectionModel();
|
||||
const QModelIndexList indices(sel_model->selectedIndexes());
|
||||
if (indices.empty())
|
||||
return std::nullopt;
|
||||
|
||||
return m_code_model->getAddressForIndex(indices[0]);
|
||||
}
|
||||
|
||||
bool DebuggerWindow::tryFollowLoadStore(VirtualMemoryAddress address)
|
||||
{
|
||||
CPU::Instruction inst;
|
||||
@@ -760,7 +711,7 @@ void DebuggerWindow::addBreakpoint(CPU::BreakpointType type, u32 address)
|
||||
}
|
||||
|
||||
if (type == CPU::BreakpointType::Execute)
|
||||
m_code_model->setBreakpointState(address, true);
|
||||
m_ui.codeView->setBreakpointState(address, true);
|
||||
|
||||
refreshBreakpointList(bps);
|
||||
});
|
||||
@@ -779,7 +730,7 @@ void DebuggerWindow::removeBreakpoint(CPU::BreakpointType type, u32 address)
|
||||
}
|
||||
|
||||
if (type == CPU::BreakpointType::Execute)
|
||||
m_code_model->setBreakpointState(address, false);
|
||||
m_ui.codeView->setBreakpointState(address, false);
|
||||
|
||||
refreshBreakpointList(bps);
|
||||
});
|
||||
|
||||
@@ -10,14 +10,12 @@
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Bus {
|
||||
enum class MemoryRegion;
|
||||
}
|
||||
|
||||
class DebuggerCodeModel;
|
||||
class DebuggerRegistersModel;
|
||||
class DebuggerStackModel;
|
||||
|
||||
@@ -50,7 +48,6 @@ private Q_SLOTS:
|
||||
void onGoToPCTriggered();
|
||||
void onGoToAddressTriggered();
|
||||
void onDumpAddressTriggered();
|
||||
void onFollowAddressTriggered();
|
||||
void onTraceTriggered();
|
||||
void onAddBreakpointTriggered();
|
||||
void onToggleBreakpointTriggered();
|
||||
@@ -60,7 +57,9 @@ private Q_SLOTS:
|
||||
void onStepIntoActionTriggered();
|
||||
void onStepOverActionTriggered();
|
||||
void onStepOutActionTriggered();
|
||||
void onCodeViewItemActivated(QModelIndex index);
|
||||
void onCodeViewAddressActivated(VirtualMemoryAddress address);
|
||||
void onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address);
|
||||
void onCodeViewCommentActivated(VirtualMemoryAddress address);
|
||||
void onCodeViewContextMenuRequested(const QPoint& pt);
|
||||
void onMemorySearchTriggered();
|
||||
void onMemorySearchStringChanged(const QString&);
|
||||
@@ -75,7 +74,6 @@ private:
|
||||
void setMemoryViewRegion(Bus::MemoryRegion region);
|
||||
void toggleBreakpoint(VirtualMemoryAddress address);
|
||||
void clearBreakpoints();
|
||||
std::optional<VirtualMemoryAddress> getSelectedCodeAddress();
|
||||
bool tryFollowLoadStore(VirtualMemoryAddress address);
|
||||
void scrollToPC(bool center);
|
||||
void scrollToCodeAddress(VirtualMemoryAddress address, bool center);
|
||||
@@ -87,9 +85,8 @@ private:
|
||||
|
||||
Ui::DebuggerWindow m_ui;
|
||||
|
||||
std::unique_ptr<DebuggerCodeModel> m_code_model;
|
||||
std::unique_ptr<DebuggerRegistersModel> m_registers_model;
|
||||
std::unique_ptr<DebuggerStackModel> m_stack_model;
|
||||
DebuggerRegistersModel* m_registers_model;
|
||||
DebuggerStackModel* m_stack_model;
|
||||
|
||||
QTimer m_refresh_timer;
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>4</number>
|
||||
</attribute>
|
||||
<widget class="QTreeView" name="codeView">
|
||||
<widget class="DebuggerCodeView" name="codeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -481,6 +481,12 @@
|
||||
<header>duckstation-qt/memoryviewwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DebuggerCodeView</class>
|
||||
<extends>QAbstractScrollArea</extends>
|
||||
<header>duckstation-qt/debuggercodeview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ClCompile Include="controllerglobalsettingswidget.cpp" />
|
||||
<ClCompile Include="controllersettingswindow.cpp" />
|
||||
<ClCompile Include="coverdownloadwindow.cpp" />
|
||||
<ClCompile Include="debuggercodeview.cpp" />
|
||||
<ClCompile Include="emulationsettingswidget.cpp" />
|
||||
<ClCompile Include="debuggermodels.cpp" />
|
||||
<ClCompile Include="debuggerwindow.cpp" />
|
||||
@@ -84,6 +85,7 @@
|
||||
<QtMoc Include="gamecheatsettingswidget.h" />
|
||||
<QtMoc Include="gamepatchsettingswidget.h" />
|
||||
<QtMoc Include="isobrowserwindow.h" />
|
||||
<QtMoc Include="debuggercodeview.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<QtMoc Include="selectdiscdialog.h" />
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<ClCompile Include="gamecheatsettingswidget.cpp" />
|
||||
<ClCompile Include="gamepatchsettingswidget.cpp" />
|
||||
<ClCompile Include="isobrowserwindow.cpp" />
|
||||
<ClCompile Include="debuggercodeview.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="qtutils.h" />
|
||||
@@ -107,6 +108,7 @@
|
||||
<QtMoc Include="gamecheatsettingswidget.h" />
|
||||
<QtMoc Include="gamepatchsettingswidget.h" />
|
||||
<QtMoc Include="isobrowserwindow.h" />
|
||||
<QtMoc Include="debuggercodeview.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUi Include="consolesettingswidget.ui" />
|
||||
|
||||
Reference in New Issue
Block a user