Debugger: Implement subsequent/next scan for Mem Search (#10260)

Co-authored-by: Ty <AmFobes@gmail.com>
This commit is contained in:
Dan McCarthy 2023-11-19 08:41:27 -06:00 committed by GitHub
parent 9a6e5458c9
commit 90e9b60287
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 85 deletions

View File

@ -32,6 +32,7 @@
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
#include <QtCore/QFutureWatcher> #include <QtCore/QFutureWatcher>
#include <QtWidgets/QScrollBar>
#include "demangler/demangler.h" #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.txtFuncSearch, &QLineEdit::textChanged, [this] { updateFunctionList(); });
connect(m_ui.btnSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked); 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) { connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) {
if (i < 4) if (i < 4)
m_ui.chkSearchHex->setEnabled(true); m_ui.chkSearchHex->setEnabled(true);
@ -122,6 +125,11 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
m_ui.listFunctions->setVisible(false); m_ui.listFunctions->setVisible(false);
} }
this->repaint(); 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; CpuWidget::~CpuWidget() = default;
@ -816,114 +824,145 @@ void CpuWidget::onStackListDoubleClick(const QModelIndex& index)
} }
template <typename T> template <typename T>
static std::vector<u32> searchWorker(DebugInterface* cpu, u32 start, u32 end, T value) static bool checkAddressValueMatches(DebugInterface* cpu, u32 addr, T value)
{ {
std::vector<u32> hitAddresses; T val = 0;
for (u32 addr = start; addr < end; addr += sizeof(T)) switch (sizeof(T))
{ {
T val = 0; case sizeof(u8):
switch (sizeof(T)) val = cpu->read8(addr);
break;
case sizeof(u16):
val = cpu->read16(addr);
break;
case sizeof(u32):
{ {
case sizeof(u8): if (std::is_same_v<T, float>)
val = cpu->read8(addr);
break;
case sizeof(u16):
val = cpu->read16(addr);
break;
case sizeof(u32):
{ {
if (std::is_same_v<T, float>) const float fTop = value + 0.00001f;
{ const float fBottom = value - 0.00001f;
const float fTop = value + 0.00001f; const float memValue = std::bit_cast<float, u32>(cpu->read32(addr));
const float fBottom = value - 0.00001f; return (fBottom < memValue && memValue < fTop);
const float memValue = std::bit_cast<float, u32>(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<T, double>)
{
const double dTop = value + 0.00001f;
const double dBottom = value - 0.00001f;
const double memValue = std::bit_cast<double, u64>(cpu->read64(addr));
if (dBottom < memValue && memValue < dTop)
{
hitAddresses.emplace_back(addr);
}
continue;
}
val = cpu->read64(addr);
break;
} }
default: val = cpu->read32(addr);
Console.Error("Debugger: Unknown type when doing memory search!"); break;
return hitAddresses; }
break; case sizeof(u64):
{
if (std::is_same_v<T, double>)
{
const double dTop = value + 0.00001f;
const double dBottom = value - 0.00001f;
const double memValue = std::bit_cast<double, u64>(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 <typename T>
static std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddresses, u32 start, u32 end, T value)
{
std::vector<u32> 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; return hitAddresses;
} }
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, u32 start, u32 end, QByteArray value) static bool compareByteArrayAtAddress(DebugInterface* cpu, u32 addr, QByteArray value)
{ {
std::vector<u32> hitAddresses; for (qsizetype i = 0; i < value.length(); i++)
for (u32 addr = start; addr < end; addr += 1)
{ {
bool hit = true; if (static_cast<char>(cpu->read8(addr + i)) != value[i])
for (qsizetype i = 0; i < value.length(); i++)
{ {
if (static_cast<char>(cpu->read8(addr + i)) != value[i]) return false;
}
}
return true;
}
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.size() <= 0;
if (isSearchingRange)
{
for (u32 addr = start; addr < end; addr += 1)
{
if (compareByteArrayAtAddress(cpu, addr, value))
{ {
hit = false; hitAddresses.emplace_back(addr);
break; addr += value.length() - 1;
} }
} }
if (hit) }
else
{
for (u32 addr : searchAddresses)
{ {
hitAddresses.emplace_back(addr); if (compareByteArrayAtAddress(cpu, addr, value))
addr += value.length() - 1; {
hitAddresses.emplace_back(addr);
}
} }
} }
return hitAddresses; return hitAddresses;
} }
std::vector<u32> startWorker(DebugInterface* cpu, int type, u32 start, u32 end, QString value, int base) std::vector<u32> startWorker(DebugInterface* cpu, int type, std::vector<u32> searchAddresses, u32 start, u32 end, QString value, int base)
{ {
const bool isSigned = value.startsWith("-"); const bool isSigned = value.startsWith("-");
switch (type) switch (type)
{ {
case 0: case 0:
return isSigned ? searchWorker<s8>(cpu, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, start, end, value.toUShort(nullptr, base)); return isSigned ? searchWorker<s8>(cpu, searchAddresses, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, searchAddresses, start, end, value.toUShort(nullptr, base));
case 1: case 1:
return isSigned ? searchWorker<s16>(cpu, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, start, end, value.toUShort(nullptr, base)); return isSigned ? searchWorker<s16>(cpu, searchAddresses, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, searchAddresses, start, end, value.toUShort(nullptr, base));
case 2: case 2:
return isSigned ? searchWorker<s32>(cpu, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, start, end, value.toUInt(nullptr, base)); return isSigned ? searchWorker<s32>(cpu, searchAddresses, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchAddresses, start, end, value.toUInt(nullptr, base));
case 3: case 3:
return isSigned ? searchWorker<s64>(cpu, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, start, end, value.toULongLong(nullptr, base)); return isSigned ? searchWorker<s64>(cpu, searchAddresses, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchAddresses, start, end, value.toULongLong(nullptr, base));
case 4: case 4:
return searchWorker<float>(cpu, start, end, value.toFloat()); return searchWorker<float>(cpu, searchAddresses, start, end, value.toFloat());
case 5: case 5:
return searchWorker<double>(cpu, start, end, value.toDouble()); return searchWorker<double>(cpu, searchAddresses, start, end, value.toDouble());
case 6: case 6:
return searchWorkerByteArray(cpu, start, end, value.toUtf8()); return searchWorkerByteArray(cpu, searchAddresses, start, end, value.toUtf8());
case 7: case 7:
return searchWorkerByteArray(cpu, start, end, QByteArray::fromHex(value.toUtf8())); return searchWorkerByteArray(cpu, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8()));
default: default:
Console.Error("Debugger: Unknown type when doing memory search!"); Console.Error("Debugger: Unknown type when doing memory search!");
break; break;
@ -1024,16 +1063,53 @@ void CpuWidget::onSearchButtonClicked()
m_ui.listSearchResults->clear(); m_ui.listSearchResults->clear();
const auto& results = workerWatcher->future().result(); const auto& results = workerWatcher->future().result();
for (const auto& address : results) m_searchResults = results;
{ loadSearchResults();
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16)); m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0);
item->setData(256, address);
m_ui.listSearchResults->addItem(item);
}
}); });
m_ui.btnSearch->setDisabled(true); m_ui.btnSearch->setDisabled(true);
QPushButton* senderButton = qobject_cast<QPushButton*>(sender());
bool isFilterSearch = senderButton == m_ui.btnFilterSearch;
std::vector<u32> addresses;
if (isFilterSearch)
{
addresses = m_searchResults;
}
QFuture<std::vector<u32>> workerFuture = QFuture<std::vector<u32>> 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); workerWatcher->setFuture(workerFuture);
} }
void CpuWidget::onSearchResultsListScroll(u32 value)
{
bool hasResultsToLoad = static_cast<size_t>(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);
}
}

View File

@ -30,6 +30,7 @@
#include <QtWidgets/QWidget> #include <QtWidgets/QWidget>
#include <QtWidgets/QTableWidget> #include <QtWidgets/QTableWidget>
#include <QtCore/QSortFilterProxyModel> #include <QtCore/QSortFilterProxyModel>
#include <QtCore/QTimer>
#include <vector> #include <vector>
@ -94,9 +95,12 @@ public slots:
}; };
void onSearchButtonClicked(); void onSearchButtonClicked();
void onSearchResultsListScroll(u32 value);
void loadSearchResults();
private: private:
std::vector<QTableWidget*> m_registerTableViews; std::vector<QTableWidget*> m_registerTableViews;
std::vector<u32> m_searchResults;
QMenu* m_stacklistContextMenu = 0; QMenu* m_stacklistContextMenu = 0;
QMenu* m_funclistContextMenu = 0; QMenu* m_funclistContextMenu = 0;
@ -110,7 +114,10 @@ private:
ThreadModel m_threadModel; ThreadModel m_threadModel;
QSortFilterProxyModel m_threadProxyModel; QSortFilterProxyModel m_threadProxyModel;
StackModel m_stackModel; StackModel m_stackModel;
QTimer m_resultsLoadTimer;
bool m_demangleFunctions = true; bool m_demangleFunctions = true;
bool m_moduleView = true; bool m_moduleView = true;
u32 m_initialResultsLoadLimit = 20000;
u32 m_numResultsAddedPerLoad = 10000;
}; };

View File

@ -183,14 +183,14 @@
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="5" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>End</string> <string>End</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" colspan="3"> <item row="5" column="1" colspan="3">
<widget class="QLineEdit" name="txtSearchStart"> <widget class="QLineEdit" name="txtSearchStart">
<property name="text"> <property name="text">
<string notr="true">0x00</string> <string notr="true">0x00</string>
@ -207,28 +207,28 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="txtSearchValue"/> <widget class="QLineEdit" name="txtSearchValue"/>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Start</string> <string>Start</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" colspan="3"> <item row="6" column="1" colspan="3">
<widget class="QLineEdit" name="txtSearchEnd"> <widget class="QLineEdit" name="txtSearchEnd">
<property name="text"> <property name="text">
<string notr="true">0x2000000</string> <string notr="true">0x2000000</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Type</string> <string>Type</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="cmbSearchType"> <widget class="QComboBox" name="cmbSearchType">
<item> <item>
<property name="text"> <property name="text">
@ -272,14 +272,14 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="2" column="2">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Hex</string> <string>Hex</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="3"> <item row="2" column="3">
<widget class="QCheckBox" name="chkSearchHex"> <widget class="QCheckBox" name="chkSearchHex">
<property name="text"> <property name="text">
<string/> <string/>
@ -296,6 +296,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2" colspan="2">
<widget class="QPushButton" name="btnFilterSearch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Filter Search</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>