From 90e9b602875e8066ef41a6f6ef4eaef12c04f069 Mon Sep 17 00:00:00 2001 From: Dan McCarthy Date: Sun, 19 Nov 2023 08:41:27 -0600 Subject: [PATCH] Debugger: Implement subsequent/next scan for Mem Search (#10260) Co-authored-by: Ty --- pcsx2-qt/Debugger/CpuWidget.cpp | 230 +++++++++++++++++++++----------- pcsx2-qt/Debugger/CpuWidget.h | 7 + pcsx2-qt/Debugger/CpuWidget.ui | 26 ++-- 3 files changed, 178 insertions(+), 85 deletions(-) diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp index d9d569c12a..2157561817 100644 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ b/pcsx2-qt/Debugger/CpuWidget.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "demangler/demangler.h" @@ -101,7 +102,9 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) connect(m_ui.txtFuncSearch, &QLineEdit::textChanged, [this] { updateFunctionList(); }); connect(m_ui.btnSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked); - connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { m_ui.memoryviewWidget->gotoAddress(item->data(256).toUInt()); }); + connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked); + connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { m_ui.memoryviewWidget->gotoAddress(item->text().toUInt(nullptr, 16)); }); + connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &CpuWidget::onSearchResultsListScroll); connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) { if (i < 4) m_ui.chkSearchHex->setEnabled(true); @@ -122,6 +125,11 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) m_ui.listFunctions->setVisible(false); } this->repaint(); + + // Ensures we don't retrigger the load results function unintentionally + m_resultsLoadTimer.setInterval(100); + m_resultsLoadTimer.setSingleShot(true); + connect(&m_resultsLoadTimer, &QTimer::timeout, this, &CpuWidget::loadSearchResults); } CpuWidget::~CpuWidget() = default; @@ -816,114 +824,145 @@ void CpuWidget::onStackListDoubleClick(const QModelIndex& index) } template -static std::vector searchWorker(DebugInterface* cpu, u32 start, u32 end, T value) +static bool checkAddressValueMatches(DebugInterface* cpu, u32 addr, T value) { - std::vector hitAddresses; - for (u32 addr = start; addr < end; addr += sizeof(T)) + T val = 0; + switch (sizeof(T)) { - T val = 0; - switch (sizeof(T)) + case sizeof(u8): + val = cpu->read8(addr); + break; + case sizeof(u16): + val = cpu->read16(addr); + break; + case sizeof(u32): { - case sizeof(u8): - val = cpu->read8(addr); - break; - case sizeof(u16): - val = cpu->read16(addr); - break; - case sizeof(u32): + if (std::is_same_v) { - if (std::is_same_v) - { - const float fTop = value + 0.00001f; - const float fBottom = value - 0.00001f; - const float memValue = std::bit_cast(cpu->read32(addr)); - if (fBottom < memValue && memValue < fTop) - { - hitAddresses.emplace_back(addr); - } - continue; - } - - val = cpu->read32(addr); - break; - } - case sizeof(u64): - { - if (std::is_same_v) - { - const double dTop = value + 0.00001f; - const double dBottom = value - 0.00001f; - const double memValue = std::bit_cast(cpu->read64(addr)); - if (dBottom < memValue && memValue < dTop) - { - hitAddresses.emplace_back(addr); - } - continue; - } - - val = cpu->read64(addr); - break; + const float fTop = value + 0.00001f; + const float fBottom = value - 0.00001f; + const float memValue = std::bit_cast(cpu->read32(addr)); + return (fBottom < memValue && memValue < fTop); } - default: - Console.Error("Debugger: Unknown type when doing memory search!"); - return hitAddresses; - break; + val = cpu->read32(addr); + break; + } + case sizeof(u64): + { + if (std::is_same_v) + { + const double dTop = value + 0.00001f; + const double dBottom = value - 0.00001f; + const double memValue = std::bit_cast(cpu->read64(addr)); + return (dBottom < memValue && memValue < dTop); + } + + val = cpu->read64(addr); + break; } - if (val == value) + default: + Console.Error("Debugger: Unknown type when doing memory search!"); + return false; + break; + } + + return val == value; +} + +template +static std::vector searchWorker(DebugInterface* cpu, std::vector searchAddresses, u32 start, u32 end, T value) +{ + std::vector hitAddresses; + const bool isSearchingRange = searchAddresses.size() <= 0; + if (isSearchingRange) + { + for (u32 addr = start; addr < end; addr += sizeof(T)) { - hitAddresses.push_back(addr); + if (checkAddressValueMatches(cpu, addr, value)) + { + hitAddresses.push_back(addr); + } + } + } + else + { + for (const u32 addr : searchAddresses) + { + if (checkAddressValueMatches(cpu, addr, value)) + { + hitAddresses.push_back(addr); + } + } } return hitAddresses; } -static std::vector searchWorkerByteArray(DebugInterface* cpu, u32 start, u32 end, QByteArray value) +static bool compareByteArrayAtAddress(DebugInterface* cpu, u32 addr, QByteArray value) { - std::vector hitAddresses; - for (u32 addr = start; addr < end; addr += 1) + for (qsizetype i = 0; i < value.length(); i++) { - bool hit = true; - for (qsizetype i = 0; i < value.length(); i++) + if (static_cast(cpu->read8(addr + i)) != value[i]) { - if (static_cast(cpu->read8(addr + i)) != value[i]) + return false; + } + } + return true; +} + +static std::vector searchWorkerByteArray(DebugInterface* cpu, std::vector searchAddresses, u32 start, u32 end, QByteArray value) +{ + + std::vector hitAddresses; + const bool isSearchingRange = searchAddresses.size() <= 0; + if (isSearchingRange) + { + for (u32 addr = start; addr < end; addr += 1) + { + if (compareByteArrayAtAddress(cpu, addr, value)) { - hit = false; - break; + hitAddresses.emplace_back(addr); + addr += value.length() - 1; } } - if (hit) + } + else + { + for (u32 addr : searchAddresses) { - hitAddresses.emplace_back(addr); - addr += value.length() - 1; + if (compareByteArrayAtAddress(cpu, addr, value)) + { + hitAddresses.emplace_back(addr); + } } } return hitAddresses; } -std::vector startWorker(DebugInterface* cpu, int type, u32 start, u32 end, QString value, int base) +std::vector startWorker(DebugInterface* cpu, int type, std::vector searchAddresses, u32 start, u32 end, QString value, int base) { const bool isSigned = value.startsWith("-"); switch (type) { case 0: - return isSigned ? searchWorker(cpu, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, start, end, value.toUShort(nullptr, base)); + return isSigned ? searchWorker(cpu, searchAddresses, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, searchAddresses, start, end, value.toUShort(nullptr, base)); case 1: - return isSigned ? searchWorker(cpu, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, start, end, value.toUShort(nullptr, base)); + return isSigned ? searchWorker(cpu, searchAddresses, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, searchAddresses, start, end, value.toUShort(nullptr, base)); case 2: - return isSigned ? searchWorker(cpu, start, end, value.toInt(nullptr, base)) : searchWorker(cpu, start, end, value.toUInt(nullptr, base)); + return isSigned ? searchWorker(cpu, searchAddresses, start, end, value.toInt(nullptr, base)) : searchWorker(cpu, searchAddresses, start, end, value.toUInt(nullptr, base)); case 3: - return isSigned ? searchWorker(cpu, start, end, value.toLong(nullptr, base)) : searchWorker(cpu, start, end, value.toULongLong(nullptr, base)); + return isSigned ? searchWorker(cpu, searchAddresses, start, end, value.toLong(nullptr, base)) : searchWorker(cpu, searchAddresses, start, end, value.toULongLong(nullptr, base)); case 4: - return searchWorker(cpu, start, end, value.toFloat()); + return searchWorker(cpu, searchAddresses, start, end, value.toFloat()); case 5: - return searchWorker(cpu, start, end, value.toDouble()); + return searchWorker(cpu, searchAddresses, start, end, value.toDouble()); case 6: - return searchWorkerByteArray(cpu, start, end, value.toUtf8()); + return searchWorkerByteArray(cpu, searchAddresses, start, end, value.toUtf8()); case 7: - return searchWorkerByteArray(cpu, start, end, QByteArray::fromHex(value.toUtf8())); + return searchWorkerByteArray(cpu, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8())); default: Console.Error("Debugger: Unknown type when doing memory search!"); break; @@ -1024,16 +1063,53 @@ void CpuWidget::onSearchButtonClicked() m_ui.listSearchResults->clear(); const auto& results = workerWatcher->future().result(); - for (const auto& address : results) - { - QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16)); - item->setData(256, address); - m_ui.listSearchResults->addItem(item); - } + m_searchResults = results; + loadSearchResults(); + m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0); + }); m_ui.btnSearch->setDisabled(true); + QPushButton* senderButton = qobject_cast(sender()); + bool isFilterSearch = senderButton == m_ui.btnFilterSearch; + std::vector addresses; + if (isFilterSearch) + { + addresses = m_searchResults; + } QFuture> workerFuture = - QtConcurrent::run(startWorker, &m_cpu, searchType, searchStart, searchEnd, searchValue, searchHex ? 16 : 10); + QtConcurrent::run(startWorker, &m_cpu, searchType, addresses, searchStart, searchEnd, searchValue, searchHex ? 16 : 10); workerWatcher->setFuture(workerFuture); } + +void CpuWidget::onSearchResultsListScroll(u32 value) +{ + bool hasResultsToLoad = static_cast(m_ui.listSearchResults->count()) < m_searchResults.size(); + bool scrolledSufficiently = value > (m_ui.listSearchResults->verticalScrollBar()->maximum() * 0.95); + + if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently) + { + // Load results once timer ends, allowing us to debounce repeated requests and only do one load. + m_resultsLoadTimer.start(); + } +} + +void CpuWidget::loadSearchResults() { + const u32 numLoaded = m_ui.listSearchResults->count(); + const u32 amountLeftToLoad = m_searchResults.size() - numLoaded; + if (amountLeftToLoad < 1) + return; + + const bool isFirstLoad = numLoaded == 0; + const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad; + const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad; + + for (u32 i = 0; i < numToLoad; i++) + { + u32 address = m_searchResults.at(numLoaded + i); + QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16)); + item->setData(256, address); + m_ui.listSearchResults->addItem(item); + } +} + diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h index 22d54ad6bb..bf3f09f9c6 100644 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ b/pcsx2-qt/Debugger/CpuWidget.h @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -94,9 +95,12 @@ public slots: }; void onSearchButtonClicked(); + void onSearchResultsListScroll(u32 value); + void loadSearchResults(); private: std::vector m_registerTableViews; + std::vector m_searchResults; QMenu* m_stacklistContextMenu = 0; QMenu* m_funclistContextMenu = 0; @@ -110,7 +114,10 @@ private: ThreadModel m_threadModel; QSortFilterProxyModel m_threadProxyModel; StackModel m_stackModel; + QTimer m_resultsLoadTimer; bool m_demangleFunctions = true; bool m_moduleView = true; + u32 m_initialResultsLoadLimit = 20000; + u32 m_numResultsAddedPerLoad = 10000; }; diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui index b04725c671..d4c94bb84c 100644 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ b/pcsx2-qt/Debugger/CpuWidget.ui @@ -183,14 +183,14 @@ - + End - + 0x00 @@ -207,28 +207,28 @@ - + Start - + 0x2000000 - + Type - + @@ -272,14 +272,14 @@ - + Hex - + @@ -296,6 +296,16 @@ + + + + false + + + Filter Search + + +