From 8cf77a49f377b1b8c87f207c4440c8ce903626c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 25 Aug 2026 22:33:51 +0200 Subject: [PATCH] Add module filter to symbol list in ImDebugger --- Common/CMakeLists.txt | 1 + Common/Common.vcxproj | 3 +- Common/Common.vcxproj.filters | 3 ++ Common/Data/Text/Parsers.cpp | 13 ------ Common/Data/Text/StringWriter.cpp | 14 ++++++ Core/Debugger/SymbolMap.cpp | 8 +++- UI/ImDebugger/ImDisasmView.cpp | 61 +++++++++++++++++++++++++ UI/ImDebugger/ImDisasmView.h | 10 ++++ UWP/CommonUWP/CommonUWP.vcxproj | 2 + UWP/CommonUWP/CommonUWP.vcxproj.filters | 6 +++ android/jni/Android.mk | 1 + build.gradle.kts | 2 +- libretro/Makefile.common | 1 + 13 files changed, 109 insertions(+), 16 deletions(-) diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 7c146831c6..59a473be3a 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -107,6 +107,7 @@ add_library(Common STATIC Data/Text/Parsers.cpp Data/Text/Parsers.h Data/Text/StringWriter.h + Data/Text/StringWriter.cpp Data/Text/WrapText.cpp Data/Text/WrapText.h Data/Random/Rng.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 03421d52f1..7b33f35879 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -900,6 +900,7 @@ + @@ -1081,4 +1082,4 @@ - + \ No newline at end of file diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 45235ce676..72b9de58e3 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -1472,6 +1472,9 @@ UI + + Data\Text + diff --git a/Common/Data/Text/Parsers.cpp b/Common/Data/Text/Parsers.cpp index b5b5c0992c..55b626b0b4 100644 --- a/Common/Data/Text/Parsers.cpp +++ b/Common/Data/Text/Parsers.cpp @@ -160,16 +160,3 @@ bool TryParse(const std::string &str, bool *const output) { return true; } - -StringWriter &StringWriter::F(const char *format, ...) { - const size_t remainder = bufSize_ - (p_ - start_); - if (remainder < 3) { - return *this; - } - va_list args; - va_start(args, format); - int wouldHaveBeenWritten = vsnprintf(p_, remainder, format, args); - p_ += std::min((int)remainder, wouldHaveBeenWritten); - va_end(args); - return *this; -} diff --git a/Common/Data/Text/StringWriter.cpp b/Common/Data/Text/StringWriter.cpp index 8d8ba6f53a..7e554f386f 100644 --- a/Common/Data/Text/StringWriter.cpp +++ b/Common/Data/Text/StringWriter.cpp @@ -1,2 +1,16 @@ +#include #include "Common/Data/Text/StringWriter.h" + +StringWriter &StringWriter::F(const char *format, ...) { + const size_t remainder = bufSize_ - (p_ - start_); + if (remainder < 3) { + return *this; + } + va_list args; + va_start(args, format); + int wouldHaveBeenWritten = vsnprintf(p_, remainder, format, args); + p_ += std::min((int)remainder, wouldHaveBeenWritten); + va_end(args); + return *this; +} diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp index 577dfd187e..8136e320a5 100644 --- a/Core/Debugger/SymbolMap.cpp +++ b/Core/Debugger/SymbolMap.cpp @@ -477,11 +477,14 @@ u32 SymbolMap::GetModuleCrc(int moduleIndex) const { // per-game rather than per-module, hence GetGameSymbolsPath instead of GetModuleSymbolsPath. bool SymbolMap::SaveModuleSymbols(int moduleIndex, const Path &filename, const std::string &gameID, const std::string &gameTitle) const { u32 crc = 0; + // Only for logging - module 0 isn't a module, it's the symbols that aren't in one. + const char *moduleName = "(no module)"; if (moduleIndex != 0) { bool found = false; for (const auto &module : modules) { if (module.index == moduleIndex) { crc = module.crc; + moduleName = module.name; found = true; break; } @@ -529,8 +532,10 @@ bool SymbolMap::SaveModuleSymbols(int moduleIndex, const Path &filename, const s if (count == 0) { // Nothing worth keeping. Remove any previous file rather than leaving one behind that // would restore symbols the user has since deleted. - if (File::Exists(filename)) + if (File::Exists(filename)) { + INFO_LOG(Log::Loader, "SaveModuleSymbols: '%s' has no named symbols left, deleting %s", moduleName, filename.c_str()); File::Delete(filename); + } return true; } @@ -552,6 +557,7 @@ bool SymbolMap::SaveModuleSymbols(int moduleIndex, const Path &filename, const s fwrite(text.data(), 1, text.size(), f); fclose(f); + INFO_LOG(Log::Loader, "SaveModuleSymbols: wrote %d symbol(s) for '%s' to %s", count, moduleName, filename.c_str()); return true; } diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 108b07b89e..4afe874ecc 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -1323,6 +1323,38 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro avail.y -= ImGui::GetTextLineHeightWithSpacing(); if (ImGui::BeginChild("left", ImVec2(150.0f, avail.y), ImGuiChildFlags_ResizeX)) { + // Modules load and unload as the game runs and nothing tells us about it, so poll. The + // list is a couple of dozen entries at most, and this only runs while the panel is open. + std::vector modules = g_symbolMap->getAllModules(); + modules.erase(std::remove_if(modules.begin(), modules.end(), [](const LoadedModuleInfo &m) { + return !m.active; + }), modules.end()); + if (modules.size() != symModules_.size() || !std::equal(modules.begin(), modules.end(), symModules_.begin(), + [](const LoadedModuleInfo &a, const LoadedModuleInfo &b) { + return a.address == b.address && a.size == b.size && a.name == b.name; + })) { + symModules_ = std::move(modules); + // symCache_ holds active symbols only, so it's stale now too. + symsDirty_ = true; + + // Re-resolve the module filter against the new list. A module that reloaded lands at + // a different address, and one that's gone should drop back to All rather than leave + // the list mysteriously empty. + if (!symModuleFilter_.empty()) { + auto found = std::find_if(symModules_.begin(), symModules_.end(), [this](const LoadedModuleInfo &m) { + return m.name == symModuleFilter_; + }); + if (found == symModules_.end()) { + symModuleFilter_.clear(); + symModuleFilterStart_ = 0; + symModuleFilterSize_ = 0; + } else { + symModuleFilterStart_ = found->address; + symModuleFilterSize_ = found->size; + } + } + } + if (symCache_.empty() || symsDirty_) { symCache_ = g_symbolMap->GetAllActiveSymbols(SymbolType::ST_FUNCTION); symsDirty_ = false; @@ -1346,6 +1378,30 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro symMatchesDirty_ = true; } + ImGui::SetNextItemWidth(-1.0f); + if (ImGui::BeginCombo("##symmodule", symModuleFilter_.empty() ? "All modules" : symModuleFilter_.c_str())) { + if (ImGui::Selectable("All modules", symModuleFilter_.empty())) { + symModuleFilter_.clear(); + symModuleFilterStart_ = 0; + symModuleFilterSize_ = 0; + symMatchesDirty_ = true; + } + for (int i = 0; i < (int)symModules_.size(); i++) { + const LoadedModuleInfo &module = symModules_[i]; + // Module names are not unique - the same one can be loaded more than once. + ImGui::PushID(i); + const bool selected = symModuleFilter_ == module.name && symModuleFilterStart_ == module.address; + if (ImGui::Selectable(module.name.c_str(), selected)) { + symModuleFilter_ = module.name; + symModuleFilterStart_ = module.address; + symModuleFilterSize_ = module.size; + symMatchesDirty_ = true; + } + ImGui::PopID(); + } + ImGui::EndCombo(); + } + if (symMatchesDirty_) { symMatchesDirty_ = false; symMatches_.clear(); @@ -1353,6 +1409,11 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro // path to get wrong. These lists run to a few thousand entries, and this only reruns // when the filter or the symbol map changes, not per frame. for (int i = 0; i < (int)symCache_.size(); i++) { + if (!symModuleFilter_.empty()) { + const u32 addr = symCache_[i].address; + if (addr < symModuleFilterStart_ || addr >= symModuleFilterStart_ + symModuleFilterSize_) + continue; + } if (symFilter_[0] == '\0' || containsNoCase(symCache_[i].name, symFilter_)) symMatches_.push_back(i); } diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 6aa850b94a..fa4723d5c1 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -230,6 +230,16 @@ private: std::vector symMatches_; bool symMatchesDirty_ = true; + // Currently loaded modules, for the module filter dropdown. Refreshed every frame the + // symbol list is visible: modules come and go while a game runs, and nothing notifies us. + // A change here also invalidates symCache_, since it's built from active symbols only. + std::vector symModules_; + // The module filter is remembered as a name plus an address range rather than an index into + // symModules_, so a selection survives that list being rebuilt. Empty name means "All". + std::string symModuleFilter_; + u32 symModuleFilterStart_ = 0; + u32 symModuleFilterSize_ = 0; + ImDisasmView disasmView_; char searchTerm_[64]{}; }; diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index a114502d9f..c7684f4dcb 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -93,6 +93,7 @@ + @@ -305,6 +306,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 0489ff52a5..834c991ec0 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -591,6 +591,9 @@ UI + + Data\Text + @@ -1145,6 +1148,9 @@ UI + + Data\Text + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index be75a70f5f..ff7d5723a9 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -334,6 +334,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/Data/Hash/Hash.cpp \ $(SRC)/Common/Data/Text/I18n.cpp \ $(SRC)/Common/Data/Text/Parsers.cpp \ + $(SRC)/Common/Data/Text/StringWriter.cpp \ $(SRC)/Common/Data/Text/WrapText.cpp \ $(SRC)/Common/File/AndroidStorage.cpp \ $(SRC)/Common/File/AndroidContentURI.cpp \ diff --git a/build.gradle.kts b/build.gradle.kts index 94f1c9630e..db41e13ec9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ plugins { - id("com.android.application") version "9.3.1" apply false + id("com.android.application") version "9.3.2" apply false id("com.google.protobuf") version "0.10.0" apply false } diff --git a/libretro/Makefile.common b/libretro/Makefile.common index fb201a2080..dff19e1e61 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -451,6 +451,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Data/Hash/Hash.cpp \ $(COMMONDIR)/Data/Text/I18n.cpp \ $(COMMONDIR)/Data/Text/Parsers.cpp \ + $(COMMONDIR)/Data/Text/StringWriter.cpp \ $(COMMONDIR)/Data/Text/WrapText.cpp \ $(COMMONDIR)/File/VFS/VFS.cpp \ $(COMMONDIR)/File/VFS/DirectoryReader.cpp \