Load module symbols from the debugger backend on demand (#210) - #1115
Merged
Conversation
Add the ability to read the symbols that the debugger backend (LLDB, DbgEng, Windows Native) knows about for a module, and add them to the debugger BinaryView as auto symbols so the annotation process becomes aware of them (e.g. when a register points to a Windows API function). By default no backend symbols are loaded. The user requests them per module (or for all modules); the debugger tracks the exact Symbol objects it adds so they can be removed again on request or when the target is gone. Tracking the Symbol objects (rather than addresses) matters because the linker can fold several symbols onto one address (e.g. identical .cold stubs), which GetSymbolByAddress cannot round-trip. Re-loading a module is idempotent: existing symbols are removed first. - core: DebugSymbol, DebugAdapterSupportSymbols, GetSymbolsForModule; LLDB (SBModule symbols), DbgEng (StartSymbolMatch), Windows Native (SymEnumSymbols) implementations - controller: LoadSymbolsForModule / LoadSymbolsForAllModules / RemoveSymbolsForModule / RemoveAllLoadedSymbols / tracking + cleanup - FFI, C++ API and Python bindings - Modules widget: Load/Reload/Remove context-menu actions, a Symbols column showing which modules have symbols loaded - test: load then remove symbols and check the BinaryView symbol count increases and returns to its original value Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UndefineTrackedSymbols removed the auto symbol before its data variable, the reverse of the load order. That briefly left the data variable symbol-less, and on Windows the core auto-creates an anonymous "data_..." symbol for it, which the subsequent UndefineDataVariable did not clean up -- so each remove/re-add cycle leaked one symbol (LLDB does not auto-name symbol-less data variables, so it did not show there). Undo in the exact inverse of the creation order: remove the data variable first, then the symbol. Also make the unit test robust: keep the strong "no symbols left behind" check on a single clean load/remove cycle, and verify load idempotency via the debugger's own tracking rather than the BinaryView's global symbol count, which some backends (DbgEng) resolve lazily and enumerate slightly differently across calls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removing a module's backend symbols undefined their data variables with UndefineDataVariable's default blacklist=true, which blacklists the address so auto analysis will not recreate an auto data variable there. Because the symbols are added as auto data variables, a later re-load's DefineDataVariable was then suppressed: the symbol had no data variable and did not render in the linear view. Pass blacklist=false when undefining these debugger-managed variables -- we own them and re-add them on demand. Extend the symbol test to track the data variable count across a load/remove/load cycle so it fails under the blacklist bug. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BinaryView has no get_data_variables() method in the Python API; the data variables are exposed via the data_vars property (an address->DataVariable mapping). Count them through that instead so the test runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Symbols added from the debugger backend were defined while function-analysis updates were disabled, and nothing kicked an analysis pass afterward. The symbol table entry appeared immediately, but its data variable was never processed into the views, so a freshly loaded symbol rendered "bare" until the user manually refreshed the symbols view. Call UpdateAnalysis() after the disabled-update window in the load and remove paths so the pending data variables are materialized and the views are notified. RemoveAllLoadedSymbols gains an updateAnalysis flag; the target-gone teardown path passes false so it does not schedule an async pass that could read from the debugger memory region as it is torn down. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The data-variable count assertion after symbol removal fails only on some CI environments and not locally, so the leftover cannot be inspected directly. Attach a diagnostic message to the assertion that lists the data variable addresses that leaked (present after removal, absent at baseline) or went missing, along with each address's data-variable type and the symbols defined there. It is returned as the assertEqual message rather than printed so pytest surfaces it in the failure report even with output capturing enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The post-removal assertion compared the BinaryView's global data-variable count to a pre-load baseline. That count is not a stable oracle: data variables appear for reasons unrelated to this feature and on environment-dependent timing -- e.g. stack-variable annotation defines a null-pointer data variable at 0x0, and analysis materializes data variables at reference sites -- and background analysis is often still running when the baseline is captured. This made the test fail on some CI machines while passing locally. Track the feature's own data variables by address instead: the addresses where the load introduced symbols (minus any that already had a data variable). Assert those all gain a data variable on load, are all gone after removal, and are all recreated on re-load. Data variables that exist for unrelated reasons are ignored by construction. On failure, the assertion message lists the offending addresses with their type and symbols. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the same treatment to the symbol check that was applied to the data variable check: compare the feature's own symbol addresses rather than the global symbol count. The global count drifts with symbols created for unrelated reasons (analysis, stack-variable annotation) and with background analysis still settling when the baseline is captured -- stable on Linux but off by one on Windows. On failure the assertion message names the leftover symbols so a genuine removal leak can still be diagnosed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xusheng6
force-pushed
the
test_load_module_symbols
branch
from
July 9, 2026 19:57
9937779 to
999bff8
Compare
Document the new on-demand backend-symbol feature in the user guide: the Module Widget's Symbols column and Load/Reload/Remove Symbols context-menu actions, and the Python API (load_symbols_for_module / load_symbols_for_all_modules / remove_symbols_for_module / remove_all_loaded_symbols / modules_with_loaded_symbols). Reframe the "Listing Symbol At/Near an Address" section now that reading symbols from the backend is supported. Adds screenshots of the Module Widget (with the Symbols column and the context menu) and the loaded symbols in the Symbols sidebar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the ability to load a module's symbols from the debugger backend on demand, surfaced through the Modules widget (Load / Reload / Remove Symbols, and all-module variants) and the debugger API. Closes #210.
By default no backend symbols are loaded. The user requests them per module (or for all modules); each symbol is added as an auto symbol with a data variable at its address so it renders in the views. The added symbols are tracked so they can be removed precisely, and they are cleaned up automatically when the target is gone.
Contents
LoadSymbolsForModule/LoadSymbolsForAllModules/RemoveSymbolsForModule/RemoveAllLoadedSymbols, plus per-module tracking and counts, inDebuggerController.GetSymbolsForModule/DebugAdapterSupportSymbols).test_load_module_symbolscovering the add/remove counts, the load/remove/load data-variable regression, and idempotent re-loading.Analysis-refresh fix (latest commit)
Symbols were being defined while function-analysis updates were disabled, with no analysis pass kicked afterward — so a freshly loaded symbol appeared in the symbols view but rendered "bare" (its data variable was never processed into the views) until a manual refresh. The load and remove paths now call
UpdateAnalysis()after the disabled-update window. The target-gone teardown path (FinalizeTargetGoneCleanup) passesupdateAnalysis = falseso it does not schedule an async pass that could read from the debugger memory region as it is torn down.Test
test_load_module_symbolspasses against a locally built core (LLDB / x86_64 Linux).🤖 Generated with Claude Code