From 8f64422929e51b2b89fc99fb7f3fd61f832361a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 6 Nov 2026 00:43:56 +0100 Subject: [PATCH] ImDebugger: Add scan/remove functions in range, reapply replacements The Win32 Edit Symbols dialog was the only UI that could do a function scan or a range removal, and so the only way to rerun ReplaceFunctions(). The disassembly context menu now has a range popup (prefilled from the selection or the current function) with Scan, Remove and Remove and rescan, plus a "Reapply function replacements" item. The range logic moves into MIPSAnalyst::ScanFunctionsInRange() and RemoveFunctionsInRange(), shared with the WebSocket debugger and the Win32 dialog. That also fixes the dialog scanning to address + size - 1, an unaligned end that ScanForFunctions() asserts on. Co-Authored-By: Claude Opus 5.5 --- Core/Debugger/WebSocket/HLESubscriber.cpp | 29 +--------- Core/MIPS/MIPSAnalyst.cpp | 36 ++++++++++++ Core/MIPS/MIPSAnalyst.h | 7 +++ UI/ImDebugger/ImDisasmView.cpp | 69 +++++++++++++++++++++++ UI/ImDebugger/ImDisasmView.h | 5 ++ Windows/Debugger/EditSymbolsWindow.cpp | 30 +--------- 6 files changed, 123 insertions(+), 53 deletions(-) diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp index 0237fff5d7..81c66fbf9e 100644 --- a/Core/Debugger/WebSocket/HLESubscriber.cpp +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -496,31 +496,8 @@ void WebSocketHLEFuncRemove(DebuggerRequest &req) { // (Note: this makes no checks whether the range is valid) // Returns the number of removed functions static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) { - u32 func_address = g_symbolMap->GetFunctionStart(addr); - if (func_address == SymbolMap::INVALID_ADDRESS) { - func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION); - } - - u32 counter = 0; - while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) { - g_symbolMap->RemoveFunction(func_address, true); - ++counter; - func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION); - } - + u32 counter = MIPSAnalyst::RemoveFunctionsInRange(addr, size); if (counter) { - MIPSAnalyst::ForgetFunctions(addr, addr + size); - - // The following was copied from hle.func.remove: - g_symbolMap->SortSymbols(); - - MIPSAnalyst::UpdateHashMap(); - MIPSAnalyst::ApplyHashMap(); - - if (g_Config.bFuncReplacements) { - MIPSAnalyst::ReplaceFunctions(); - } - // Clear cache for branch lines and such. g_disassemblyManager.clear(); } @@ -664,8 +641,8 @@ void WebSocketHLEFuncScan(DebuggerRequest &req) { RemoveFuncSymbolsInRange(addr, size); } - bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true); - MIPSAnalyst::FinalizeScan(insertSymbols); + MIPSAnalyst::ScanFunctionsInRange(addr, size); + g_disassemblyManager.clear(); req.Respond(); }); diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 05a8cfb582..e4d75e0c3f 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -26,6 +26,7 @@ #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/TimeUtil.h" +#include "Common/Math/math_util.h" #include "Core/Config.h" #include "Core/MemMap.h" #include "Core/System.h" @@ -1317,6 +1318,41 @@ namespace MIPSAnalyst { } } + void ScanFunctionsInRange(u32 addr, u32 size) { + addr = RoundDownToMultipleOf(addr, 4); + size = RoundUpToMultipleOf(size, 4); + bool insertSymbols = ScanForFunctions(addr, addr + size, true); + FinalizeScan(insertSymbols); + } + + u32 RemoveFunctionsInRange(u32 addr, u32 size) { + addr = RoundDownToMultipleOf(addr, 4); + size = RoundUpToMultipleOf(size, 4); + + u32 funcAddr = g_symbolMap->GetFunctionStart(addr); + if (funcAddr == SymbolMap::INVALID_ADDRESS) { + funcAddr = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION); + } + + u32 count = 0; + while (funcAddr < addr + size && funcAddr != SymbolMap::INVALID_ADDRESS) { + g_symbolMap->RemoveFunction(funcAddr, true); + ++count; + funcAddr = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION); + } + + if (count) { + ForgetFunctions(addr, addr + size); + g_symbolMap->SortSymbols(); + UpdateHashMap(); + ApplyHashMap(); + if (g_Config.bFuncReplacements) { + ReplaceFunctions(); + } + } + return count; + } + bool GetAnalyzedFunctionAt(u32 addr, AnalyzedFunction *out) { std::lock_guard guard(functions_lock); for (auto iter = functions.begin(), end = functions.end(); iter != end; ++iter) { diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index bfd9b05071..6b1489ecb7 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -109,6 +109,13 @@ namespace MIPSAnalyst { void FinalizeScan(bool insertSymbols); void ForgetFunctions(u32 startAddr, u32 endAddr); + // Debugger helpers. The range is widened to whole instructions. Callers should clear + // g_disassemblyManager afterwards. + void ScanFunctionsInRange(u32 addr, u32 size); + // Removes the function symbols starting in (or containing the start of) the range, forgets + // them, and reapplies replacements. Returns the number removed. + u32 RemoveFunctionsInRange(u32 addr, u32 size); + bool GetAnalyzedFunctionAt(u32 addr, AnalyzedFunction *out); // endAddr is exclusive. bool HasFunctionsInRange(u32 startAddr, u32 endAddr); diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 221c5246b1..f21e49dcba 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -5,6 +5,7 @@ #include "Common/StringUtils.h" #include "Common/Log.h" #include "Common/Math/geom2d.h" +#include "Core/Config.h" #include "Core/Core.h" #include "Core/HLE/HLE.h" #include "Core/Debugger/DebugInterface.h" @@ -787,6 +788,7 @@ void ImDisasmView::RemoveFunction(u32 funcStart) { void ImDisasmView::PopupMenu(MIPSState *mips, ImControl &control) { bool renameFunctionPopup = false; + bool funcRangePopup = false; if (ImGui::BeginPopup("context")) { // The source location is the more useful heading when there is one, but keep the address: // it's what you paste into a bug report or another debugger. @@ -905,6 +907,24 @@ void ImDisasmView::PopupMenu(MIPSState *mips, ImControl &control) { mapReloaded_ = true; } } + if (ImGui::MenuItem("Scan/remove functions in range...")) { + // Prefill with the selection if there is one, otherwise the function we're in. + if (selectRangeEnd_ - selectRangeStart_ > 4 || funcStart == -1) { + funcRangeAddr_ = selectRangeStart_; + funcRangeSize_ = selectRangeEnd_ - selectRangeStart_; + } else { + funcRangeAddr_ = funcStart; + funcRangeSize_ = g_symbolMap->GetFunctionSize(funcStart); + } + funcRangeError_.clear(); + funcRangePopup = true; + } + if (ImGui::MenuItem("Reapply function replacements", nullptr, false, g_Config.bFuncReplacements)) { + MIPSAnalyst::ReplaceFunctions(); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("Installs the HLE replacements (ReplaceTables) for every known function whose hash matches."); + } if (prevBegin != -1) { u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin); ShowInMemoryDumperMenuItem(prevBegin, prevSize, MemDumpMode::Disassembly, control); @@ -912,6 +932,55 @@ void ImDisasmView::PopupMenu(MIPSState *mips, ImControl &control) { ImGui::EndPopup(); } + if (funcRangePopup) { + ImGui::OpenPopup("funcRange"); + } + if (ImGui::BeginPopup("funcRange")) { + ImGui::TextUnformatted("Scan or remove functions in range"); + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + } + ImGui::InputScalar("Address", ImGuiDataType_U32, &funcRangeAddr_, nullptr, nullptr, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("Size", ImGuiDataType_U32, &funcRangeSize_, nullptr, nullptr, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + + enum class RangeAction { NONE, SCAN, REMOVE, RESCAN }; + RangeAction action = RangeAction::NONE; + if (ImGui::Button("Scan")) { + action = RangeAction::SCAN; + } + ImGui::SameLine(); + if (ImGui::Button("Remove")) { + action = RangeAction::REMOVE; + } + ImGui::SameLine(); + if (ImGui::Button("Remove and rescan")) { + action = RangeAction::RESCAN; + } + if (action != RangeAction::NONE) { + if (funcRangeSize_ == 0 || !Memory::IsValidRange(funcRangeAddr_, funcRangeSize_)) { + funcRangeError_ = "Invalid range"; + } else { + u32 removed = 0; + if (action != RangeAction::SCAN) { + removed = MIPSAnalyst::RemoveFunctionsInRange(funcRangeAddr_, funcRangeSize_); + } + if (action != RangeAction::REMOVE) { + MIPSAnalyst::ScanFunctionsInRange(funcRangeAddr_, funcRangeSize_); + } + g_disassemblyManager.clear(); + mapReloaded_ = true; + if (action == RangeAction::REMOVE) { + statusBarText_ = StringFromFormat("Removed %d functions", removed); + } + ImGui::CloseCurrentPopup(); + } + } + if (!funcRangeError_.empty()) { + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", funcRangeError_.c_str()); + } + ImGui::EndPopup(); + } + // Sub popups here if (renameFunctionPopup) { ImGui::OpenPopup("renameFunction"); diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 86d3323c65..c6f4508996 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -197,6 +197,11 @@ private: u32 funcBegin_ = 0; char funcNameTemp_[128]{}; + // "Scan/remove functions in range" popup. + u32 funcRangeAddr_ = 0; + u32 funcRangeSize_ = 0; + std::string funcRangeError_; + bool assemblePopup_ = false; u32 assembleAddress_ = 0; char assembleTemp_[256]{}; diff --git a/Windows/Debugger/EditSymbolsWindow.cpp b/Windows/Debugger/EditSymbolsWindow.cpp index e9e44408e7..c385304f1e 100644 --- a/Windows/Debugger/EditSymbolsWindow.cpp +++ b/Windows/Debugger/EditSymbolsWindow.cpp @@ -147,36 +147,12 @@ bool EditSymbolsWindow::exec() { } void EditSymbolsWindow::Scan() { - bool insertSymbols = MIPSAnalyst::ScanForFunctions(address_, address_ + size_ - 1, true); - MIPSAnalyst::FinalizeScan(insertSymbols); + MIPSAnalyst::ScanFunctionsInRange(address_, size_); + g_disassemblyManager.clear(); } void EditSymbolsWindow::Remove() { - u32 func_address = g_symbolMap->GetFunctionStart(address_); - if (func_address == SymbolMap::INVALID_ADDRESS) { - func_address = g_symbolMap->GetNextSymbolAddress(address_, SymbolType::ST_FUNCTION); - } - - u32 counter = 0; - while (func_address < address_ + size_ && func_address != SymbolMap::INVALID_ADDRESS) { - g_symbolMap->RemoveFunction(func_address, true); - ++counter; - func_address = g_symbolMap->GetNextSymbolAddress(address_, SymbolType::ST_FUNCTION); - } - - if (counter) { - MIPSAnalyst::ForgetFunctions(address_, address_ + size_); - - // The following was copied from hle.func.remove: - g_symbolMap->SortSymbols(); - - MIPSAnalyst::UpdateHashMap(); - MIPSAnalyst::ApplyHashMap(); - - if (g_Config.bFuncReplacements) { - MIPSAnalyst::ReplaceFunctions(); - } - + if (MIPSAnalyst::RemoveFunctionsInRange(address_, size_)) { // Clear cache for branch lines and such. g_disassemblyManager.clear(); }