Skip to content

feat(native): symbolicated stacktraces for non-crashed threads on Linux#1747

Open
jpnurmi wants to merge 30 commits into
masterfrom
jpnurmi/feat/native/libunwind-remote
Open

feat(native): symbolicated stacktraces for non-crashed threads on Linux#1747
jpnurmi wants to merge 30 commits into
masterfrom
jpnurmi/feat/native/libunwind-remote

Conversation

@jpnurmi
Copy link
Copy Markdown
Collaborator

@jpnurmi jpnurmi commented May 22, 2026

Add libunwind remote unwinding support to the native backend's crash daemon, enabling DWARF-based stack walking and symbol name resolution for non-crashed threads on Linux. Since #1764, only the crashing thread received a stacktrace; all other threads appeared without one.

  • Re-add upstream libunwind src/ptrace/ (_UPT_* accessors)
  • Add unwind_remote CMake target with G-prefix + ptrace sources, only built when SENTRY_BACKEND=native on Linux
  • New sentry_remote_unwind.c using unw_init_remote() + unw_get_proc_name()
  • Integrate into daemon's build_stacktrace_for_thread() with fallback to pre-captured backtrace and FP-walking
Before After
before after

See also:

Add libunwind remote unwinding support to the native backend's crash
daemon, enabling DWARF-based stack walking and symbol name resolution
for all threads (not just the crashing thread) on Linux.

- Re-add upstream libunwind src/ptrace/ (_UPT_* accessors)
- Add `unwind_remote` CMake target with G-prefix + ptrace sources,
  only built when SENTRY_BACKEND=native on Linux
- New sentry_remote_unwind.c using unw_init_remote() + unw_get_proc_name()
- Integrate into daemon's build_stacktrace_for_thread() with fallback
  to pre-captured backtrace and FP-walking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Comment thread src/backends/native/sentry_crash_daemon.c
jpnurmi added 2 commits May 25, 2026 09:34
Only free the captured stack buffer once a remote-unwind stacktrace is
actually returned. If remote unwinding yields frames but all are filtered,
the fallback stacktrace paths still need to own and clean up the buffer.
Avoid reserving the remote unwind frame buffer on the daemon stack. If the
allocation fails, remote unwinding is skipped and the existing fallback
stacktrace paths continue to run.
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c
jpnurmi added 4 commits May 25, 2026 11:05
Move the Linux remote libunwind implementation under src/unwinder and route it
through sentry_unwinder.c, matching the existing local unwinder backend
selection pattern.

Expose a backend-neutral sentry__unwind_stack_from_thread() entry point for the
daemon while keeping the libunwind-specific implementation private to the
backend file.
Skip remote thread unwinding for the crashed Linux thread when the signal
handler already captured a backtrace. This keeps the signal-frame-aware
ucontext unwind as the primary stacktrace and avoids replacing it with a
potentially partial remote unwind from the handler wait state.

Make sentry_unwinder.h self-contained now that sentry_unwinder.c includes it
for remote unwinder dispatch.
Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c
Document that frame trust tracks the unwind source rather than the emitted
frame index, so filtered initial frames do not cause subsequent CFI frames to
be labeled as context frames.
Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c Outdated
jpnurmi added 4 commits May 25, 2026 12:09
Skip daemon-side ptrace unwinding for the crashed Linux thread so
PTRACE_DETACH cannot resume it while crash processing is still running.
The crashed thread continues to use the saved fault context and any
pre-captured backtrace.
After attaching to a thread, require waitpid() to report a stopped
tracee before entering libunwind's remote unwinder. This avoids calling
unw_init_remote() when the attach/wait race reports a non-stopped thread.
Add the arm32 generic libunwind sources and target definitions to the
vendored unwind_remote target so daemon-side remote unwinding is built
consistently with the local arm32 unwinder.
The regular sentry target compiles the Linux thread-unwind dispatcher
without the daemon-only remote unwinder backend enabled. Mark the
dispatcher arguments as intentionally unused so Clang -Werror builds do
not fail in that configuration.
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Non-crashed Linux threads discovered by the daemon only have a TID and a
zero-filled ucontext. Do not attach a registers object to successfully
remote-unwound stacktraces for those threads.
Comment thread src/backends/native/sentry_crash_daemon.c
Capture register values from the initial libunwind cursor for remote
Linux stack walking and attach them to the produced stacktrace.

Keep the remote thread unwinder API aligned with sentry_unwind_stack by
returning the frame count directly, with registers passed as optional
side metadata.
@jpnurmi jpnurmi changed the title wip(native): add remote DWARF unwinding + symbol names for Linux feat(native): add remote DWARF unwinding + symbol names for Linux May 25, 2026
@jpnurmi jpnurmi requested a review from supervacuus May 26, 2026 08:38
Copy link
Copy Markdown
Collaborator

@supervacuus supervacuus left a comment

Choose a reason for hiding this comment

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

LGTM. Just one documentation feedback and one build-cleanup.

Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c Outdated
Comment thread vendor/libunwind/src/ptrace/_UPT_access_reg.c Outdated
@jpnurmi jpnurmi changed the title feat(native): add remote DWARF unwinding + symbol names for Linux wip(native): add remote DWARF unwinding + symbol names for Linux May 28, 2026
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread CMakeLists.txt
jpnurmi added 3 commits May 28, 2026 15:32
When thread_idx == 0, tid was unconditionally set to ctx->crashed_tid,
causing is_crashed_thread to be true. But the caller only invokes
build_stacktrace_for_thread(ctx, 0) for non-crashed threads, so remote
DWARF unwinding was incorrectly skipped for the first thread in the
list whenever it wasn't the crashing thread. Remove the || thread_idx == 0
condition so thread 0 resolves its own tid from platform.threads[0].tid
like every other non-crashed thread.
When SENTRY_LIBUNWIND_SYSTEM is used, the daemon's remote DWARF
unwinding code needs _UPT_* ptrace accessor symbols from the separate
libunwind-ptrace library, but only the core libunwind was linked.
Add a pkg_check_modules for libunwind-ptrace and link it alongside
PkgConfig::LIBUNWIND.
…hardcoding

The vendored libunwind's config.h.cmake.in hardcoded all HAVE_DECL_PT_*
symbols to 0 and added a comment claiming they were "not available on
Linux". On modern Linux kernels with CONFIG_COMPAT, PT_GETREGS is in
fact available via <sys/ptrace.h>, so the cmake build should detect it.

- Replace hardcoded `#define ... 0` with `#cmakedefine01` in
  config.h.cmake.in so the values come from cmake detection
- Add a check_c_source_compiles loop in CMakeLists.txt for the PT_*
  declarations, matching the existing PTRACE_* pattern
- Switch _UPT_access_reg.c from `#if defined(HAVE_DECL_PT_GETREGS)`
  to `#if HAVE_DECL_PT_GETREGS` so that a value of 0 correctly
  disables the code path (defined() treats any definition, even 0,
  as true)
Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c Outdated
The blocking waitpid(tid, ..., __WALL) after PTRACE_ATTACH can hang
indefinitely if the target thread is in uninterruptible sleep (D state).
Replace it with a WNOHANG-based polling loop (50 × 100ms = 5s timeout)
so the daemon logs a warning and continues instead of blocking forever.
@jpnurmi jpnurmi changed the title wip(native): add remote DWARF unwinding + symbol names for Linux wip(native): symbolicated stacktraces for non-crashed threads on Linux May 28, 2026
Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c
- **Normal case**: waitpid returns on the first iteration (microseconds),
  so 20 retries add zero overhead vs 50.
- **Transient D state**: I/O or page-fault wait usually resolves in
  well under 1s. 2s covers pathological cases without compounding
  latency for the user.
- **Crash report latency**: The daemon holds all other threads in
  SIGSTOP while unwinding. Every extra second frozen delays the crash
  report and makes the perceived hang worse.
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c
@jpnurmi jpnurmi changed the title wip(native): symbolicated stacktraces for non-crashed threads on Linux feat(native): symbolicated stacktraces for non-crashed threads on Linux May 28, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 28, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 8312214

Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c
Comment thread src/backends/native/sentry_crash_daemon.c
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 79f17f0. Configure here.

Comment thread src/unwinder/sentry_unwinder_libunwind_remote.c
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.

2 participants