Qt: Add multi-instruction selection to debugger

And the ability to copy code to the clipboard.
pull/3799/head
Stenzek 1 week ago
parent b5f0fb9c36
commit c81a747ff8
No known key found for this signature in database

@ -15,6 +15,7 @@
#include <QtCore/QTimer>
#include <QtGui/QFontDatabase>
#include <QtGui/QKeySequence>
#include <QtGui/QMouseEvent>
#include <QtGui/QPainter>
#include <QtGui/QPainterPath>
@ -231,13 +232,35 @@ std::optional<VirtualMemoryAddress> DebuggerCodeView::getSelectedAddress() const
return m_selected_address;
}
std::optional<std::pair<VirtualMemoryAddress, VirtualMemoryAddress>> DebuggerCodeView::getSelectedAddressRange() const
{
if (!m_has_selection)
return std::nullopt;
return std::make_pair(std::min(m_selection_anchor_address, m_selected_address),
std::max(m_selection_anchor_address, m_selected_address));
}
bool DebuggerCodeView::isAddressSelected(VirtualMemoryAddress address) const
{
const std::optional<std::pair<VirtualMemoryAddress, VirtualMemoryAddress>> range = getSelectedAddressRange();
return range.has_value() && address >= range->first && address <= range->second;
}
void DebuggerCodeView::setSelectedAddress(VirtualMemoryAddress address)
{
m_selected_address = address;
m_selection_anchor_address = address;
m_has_selection = true;
viewport()->update();
}
void DebuggerCodeView::setSelectionEndAddress(VirtualMemoryAddress address)
{
m_selected_address = address;
viewport()->update();
}
VirtualMemoryAddress DebuggerCodeView::getAddressAtPoint(const QPoint& point) const
{
const int top_row = getRowForAddress(m_top_address);
@ -269,7 +292,7 @@ void DebuggerCodeView::paintEvent(QPaintEvent* event)
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_selected = isAddressSelected(address);
const bool is_pc = (address == m_last_pc);
drawInstruction(painter, address, y, is_selected, is_pc);
@ -581,7 +604,10 @@ void DebuggerCodeView::mousePressEvent(QMouseEvent* event)
if (event->button() == Qt::LeftButton)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
setSelectedAddress(address);
if (m_has_selection && (event->modifiers() & Qt::ShiftModifier))
setSelectionEndAddress(address);
else
setSelectedAddress(address);
}
QAbstractScrollArea::mousePressEvent(event);
@ -592,7 +618,7 @@ void DebuggerCodeView::mouseMoveEvent(QMouseEvent* event)
if (event->buttons() & Qt::LeftButton)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
setSelectedAddress(address);
setSelectionEndAddress(address);
}
QAbstractScrollArea::mouseMoveEvent(event);
}
@ -631,35 +657,49 @@ void DebuggerCodeView::contextMenuEvent(QContextMenuEvent* event)
void DebuggerCodeView::keyPressEvent(QKeyEvent* event)
{
if (m_has_selection && event->matches(QKeySequence::Copy))
{
emit copyActivated();
event->accept();
return;
}
if (!m_has_selection)
{
QAbstractScrollArea::keyPressEvent(event);
return;
}
VirtualMemoryAddress new_address = m_selected_address;
int row_delta;
switch (event->key())
{
case Qt::Key_Up:
new_address -= CPU::INSTRUCTION_SIZE;
row_delta = -1;
break;
case Qt::Key_Down:
new_address += CPU::INSTRUCTION_SIZE;
row_delta = 1;
break;
case Qt::Key_PageUp:
new_address -= CPU::INSTRUCTION_SIZE * getVisibleRowCount();
row_delta = -getVisibleRowCount();
break;
case Qt::Key_PageDown:
new_address += CPU::INSTRUCTION_SIZE * getVisibleRowCount();
row_delta = getVisibleRowCount();
break;
default:
QAbstractScrollArea::keyPressEvent(event);
return;
}
const int total_rows = static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE);
const int new_row = std::clamp(getRowForAddress(m_selected_address) + row_delta, 0, total_rows - 1);
const VirtualMemoryAddress new_address = getAddressForRow(new_row);
scrollToAddress(new_address, false);
setSelectedAddress(new_address);
if (event->modifiers() & Qt::ShiftModifier)
setSelectionEndAddress(new_address);
else
setSelectedAddress(new_address);
event->accept();
}
void DebuggerCodeView::wheelEvent(QWheelEvent* event)

@ -15,6 +15,7 @@
#include <memory>
#include <optional>
#include <utility>
#include <vector>
class DebuggerCodeView : public QAbstractScrollArea
@ -30,6 +31,8 @@ public:
void scrollToAddress(VirtualMemoryAddress address, bool center = false);
std::optional<VirtualMemoryAddress> getSelectedAddress() const;
std::optional<std::pair<VirtualMemoryAddress, VirtualMemoryAddress>> getSelectedAddressRange() const;
bool isAddressSelected(VirtualMemoryAddress address) const;
void setSelectedAddress(VirtualMemoryAddress address);
VirtualMemoryAddress getAddressAtPoint(const QPoint& point) const;
@ -48,6 +51,7 @@ Q_SIGNALS:
void toggleBreakpointActivated(VirtualMemoryAddress address);
void addressActivated(VirtualMemoryAddress address);
void commentActivated(VirtualMemoryAddress address);
void copyActivated();
void contextMenuRequested(const QPoint& point, VirtualMemoryAddress address);
protected:
@ -99,11 +103,13 @@ private:
int getVisibleRowCount() const;
VirtualMemoryAddress getFirstVisibleAddress() const;
VirtualMemoryAddress getLastVisibleAddress() const;
void setSelectionEndAddress(VirtualMemoryAddress address);
int m_row_height = 1;
int m_char_width = 0;
VirtualMemoryAddress m_selected_address = 0;
VirtualMemoryAddress m_selection_anchor_address = 0;
bool m_has_selection = false;
std::vector<BranchArrow> m_branch_arrows;

@ -23,6 +23,7 @@
#include "common/string_util.h"
#include "util/ini_settings_interface.h"
#include "util/translation.h"
#include <fmt/format.h>
@ -340,7 +341,11 @@ void DebuggerWindow::onCodeViewCommentActivated(VirtualMemoryAddress address)
void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt)
{
const VirtualMemoryAddress address = m_ui.codeView->getAddressAtPoint(pt);
m_ui.codeView->setSelectedAddress(address);
if (!m_ui.codeView->isAddressSelected(address))
m_ui.codeView->setSelectedAddress(address);
const auto [selection_start, selection_end] = m_ui.codeView->getSelectedAddressRange().value();
const u32 selection_count = ((selection_end - selection_start) / CPU::INSTRUCTION_SIZE) + 1;
QMenu* const menu = QtUtils::NewPopupMenu(this);
menu->addAction(QStringLiteral("0x%1").arg(static_cast<uint>(address), 8, 16, QChar('0')))->setEnabled(false);
@ -364,11 +369,17 @@ void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt)
[this, address]() { startPatchInstruction(address); });
patch_action->setEnabled(can_patch);
QAction* const nop_action = menu->addAction(QIcon(u":/icons/monochrome/svg/trash-fill.svg"_s), tr("&Nop Instruction"),
[this, address]() { patchInstruction(address, 0); });
QAction* const nop_action =
menu->addAction(QIcon(u":/icons/monochrome/svg/trash-fill.svg"_s),
tr("&Nop %n Instruction(s)", nullptr, static_cast<int>(selection_count)),
[this, selection_start, selection_end]() { patchInstructions(selection_start, selection_end, 0); });
nop_action->setEnabled(can_patch);
menu->addSeparator();
menu->addAction(QIcon(u":/icons/monochrome/svg/file-copy-line.svg"_s),
tr("&Copy %n Instruction(s)", nullptr, static_cast<int>(selection_count)), this,
&DebuggerWindow::copySelectedCodeToClipboard);
menu->addAction(QIcon(u":/icons/monochrome/svg/debugger-go-to-address.svg"_s), tr("View in &Dump"),
[this, address]() { scrollToMemoryAddress(address); });
@ -437,7 +448,7 @@ void DebuggerWindow::startPatchInstruction(VirtualMemoryAddress address)
if (CPU::AssembleInstruction(&replacement_bits, address, std::string_view(text_utf8.constData(), text_utf8.size()),
&error))
{
patchInstruction(address, replacement_bits);
patchInstructions(address, address, replacement_bits);
return;
}
@ -445,32 +456,104 @@ void DebuggerWindow::startPatchInstruction(VirtualMemoryAddress address)
}
}
void DebuggerWindow::patchInstruction(VirtualMemoryAddress address, u32 bits)
void DebuggerWindow::patchInstructions(VirtualMemoryAddress start_address, VirtualMemoryAddress end_address, u32 bits)
{
Host::RunOnCoreThread([win = QPointer(this), address, bits]() mutable {
const bool success = CPU::SafeWriteMemoryWord(address, bits);
if (success)
CPU::InvalidateICacheAt(address);
Host::RunOnCoreThread([win = QPointer(this), start_address, end_address, bits]() mutable {
u32 success_count = 0;
std::optional<VirtualMemoryAddress> first_failed_address;
for (VirtualMemoryAddress address = start_address;; address += CPU::INSTRUCTION_SIZE)
{
if (CPU::SafeWriteMemoryWord(address, bits))
{
CPU::InvalidateICacheAt(address);
success_count++;
}
else if (!first_failed_address.has_value())
{
first_failed_address = address;
}
if (address == end_address)
break;
}
Host::RunOnUIThread([win = std::move(win), address, success]() {
const u32 instruction_count = ((end_address - start_address) / CPU::INSTRUCTION_SIZE) + 1;
Host::RunOnUIThread([win = std::move(win), start_address, success_count, instruction_count,
first_failed_address]() {
if (!win)
return;
if (!success)
if (success_count > 0)
{
win->m_ui.codeView->refreshView();
win->m_ui.memoryView->forceRefresh();
}
if (first_failed_address.has_value())
{
QtUtils::AsyncMessageBox(
win, QMessageBox::Critical, win->windowTitle(),
tr("Failed to write patched instruction to 0x%1.").arg(static_cast<uint>(address), 8, 16, QChar('0')));
return;
tr("Failed to write one or more patched instructions. Patched %1 of %2 instructions; the first failure was "
"at 0x%3.")
.arg(success_count)
.arg(instruction_count)
.arg(static_cast<uint>(first_failed_address.value()), 8, 16, QChar('0')));
win->reportMessage(tr("Patched %1 of %2 selected instructions.").arg(success_count).arg(instruction_count));
}
else if (instruction_count == 1)
{
win->reportMessage(tr("Patched instruction at 0x%1.").arg(static_cast<uint>(start_address), 8, 16, QChar('0')));
}
else
{
win->reportMessage(tr("Patched %1 instructions.").arg(instruction_count));
}
win->m_ui.codeView->refreshView();
win->m_ui.memoryView->forceRefresh();
win->reportMessage(tr("Patched instruction at 0x%1.").arg(static_cast<uint>(address), 8, 16, QChar('0')));
});
});
}
void DebuggerWindow::copySelectedCodeToClipboard()
{
const std::optional<std::pair<VirtualMemoryAddress, VirtualMemoryAddress>> range =
m_ui.codeView->getSelectedAddressRange();
if (!range.has_value())
return;
Host::RunOnCoreThread([range = range.value()]() {
if (!System::IsValid())
return;
SmallString text;
u32 instruction_count = 0;
for (VirtualMemoryAddress address = range.first;; address += CPU::INSTRUCTION_SIZE)
{
if (instruction_count > 0)
text.append('\n');
text.append_format("0x{:08X} ", address);
if (u32 instruction_bits; CPU::SafeReadInstruction(address, &instruction_bits))
{
SmallString disassembly;
CPU::DisassembleInstruction(&disassembly, address, instruction_bits);
text.append(disassembly);
}
else
{
text.append("<invalid>");
}
instruction_count++;
if (address == range.second)
break;
}
Host::CopyTextToClipboard(text);
Host::ReportDebuggerEvent(CPU::DebuggerEvent::Message,
TRANSLATE_PLURAL_SSTR("DebuggerWindow", "Copied %n instruction(s) to the clipboard.",
"CopyRange", static_cast<int>(instruction_count)));
});
}
void DebuggerWindow::onMemorySearchTriggered()
{
m_ui.memoryView->clearHighlightRange();
@ -651,6 +734,7 @@ void DebuggerWindow::connectSignals()
connect(m_ui.codeView, &DebuggerCodeView::toggleBreakpointActivated, this,
&DebuggerWindow::onCodeViewToggleBreakpointActivated);
connect(m_ui.codeView, &DebuggerCodeView::commentActivated, this, &DebuggerWindow::onCodeViewCommentActivated);
connect(m_ui.codeView, &DebuggerCodeView::copyActivated, this, &DebuggerWindow::copySelectedCodeToClipboard);
connect(m_ui.codeView, &QWidget::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested);
connect(m_ui.callStackView, &QTreeView::doubleClicked, this, &DebuggerWindow::onCallStackItemDoubleClicked);
connect(m_ui.stackView, &QTreeView::doubleClicked, this, &DebuggerWindow::onStackItemDoubleClicked);
@ -1040,7 +1124,7 @@ void DebuggerWindow::loadGameSettings(bool clear_existing)
}
if (!queued_bps.empty())
reportMessage(tr("Loaded %n saved breakpoint(s).", nullptr, queued_bps.size()));
reportMessage(tr("Loaded %n saved breakpoint(s).", nullptr, static_cast<int>(queued_bps.size())));
Host::RunOnCoreThread([win = QPointer<DebuggerWindow>(this), serial = m_game_serial, clear_existing,
queued_bps = std::move(queued_bps)]() mutable {

@ -49,7 +49,8 @@ private:
void saveCurrentState();
void setMemoryViewRegion(Bus::MemoryRegion region);
void startPatchInstruction(VirtualMemoryAddress address);
void patchInstruction(VirtualMemoryAddress address, u32 bits);
void patchInstructions(VirtualMemoryAddress start_address, VirtualMemoryAddress end_address, u32 bits);
void copySelectedCodeToClipboard();
void toggleBreakpoint(VirtualMemoryAddress address);
void clearBreakpoints();
bool tryFollowLoadStore(VirtualMemoryAddress address);

@ -103,6 +103,7 @@
<file>icons/monochrome/svg/export-line.svg</file>
<file>icons/monochrome/svg/file-add-line.svg</file>
<file>icons/monochrome/svg/file-check-line.svg</file>
<file>icons/monochrome/svg/file-copy-line.svg</file>
<file>icons/monochrome/svg/file-forbid-line.svg</file>
<file>icons/monochrome/svg/file-line.svg</file>
<file>icons/monochrome/svg/file-list-line.svg</file>

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6.9998 6V3C6.9998 2.44772 7.44752 2 7.9998 2H19.9998C20.5521 2 20.9998 2.44772 20.9998 3V17C20.9998 17.5523 20.5521 18 19.9998 18H16.9998V20.9991C16.9998 21.5519 16.5499 22 15.993 22H4.00666C3.45059 22 3 21.5554 3 20.9991L3.0026 7.00087C3.0027 6.44811 3.45264 6 4.00942 6H6.9998ZM5.00242 8L5.00019 20H14.9998V8H5.00242ZM8.9998 6H16.9998V16H18.9998V4H8.9998V6Z"></path></svg>

After

Width:  |  Height:  |  Size: 464 B

Loading…
Cancel
Save