Skip to content

Load module symbols from the debugger backend on demand (#210) - #1115

Merged
xusheng6 merged 10 commits into
devfrom
test_load_module_symbols
Jul 9, 2026
Merged

Load module symbols from the debugger backend on demand (#210)#1115
xusheng6 merged 10 commits into
devfrom
test_load_module_symbols

Conversation

@xusheng6

@xusheng6 xusheng6 commented Jul 8, 2026

Copy link
Copy Markdown
Member

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

  • Core: LoadSymbolsForModule / LoadSymbolsForAllModules / RemoveSymbolsForModule / RemoveAllLoadedSymbols, plus per-module tracking and counts, in DebuggerController.
  • Adapters: backend symbol reading for the LLDB, DbgEng, and Windows-native adapters (GetSymbolsForModule / DebugAdapterSupportSymbols).
  • FFI + Python API bindings.
  • UI: a "Symbols" column and Load/Reload/Remove actions in the Modules widget.
  • Test: test_load_module_symbols covering 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) passes updateAnalysis = false so it does not schedule an async pass that could read from the debugger memory region as it is torn down.

Note: this analysis-refresh change reduces but does not yet fully resolve the bare-symbol behavior; further work on the UI refresh path may still be needed.

Test

test_load_module_symbols passes against a locally built core (LLDB / x86_64 Linux).

🤖 Generated with Claude Code

xusheng6 and others added 9 commits July 9, 2026 15:37
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
xusheng6 force-pushed the test_load_module_symbols branch from 9937779 to 999bff8 Compare July 9, 2026 19:57
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>
@xusheng6
xusheng6 merged commit a766958 into dev Jul 9, 2026
2 checks passed
@xusheng6
xusheng6 deleted the test_load_module_symbols branch July 9, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Read symbols from the debugger backend

1 participant