Skip to content

fix(native): resolve symbols from split-debug files on Linux#1836

Open
tustanivsky wants to merge 6 commits into
masterfrom
fix/linux-daemon-split-debug-symbols
Open

fix(native): resolve symbols from split-debug files on Linux#1836
tustanivsky wants to merge 6 commits into
masterfrom
fix/linux-daemon-split-debug-symbols

Conversation

@tustanivsky

@tustanivsky tustanivsky commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

This PR fixes invalid stacktraces produced by the crash daemon on Linux for binaries that were stripped of their .symtab - the standard layout for release builds that ship a separate debug file (e.g. Unreal Engine, which runs objcopy --strip-all post-link and stores symbols in a .gnu_debuglink companion).

Since #1764, the daemon resolved frame names by picking the nearest preceding entry from the module's .dynsym without bounding the match by st_size. For stripped binaries the dynamic table covers only exported symbols (small fraction of all functions in a UE title), so most addresses fall into gaps between exported symbols and get attributed to unrelated neighboring functions.

Key Changes

  • Bound symbol matches by st_size: a symbol only matches when the target address lies within [st_value, st_value + st_size). An unresolved frame (bare instruction_addr) beats a wrong name.
  • Prefer .symtab over .dynsym: the full table is a superset when present; the previous preference order was backwards for executables.
  • Follow .gnu_debuglink: when the module has no .symtab, search for the split-debug companion per the GDB conventions (<dir>/<name>, <dir>/.debug/<name>, /usr/lib/debug/<dir>/<name>) and read its full symbol table. Candidates are validated by GNU build-id equality; when either file lacks a build-id, the candidate is accepted without CRC verification.
  • Keep it cheap at crash time: the resolved symbol-table source is cached per module, and tables are scanned in fixed-size chunks with only the winning name read from the string table - large debug companions are never loaded into memory wholesale.

No changes to unwinding: the frame addresses were already correct (pre-captured DWARF backtrace), only their naming was wrong. #1747's remote unwinding for non-crashed threads is untouched (libunwind already bounds its lookups internally).

Testing

Checked manually against UE 5.7.4 sample project build on Ubuntu 26.04 LTS:

Related items

The crash daemon resolved frame symbols by picking the nearest preceding
entry from the module's .dynsym without bounding the match by st_size.
For binaries stripped of their .symtab (the norm for release builds that
ship a split-debug companion), most addresses fall into gaps between
exported symbols and got attributed to unrelated neighboring functions,
producing plausible-looking but wrong stacktraces.

- Require the target address to lie within [st_value, st_value + st_size)
  instead of accepting the nearest preceding symbol; an unresolved frame
  beats a wrong name.
- Prefer .symtab over .dynsym (it is a superset when present).
- When the module has no .symtab, follow the .gnu_debuglink section per
  the GDB split-debug conventions (<dir>/<name>, <dir>/.debug/<name>,
  /usr/lib/debug/<dir>/<name>), validating candidates by GNU build-id
  equality, and read the full symbol table from the companion file.
- Cache the resolved symbol-table source per module and scan the table in
  fixed-size chunks, reading only the winning name from the string table,
  so large debug companions are never loaded into memory wholesale.
@tustanivsky tustanivsky force-pushed the fix/linux-daemon-split-debug-symbols branch from 4f51e99 to 190421f Compare July 3, 2026 09:14
@tustanivsky tustanivsky marked this pull request as ready for review July 3, 2026 09:15

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 190421f. Configure here.

Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Comment thread src/backends/native/sentry_crash_daemon.c
The symbol-source cache was a bounded 64-slot table keyed by module
name; once full, further modules got no symbolication. Direct-map it by
module index instead, sized to the module cap. Being BSS, only touched
slots are committed, and the name key (now redundant) is dropped.
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.

1 participant