Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugger: Implement subsequent searches/next scan for Memory Search #10260

Merged
merged 11 commits into from
Nov 19, 2023
Merged
224 changes: 150 additions & 74 deletions pcsx2-qt/Debugger/CpuWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <QtCore/QFutureWatcher>
#include <QtWidgets/QScrollBar>

#include "demangler/demangler.h"

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -816,114 +824,145 @@ void CpuWidget::onStackListDoubleClick(const QModelIndex& index)
}

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;
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<T, float>)
{
if (std::is_same_v<T, float>)
{
const float fTop = value + 0.00001f;
const float fBottom = value - 0.00001f;
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;
const float fTop = value + 0.00001f;
const float fBottom = value - 0.00001f;
const float memValue = std::bit_cast<float, u32>(cpu->read32(addr));
return (fBottom < memValue && memValue < fTop);
}
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;
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));
return (dBottom < memValue && memValue < dTop);
}

default:
Console.Error("Debugger: Unknown type when doing memory search!");
return hitAddresses;
break;
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;
}

static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, u32 start, u32 end, QByteArray value)
static bool compareByteArrayAtAddress(DebugInterface* cpu, u32 addr, QByteArray value)
{
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;
for (u32 addr = start; addr < end; addr += 1)
const bool isSearchingRange = searchAddresses.size() <= 0;
if (isSearchingRange)
{
bool hit = true;
for (qsizetype i = 0; i < value.length(); i++)
for (u32 addr = start; addr < end; addr += 1)
{
if (static_cast<char>(cpu->read8(addr + i)) != value[i])
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<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("-");
switch (type)
{
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:
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:
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:
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:
return searchWorker<float>(cpu, start, end, value.toFloat());
return searchWorker<float>(cpu, searchAddresses, start, end, value.toFloat());
case 5:
return searchWorker<double>(cpu, start, end, value.toDouble());
return searchWorker<double>(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;
Expand Down Expand Up @@ -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);
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
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<QPushButton*>(sender());
bool isFilterSearch = senderButton == m_ui.btnFilterSearch;
std::vector<u32> addresses;
if (isFilterSearch)
{
addresses = m_searchResults;
}
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);
}

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);
}
}

7 changes: 7 additions & 0 deletions pcsx2-qt/Debugger/CpuWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <QtWidgets/QWidget>
#include <QtWidgets/QTableWidget>
#include <QtCore/QSortFilterProxyModel>
#include <QtCore/QTimer>

#include <vector>

Expand Down Expand Up @@ -94,9 +95,12 @@ public slots:
};

void onSearchButtonClicked();
void onSearchResultsListScroll(u32 value);
void loadSearchResults();

private:
std::vector<QTableWidget*> m_registerTableViews;
std::vector<u32> m_searchResults;

QMenu* m_stacklistContextMenu = 0;
QMenu* m_funclistContextMenu = 0;
Expand All @@ -110,7 +114,10 @@ public slots:
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;
};
Loading