Debugger: Memory search expansions + results count

Adds memory search comparisons for Increased, Increased By, Decreased, Decreased By, Changed, Not Changed, Changed By.
For arrays, adds not equals, changed, not changed for filter searches.
Now only shows the comparison types that are currently valid for the given search type and if there's prior search results.

Also refactors to allow holding the prior set of search results rather than just the addresses, needed for these search comparisons to work.

Also adds a ui label to show that the debugger is searching after clicking the search button which then gets replaced with the results count when the search completes.
This commit is contained in:
Dan McCarthy 2024-02-11 17:53:12 -06:00 committed by refractionpcsx2
parent 875fdc40a5
commit bd032bbcb8
7 changed files with 375 additions and 105 deletions

View File

@ -33,7 +33,6 @@
using namespace QtUtils; using namespace QtUtils;
using namespace MipsStackWalk; using namespace MipsStackWalk;
CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
: m_cpu(cpu) : m_cpu(cpu)
, m_bpModel(cpu) , m_bpModel(cpu)

View File

@ -33,9 +33,6 @@ public:
CpuWidget(QWidget* parent, DebugInterface& cpu); CpuWidget(QWidget* parent, DebugInterface& cpu);
~CpuWidget(); ~CpuWidget();
// Note: The order of these enum values must reflect the order in thee Search Comparison combobox.
public slots: public slots:
void paintEvent(QPaintEvent* event); void paintEvent(QPaintEvent* event);
@ -92,7 +89,6 @@ public slots:
m_ui.memoryviewWidget->update(); m_ui.memoryviewWidget->update();
}; };
void saveBreakpointsToDebuggerSettings(); void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings(); void saveSavedAddressesToDebuggerSettings();

View File

@ -180,7 +180,7 @@
<attribute name="title"> <attribute name="title">
<string>Memory Search</string> <string>Memory Search</string>
</attribute> </attribute>
<layout class="QHBoxLayout" name="horizontalLayout_6"> <layout class="QHBoxLayout" name="horizontalLayout_10">
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>

View File

@ -410,7 +410,6 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
std::vector<BranchLine> branchLines = m_disassemblyManager.getBranchLines(m_visibleStart, visibleEnd - m_visibleStart); std::vector<BranchLine> branchLines = m_disassemblyManager.getBranchLines(m_visibleStart, visibleEnd - m_visibleStart);
s32 branchCount = 0; s32 branchCount = 0;
s32 skippedBranches = 0;
for (const auto& branchLine : branchLines) for (const auto& branchLine : branchLines)
{ {
if (branchCount == (m_showInstructionOpcode ? 3 : 5)) if (branchCount == (m_showInstructionOpcode ? 3 : 5))
@ -453,12 +452,6 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
bottom = (((branchLine.second - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2); bottom = (((branchLine.second - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2);
} }
if ((top < 0 && bottom < 0) || (top > winBottom && bottom > winBottom) || (top < 0 && bottom > winBottom) || (top > winBottom && bottom < 0))
{
skippedBranches++;
continue;
}
branchCount++; branchCount++;
if (branchLine.first == m_selectedAddressStart || branchLine.second == m_selectedAddressStart) if (branchLine.first == m_selectedAddressStart || branchLine.second == m_selectedAddressStart)

View File

@ -19,6 +19,8 @@
using SearchComparison = MemorySearchWidget::SearchComparison; using SearchComparison = MemorySearchWidget::SearchComparison;
using SearchType = MemorySearchWidget::SearchType; using SearchType = MemorySearchWidget::SearchType;
using SearchResult = MemorySearchWidget::SearchResult;
using SearchResults = QMap<u32, MemorySearchWidget::SearchResult>;
using namespace QtUtils; using namespace QtUtils;
@ -31,19 +33,14 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent)
m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu); m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked);
connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked);
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) // move back to cpu widget connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item)
{ {
emit switchToMemoryViewTab(); emit switchToMemoryViewTab();
emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16)); emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16));
}); });
connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll); connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll);
connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu); connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu);
connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) { connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, this, &MemorySearchWidget::onSearchTypeChanged);
if (i < 4)
m_ui.chkSearchHex->setEnabled(true);
else
m_ui.chkSearchHex->setEnabled(false);
});
// Ensures we don't retrigger the load results function unintentionally // Ensures we don't retrigger the load results function unintentionally
m_resultsLoadTimer.setInterval(100); m_resultsLoadTimer.setInterval(100);
@ -74,10 +71,8 @@ void MemorySearchWidget::contextRemoveSearchResult()
const int selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first()); const int selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first());
const auto* rowToRemove = m_ui.listSearchResults->takeItem(selectedResultIndex); const auto* rowToRemove = m_ui.listSearchResults->takeItem(selectedResultIndex);
if (m_searchResults.size() > static_cast<size_t>(selectedResultIndex) && m_searchResults.at(selectedResultIndex) == rowToRemove->data(Qt::UserRole).toUInt()) u32 address = rowToRemove->data(Qt::UserRole).toUInt();
{ m_searchResultsMap.remove(address);
m_searchResults.erase(m_searchResults.begin() + selectedResultIndex);
}
delete rowToRemove; delete rowToRemove;
} }
@ -123,9 +118,22 @@ void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos)
contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos)); contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
} }
template<typename T>
T readValueAtAddress(DebugInterface* cpu, u32 addr);
template<>
float readValueAtAddress<float>(DebugInterface* cpu, u32 addr)
{
return std::bit_cast<float>(cpu->read32(addr));
}
template<>
double readValueAtAddress<double>(DebugInterface* cpu, u32 addr)
{
return std::bit_cast<double>(cpu->read64(addr));
}
template <typename T> template <typename T>
static T readValueAtAddress(DebugInterface* cpu, u32 addr) T readValueAtAddress(DebugInterface* cpu, u32 addr)
{ {
T val = 0; T val = 0;
switch (sizeof(T)) switch (sizeof(T))
@ -164,15 +172,13 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal
{ {
const T fTop = searchValue + 0.00001f; const T fTop = searchValue + 0.00001f;
const T fBottom = searchValue - 0.00001f; const T fBottom = searchValue - 0.00001f;
const T memValue = std::bit_cast<float, u32>(readValue); areValuesEqual = (fBottom < readValue && readValue < fTop);
areValuesEqual = (fBottom < memValue && memValue < fTop);
} }
else if constexpr (std::is_same_v<T, double>) else if constexpr (std::is_same_v<T, double>)
{ {
const double dTop = searchValue + 0.00001f; const double dTop = searchValue + 0.00001f;
const double dBottom = searchValue - 0.00001f; const double dBottom = searchValue - 0.00001f;
const double memValue = std::bit_cast<double, u64>(readValue); areValuesEqual = (dBottom < readValue && readValue < dTop);
areValuesEqual = (dBottom < memValue && memValue < dTop);
} }
else else
{ {
@ -195,18 +201,16 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal
{ {
const T fTop = searchValue + 0.00001f; const T fTop = searchValue + 0.00001f;
const T fBottom = searchValue - 0.00001f; const T fBottom = searchValue - 0.00001f;
const T memValue = std::bit_cast<float, u32>(readValue); const bool isGreater = readValue > fTop;
const bool isGreater = memValue > fTop; const bool isLesser = readValue < fBottom;
const bool isLesser = memValue < fBottom;
return isGreaterOperator ? isGreater : isLesser; return isGreaterOperator ? isGreater : isLesser;
} }
else if (std::is_same_v<T, double>) else if (std::is_same_v<T, double>)
{ {
const double dTop = searchValue + 0.00001f; const double dTop = searchValue + 0.00001f;
const double dBottom = searchValue - 0.00001f; const double dBottom = searchValue - 0.00001f;
const double memValue = std::bit_cast<double, u64>(readValue); const bool isGreater = readValue > dTop;
const bool isGreater = memValue > dTop; const bool isLesser = readValue < dBottom;
const bool isLesser = memValue < dBottom;
return isGreaterOperator ? isGreater : isLesser; return isGreaterOperator ? isGreater : isLesser;
} }
@ -218,38 +222,104 @@ static bool memoryValueComparator(SearchComparison searchComparison, T searchVal
} }
} }
// Handles the comparison of the read value against either the search value, or if existing searchResults are available, the value at the same address in the searchResultsMap
template <typename T> template <typename T>
std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddresses, SearchComparison searchComparison, u32 start, u32 end, T searchValue) bool handleSearchComparison(SearchComparison searchComparison, u32 searchAddress, SearchResults searchResults, T searchValue, T readValue)
{ {
std::vector<u32> hitAddresses; const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged;
const bool isSearchingRange = searchAddresses.size() <= 0; switch (searchComparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
case SearchComparison::GreaterThan:
case SearchComparison::GreaterThanOrEqual:
case SearchComparison::LessThan:
case SearchComparison::LessThanOrEqual:
{
return memoryValueComparator(searchComparison, searchValue, readValue);
break;
}
case SearchComparison::Increased:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
return memoryValueComparator(SearchComparison::GreaterThan, priorValue, readValue);
break;
}
case SearchComparison::IncreasedBy:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
const T expectedIncrease = searchValue + priorValue;
return memoryValueComparator(SearchComparison::Equals, readValue, expectedIncrease);
break;
}
case SearchComparison::Decreased:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
return memoryValueComparator(SearchComparison::LessThan, priorValue, readValue);
break;
}
case SearchComparison::DecreasedBy:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
const T expectedDecrease = priorValue - searchValue;
return memoryValueComparator(SearchComparison::Equals, readValue, expectedDecrease);
break;
}
case SearchComparison::Changed:
case SearchComparison::NotChanged:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
return memoryValueComparator(isNotOperator ? SearchComparison::Equals : SearchComparison::NotEquals, priorValue, readValue);
break;
}
case SearchComparison::ChangedBy:
{
const T priorValue = searchResults.value(searchAddress).getValue<T>();
const T expectedIncrease = searchValue + priorValue;
const T expectedDecrease = priorValue - searchValue;
return memoryValueComparator(SearchComparison::Equals, readValue, expectedIncrease) || memoryValueComparator(SearchComparison::Equals, readValue, expectedDecrease);
}
default:
Console.Error("Debugger: Unknown type when doing memory search!");
return false;
}
}
template <typename T>
SearchResults searchWorker(DebugInterface* cpu, SearchResults searchResults, SearchType searchType, SearchComparison searchComparison, u32 start, u32 end, T searchValue)
{
SearchResults newSearchResults;
const bool isSearchingRange = searchResults.size() <= 0;
if (isSearchingRange) if (isSearchingRange)
{ {
for (u32 addr = start; addr < end; addr += sizeof(T)) for (u32 addr = start; addr < end; addr += sizeof(T))
{ {
if (!cpu->isValidAddress(addr)) if (!cpu->isValidAddress(addr))
continue; continue;
T readValue = readValueAtAddress<T>(cpu, addr); T readValue = readValueAtAddress<T>(cpu, addr);
if (memoryValueComparator(searchComparison, searchValue, readValue)) if (handleSearchComparison(searchComparison, addr, searchResults, searchValue, readValue))
{ {
hitAddresses.push_back(addr); newSearchResults.insert(addr, MemorySearchWidget::SearchResult(addr, QVariant::fromValue(readValue), searchType));
} }
} }
} }
else else
{ {
for (const u32 addr : searchAddresses) for (const MemorySearchWidget::SearchResult& searchResult : searchResults)
{ {
const u32 addr = searchResult.getAddress();
if (!cpu->isValidAddress(addr)) if (!cpu->isValidAddress(addr))
continue; continue;
T readValue = readValueAtAddress<T>(cpu, addr); T readValue = readValueAtAddress<T>(cpu, addr);
if (memoryValueComparator(searchComparison, searchValue, readValue)) if (handleSearchComparison(searchComparison, addr, searchResults, searchValue, readValue))
{ {
hitAddresses.push_back(addr); newSearchResults.insert(addr, MemorySearchWidget::SearchResult(addr, QVariant::fromValue(readValue), searchType));
} }
} }
} }
return hitAddresses; return newSearchResults;
} }
static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value) static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value)
@ -282,55 +352,105 @@ static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison sear
return !isNotOperator; return !isNotOperator;
} }
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value) bool handleArraySearchComparison(DebugInterface* cpu, SearchComparison searchComparison, u32 searchAddress, SearchResults searchResults, QByteArray searchValue)
{ {
std::vector<u32> hitAddresses; const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged;
const bool isSearchingRange = searchAddresses.size() <= 0; switch (searchComparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
{
return compareByteArrayAtAddress(cpu, searchComparison, searchAddress, searchValue);
break;
}
case SearchComparison::Changed:
case SearchComparison::NotChanged:
{
QByteArray priorValue = searchResults.value(searchAddress).getArrayValue();
return compareByteArrayAtAddress(cpu, isNotOperator ? SearchComparison::Equals : SearchComparison::NotEquals, searchAddress, priorValue);
break;
}
default:
{
Console.Error("Debugger: Unknown search comparison when doing memory search");
return false;
}
}
// Default to no match found unless the comparison is a NotEquals
return isNotOperator;
}
static QByteArray readArrayAtAddress(DebugInterface* cpu, u32 address, u32 length)
{
QByteArray readArray;
for (u32 i = address; i < address + length; i++)
{
readArray.append(cpu->read8(i));
}
return readArray;
}
static SearchResults searchWorkerByteArray(DebugInterface* cpu, SearchType searchType, SearchComparison searchComparison, SearchResults searchResults, u32 start, u32 end, QByteArray searchValue)
{
SearchResults newResults;
const bool isSearchingRange = searchResults.size() <= 0;
if (isSearchingRange) if (isSearchingRange)
{ {
for (u32 addr = start; addr < end; addr += 1) for (u32 addr = start; addr < end; addr += 1)
{ {
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value)) if (!cpu->isValidAddress(addr))
continue;
if (handleArraySearchComparison(cpu, searchComparison, addr, searchResults, searchValue))
{ {
hitAddresses.emplace_back(addr); newResults.insert(addr, MemorySearchWidget::SearchResult(addr, searchValue, searchType));
addr += value.length() - 1; addr += searchValue.length() - 1;
} }
} }
} }
else else
{ {
for (u32 addr : searchAddresses) for (MemorySearchWidget::SearchResult searchResult : searchResults)
{ {
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value)) const u32 addr = searchResult.getAddress();
if (!cpu->isValidAddress(addr))
continue;
if (handleArraySearchComparison(cpu, searchComparison, addr, searchResults, searchValue))
{ {
hitAddresses.emplace_back(addr); QByteArray matchValue;
if (searchComparison == SearchComparison::Equals)
matchValue = searchValue;
else if (searchComparison == SearchComparison::NotChanged)
matchValue = searchResult.getArrayValue();
else
matchValue = readArrayAtAddress(cpu, addr, searchValue.length() - 1);
newResults.insert(addr, MemorySearchWidget::SearchResult(addr, matchValue, searchType));
} }
} }
} }
return hitAddresses; return newResults;
} }
std::vector<u32> startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QString value, int base) SearchResults startWorker(DebugInterface* cpu, const SearchType type, const SearchComparison comparison, SearchResults searchResults, u32 start, u32 end, QString value, int base)
{ {
const bool isSigned = value.startsWith("-"); const bool isSigned = value.startsWith("-");
switch (type) switch (type)
{ {
case SearchType::ByteType: case SearchType::ByteType:
return isSigned ? searchWorker<s8>(cpu, searchAddresses, searchComparison, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, searchAddresses, searchComparison, start, end, value.toUShort(nullptr, base)); return isSigned ? searchWorker<s8>(cpu, searchResults, type, comparison, start, end, value.toShort(nullptr, base)) : searchWorker<u8>(cpu, searchResults, type, comparison, start, end, value.toUShort(nullptr, base));
case SearchType::Int16Type: case SearchType::Int16Type:
return isSigned ? searchWorker<s16>(cpu, searchAddresses, searchComparison, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, searchAddresses, searchComparison, start, end, value.toUShort(nullptr, base)); return isSigned ? searchWorker<s16>(cpu, searchResults, type, comparison, start, end, value.toShort(nullptr, base)) : searchWorker<u16>(cpu, searchResults, type, comparison, start, end, value.toUShort(nullptr, base));
case SearchType::Int32Type: case SearchType::Int32Type:
return isSigned ? searchWorker<s32>(cpu, searchAddresses, searchComparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchAddresses, searchComparison, start, end, value.toUInt(nullptr, base)); return isSigned ? searchWorker<s32>(cpu, searchResults, type, comparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchResults, type, comparison, start, end, value.toUInt(nullptr, base));
case SearchType::Int64Type: case SearchType::Int64Type:
return isSigned ? searchWorker<s64>(cpu, searchAddresses, searchComparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchAddresses, searchComparison, start, end, value.toULongLong(nullptr, base)); return isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base));
case SearchType::FloatType: case SearchType::FloatType:
return searchWorker<float>(cpu, searchAddresses, searchComparison, start, end, value.toFloat()); return searchWorker<float>(cpu, searchResults, type, comparison, start, end, value.toFloat());
case SearchType::DoubleType: case SearchType::DoubleType:
return searchWorker<double>(cpu, searchAddresses, searchComparison, start, end, value.toDouble()); return searchWorker<double>(cpu, searchResults, type, comparison, start, end, value.toDouble());
case SearchType::StringType: case SearchType::StringType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, value.toUtf8()); return searchWorkerByteArray(cpu, type, comparison, searchResults, start, end, value.toUtf8());
case SearchType::ArrayType: case SearchType::ArrayType:
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8())); return searchWorkerByteArray(cpu, type, comparison, searchResults, 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;
@ -343,7 +463,7 @@ void MemorySearchWidget::onSearchButtonClicked()
if (!m_cpu->isAlive()) if (!m_cpu->isAlive())
return; return;
const SearchType searchType = static_cast<SearchType>(m_ui.cmbSearchType->currentIndex()); const SearchType searchType = getCurrentSearchType();
const bool searchHex = m_ui.chkSearchHex->isChecked(); const bool searchHex = m_ui.chkSearchHex->isChecked();
bool ok; bool ok;
@ -370,23 +490,10 @@ void MemorySearchWidget::onSearchButtonClicked()
} }
const QString searchValue = m_ui.txtSearchValue->text(); const QString searchValue = m_ui.txtSearchValue->text();
const SearchComparison searchComparison = static_cast<SearchComparison>(m_ui.cmbSearchComparison->currentIndex()); const SearchComparison searchComparison = getCurrentSearchComparison();
const bool isFilterSearch = sender() == m_ui.btnFilterSearch; const bool isFilterSearch = sender() == m_ui.btnFilterSearch;
unsigned long long value; unsigned long long value;
const bool isVariableSize = searchType == SearchType::ArrayType || searchType == SearchType::StringType;
if (isVariableSize && !isFilterSearch && searchComparison == SearchComparison::NotEquals)
{
QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can use the Not Equals search comparison type with new searches."));
return;
}
if (isVariableSize && searchComparison != SearchComparison::Equals && searchComparison != SearchComparison::NotEquals)
{
QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can only be used with Equals search comparisons."));
return;
}
switch (searchType) switch (searchType)
{ {
case SearchType::ByteType: case SearchType::ByteType:
@ -437,35 +544,39 @@ void MemorySearchWidget::onSearchButtonClicked()
return; return;
} }
QFutureWatcher<std::vector<u32>>* workerWatcher = new QFutureWatcher<std::vector<u32>>; QFutureWatcher<SearchResults>* workerWatcher = new QFutureWatcher<SearchResults>();
auto onSearchFinished = [this, workerWatcher] {
connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, [this, workerWatcher] {
m_ui.btnSearch->setDisabled(false); m_ui.btnSearch->setDisabled(false);
m_ui.listSearchResults->clear(); m_ui.listSearchResults->clear();
const auto& results = workerWatcher->future().result(); const auto& results = workerWatcher->future().result();
m_searchResults = results; m_searchResultsMap = results;
loadSearchResults(); loadSearchResults();
m_ui.resultsCountLabel->setText(QString(tr("%0 results found")).arg(results.size()));
m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0); m_ui.btnFilterSearch->setDisabled(m_ui.listSearchResults->count() == 0);
}); updateSearchComparisonSelections();
};
connect(workerWatcher, &QFutureWatcher<std::vector<u32>>::finished, onSearchFinished);
m_ui.btnSearch->setDisabled(true); m_ui.btnSearch->setDisabled(true);
std::vector<u32> addresses; SearchResults searchResultsMap;
if (isFilterSearch) if (isFilterSearch)
{ {
addresses = m_searchResults; searchResultsMap = m_searchResultsMap;
} }
QFuture<std::vector<u32>> workerFuture =
QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, addresses, searchStart, searchEnd, searchValue, searchHex ? 16 : 10); QFuture<SearchResults> workerFuture = QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, searchResultsMap, searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
workerWatcher->setFuture(workerFuture); workerWatcher->setFuture(workerFuture);
connect(workerWatcher, &QFutureWatcher<SearchResults>::finished, onSearchFinished);
m_ui.resultsCountLabel->setText(tr("Searching..."));
m_ui.resultsCountLabel->setVisible(true);
} }
void MemorySearchWidget::onSearchResultsListScroll(u32 value) void MemorySearchWidget::onSearchResultsListScroll(u32 value)
{ {
bool hasResultsToLoad = static_cast<size_t>(m_ui.listSearchResults->count()) < m_searchResults.size(); const bool hasResultsToLoad = static_cast<qsizetype>(m_ui.listSearchResults->count()) < m_searchResultsMap.size();
bool scrolledSufficiently = value > (m_ui.listSearchResults->verticalScrollBar()->maximum() * 0.95); const bool scrolledSufficiently = value > (m_ui.listSearchResults->verticalScrollBar()->maximum() * 0.95);
if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently) if (!m_resultsLoadTimer.isActive() && hasResultsToLoad && scrolledSufficiently)
{ {
// Load results once timer ends, allowing us to debounce repeated requests and only do one load. // Load results once timer ends, allowing us to debounce repeated requests and only do one load.
@ -476,7 +587,7 @@ void MemorySearchWidget::onSearchResultsListScroll(u32 value)
void MemorySearchWidget::loadSearchResults() void MemorySearchWidget::loadSearchResults()
{ {
const u32 numLoaded = m_ui.listSearchResults->count(); const u32 numLoaded = m_ui.listSearchResults->count();
const u32 amountLeftToLoad = m_searchResults.size() - numLoaded; const u32 amountLeftToLoad = m_searchResultsMap.size() - numLoaded;
if (amountLeftToLoad < 1) if (amountLeftToLoad < 1)
return; return;
@ -484,11 +595,93 @@ void MemorySearchWidget::loadSearchResults()
const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad; const u32 maxLoadAmount = isFirstLoad ? m_initialResultsLoadLimit : m_numResultsAddedPerLoad;
const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad; const u32 numToLoad = amountLeftToLoad > maxLoadAmount ? maxLoadAmount : amountLeftToLoad;
const auto addresses = m_searchResultsMap.keys();
for (u32 i = 0; i < numToLoad; i++) for (u32 i = 0; i < numToLoad; i++)
{ {
u32 address = m_searchResults.at(numLoaded + i); const u32 address = addresses.at(numLoaded + i);
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16)); QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16));
item->setData(Qt::UserRole, address); item->setData(Qt::UserRole, address);
m_ui.listSearchResults->addItem(item); m_ui.listSearchResults->addItem(item);
} }
} }
SearchType MemorySearchWidget::getCurrentSearchType()
{
return static_cast<SearchType>(m_ui.cmbSearchType->currentIndex());
}
SearchComparison MemorySearchWidget::getCurrentSearchComparison()
{
// Note: The index can't be converted directly to the enum value since we change what comparisons are shown.
return m_searchComparisonLabelMap.labelToEnum(m_ui.cmbSearchComparison->currentText());
}
void MemorySearchWidget::onSearchTypeChanged(int newIndex)
{
if (newIndex < 4)
m_ui.chkSearchHex->setEnabled(true);
else
m_ui.chkSearchHex->setEnabled(false);
// Clear existing search results when the comparison type changes
if (m_searchResultsMap.size() > 0 && (int)(m_searchResultsMap.first().getType()) != newIndex)
{
m_searchResultsMap.clear();
m_ui.btnSearch->setDisabled(false);
m_ui.btnFilterSearch->setDisabled(true);
}
updateSearchComparisonSelections();
}
void MemorySearchWidget::updateSearchComparisonSelections()
{
const QString selectedComparisonLabel = m_ui.cmbSearchComparison->currentText();
const SearchComparison selectedComparison = m_searchComparisonLabelMap.labelToEnum(selectedComparisonLabel);
const std::vector<SearchComparison> comparisons = getValidSearchComparisonsForState(getCurrentSearchType(), m_searchResultsMap);
m_ui.cmbSearchComparison->clear();
for (const SearchComparison comparison : comparisons)
{
m_ui.cmbSearchComparison->addItem(m_searchComparisonLabelMap.enumToLabel(comparison));
}
// Preserve selection if applicable
if (selectedComparison == SearchComparison::Invalid)
return;
if (std::find(comparisons.begin(), comparisons.end(), selectedComparison) != comparisons.end())
m_ui.cmbSearchComparison->setCurrentText(selectedComparisonLabel);
}
std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForState(SearchType type, SearchResults existingResults)
{
const bool hasResults = existingResults.size() > 0;
std::vector<SearchComparison> comparisons = { SearchComparison::Equals };
if (type == SearchType::ArrayType || type == SearchType::StringType)
{
if (hasResults && existingResults.first().isArrayValue())
{
comparisons.push_back(SearchComparison::NotEquals);
comparisons.push_back(SearchComparison::Changed);
comparisons.push_back(SearchComparison::NotChanged);
}
return comparisons;
}
comparisons.push_back(SearchComparison::NotEquals);
comparisons.push_back(SearchComparison::GreaterThan);
comparisons.push_back(SearchComparison::GreaterThanOrEqual);
comparisons.push_back(SearchComparison::LessThan);
comparisons.push_back(SearchComparison::LessThanOrEqual);
if (hasResults && existingResults.first().getType() == type)
{
comparisons.push_back(SearchComparison::Increased);
comparisons.push_back(SearchComparison::IncreasedBy);
comparisons.push_back(SearchComparison::Decreased);
comparisons.push_back(SearchComparison::DecreasedBy);
comparisons.push_back(SearchComparison::Changed);
comparisons.push_back(SearchComparison::ChangedBy);
comparisons.push_back(SearchComparison::NotChanged);
}
return comparisons;
}

View File

@ -9,6 +9,7 @@
#include <QtWidgets/QWidget> #include <QtWidgets/QWidget>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtCore/QMap>
class MemorySearchWidget final : public QWidget class MemorySearchWidget final : public QWidget
{ {
@ -39,12 +40,86 @@ public:
GreaterThan, GreaterThan,
GreaterThanOrEqual, GreaterThanOrEqual,
LessThan, LessThan,
LessThanOrEqual LessThanOrEqual,
Increased,
IncreasedBy,
Decreased,
DecreasedBy,
Changed,
ChangedBy,
NotChanged,
Invalid
};
class SearchComparisonLabelMap
{
public:
SearchComparisonLabelMap()
{
insert(SearchComparison::Equals, tr("Equals"));
insert(SearchComparison::NotEquals, tr("Not Equals"));
insert(SearchComparison::GreaterThan, tr("Greater Than"));
insert(SearchComparison::GreaterThanOrEqual, tr("Greater Than Or Equal"));
insert(SearchComparison::LessThan, tr("Less Than"));
insert(SearchComparison::LessThanOrEqual, tr("Less Than Or Equal"));
insert(SearchComparison::Increased, tr("Increased"));
insert(SearchComparison::IncreasedBy, tr("Increased By"));
insert(SearchComparison::Decreased, tr("Decreased"));
insert(SearchComparison::DecreasedBy, tr("Decreased By"));
insert(SearchComparison::Changed, tr("Changed"));
insert(SearchComparison::ChangedBy, tr("Changed By"));
insert(SearchComparison::NotChanged, tr("Not Changed"));
insert(SearchComparison::Invalid, "");
}
SearchComparison labelToEnum(QString comparisonLabel)
{
return labelToEnumMap.value(comparisonLabel, SearchComparison::Invalid);
}
QString enumToLabel(SearchComparison comparison) {
return enumToLabelMap.value(comparison, "");
}
private:
QMap<SearchComparison, QString> enumToLabelMap;
QMap<QString, SearchComparison> labelToEnumMap;
void insert(SearchComparison comparison, QString comparisonLabel)
{
enumToLabelMap.insert(comparison, comparisonLabel);
labelToEnumMap.insert(comparisonLabel, comparison);
};
};
class SearchResult
{
private:
u32 address;
QVariant value;
SearchType type;
public:
SearchResult() {}
SearchResult(u32 address, const QVariant& value, SearchType type)
: address(address), value(value), type(type)
{
}
bool isIntegerValue() const { return type == SearchType::ByteType || type == SearchType::Int16Type || type == SearchType::Int32Type || type == SearchType::Int64Type; }
bool isFloatValue() const { return type == SearchType::FloatType; }
bool isDoubleValue() const { return type == SearchType::DoubleType; }
bool isArrayValue() const { return type == SearchType::ArrayType || type == SearchType::StringType; }
u32 getAddress() const { return address; }
SearchType getType() const { return type; }
QByteArray getArrayValue() const { return isArrayValue() ? value.toByteArray() : QByteArray(); }
template<typename T>
T getValue() const
{
return value.value<T>();
}
}; };
public slots: public slots:
void onSearchButtonClicked(); void onSearchButtonClicked();
void onSearchResultsListScroll(u32 value); void onSearchResultsListScroll(u32 value);
void onSearchTypeChanged(int newIndex);
void loadSearchResults(); void loadSearchResults();
void contextSearchResultGoToDisassembly(); void contextSearchResultGoToDisassembly();
void contextRemoveSearchResult(); void contextRemoveSearchResult();
@ -58,13 +133,17 @@ signals:
void switchToMemoryViewTab(); void switchToMemoryViewTab();
private: private:
std::vector<u32> m_searchResults; QMap<u32, SearchResult> m_searchResultsMap;
SearchComparisonLabelMap m_searchComparisonLabelMap;
Ui::MemorySearchWidget m_ui;
DebugInterface* m_cpu;
QTimer m_resultsLoadTimer;
Ui::MemorySearchWidget m_ui; u32 m_initialResultsLoadLimit = 20000;
DebugInterface* m_cpu;
QTimer m_resultsLoadTimer;
u32 m_initialResultsLoadLimit = 20000;
u32 m_numResultsAddedPerLoad = 10000; u32 m_numResultsAddedPerLoad = 10000;
void updateSearchComparisonSelections();
std::vector<SearchComparison> getValidSearchComparisonsForState(SearchType type, QMap<u32, MemorySearchWidget::SearchResult> existingResults);
SearchType getCurrentSearchType();
SearchComparison getCurrentSearchComparison();
}; };

View File

@ -22,7 +22,7 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="valueLabel">
<property name="text"> <property name="text">
<string>Value</string> <string>Value</string>
</property> </property>
@ -32,7 +32,7 @@
<widget class="QLineEdit" name="txtSearchValue"/> <widget class="QLineEdit" name="txtSearchValue"/>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="typeLabel">
<property name="text"> <property name="text">
<string>Type</string> <string>Type</string>
</property> </property>
@ -83,7 +83,7 @@
</widget> </widget>
</item> </item>
<item row="2" column="2"> <item row="2" column="2">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="hexLabel">
<property name="text"> <property name="text">
<string>Hex</string> <string>Hex</string>
</property> </property>
@ -162,7 +162,7 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" alignment="Qt::AlignLeft"> <item row="0" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="startLabel">
<property name="text"> <property name="text">
<string>Start</string> <string>Start</string>
</property> </property>
@ -176,7 +176,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="endLabel">
<property name="text"> <property name="text">
<string>End</string> <string>End</string>
</property> </property>
@ -194,6 +194,16 @@
<item> <item>
<widget class="QListWidget" name="listSearchResults"/> <widget class="QListWidget" name="listSearchResults"/>
</item> </item>
<item>
<widget class="QLabel" name="resultsCountLabel">
<property name="visible">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>