Skip to content

Comments

vendor libunwind for Linux#1523

Merged
supervacuus merged 11 commits intofix/split_inproc_handler_threadfrom
vendor_libunwind
Feb 17, 2026
Merged

vendor libunwind for Linux#1523
supervacuus merged 11 commits intofix/split_inproc_handler_threadfrom
vendor_libunwind

Conversation

@supervacuus
Copy link
Collaborator

evaluate vendor libunwind with a minimal Linux cmake setup for Linux x86, x86_64 and aarch64

#skip-changelog

@supervacuus
Copy link
Collaborator Author

@jpnurmi, this is good to go for a downstream test. If everything works, I will reduce what we vendor here further.

jpnurmi added a commit to getsentry/sentry-dotnet that referenced this pull request Feb 17, 2026
@jpnurmi
Copy link
Collaborator

jpnurmi commented Feb 17, 2026

Looks good! CI passed with vendored libunwind:
https://github.com/getsentry/sentry-dotnet/actions/runs/22090351731/job/63833997711

@supervacuus supervacuus marked this pull request as ready for review February 17, 2026 16:00
Copy link

@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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

check_include_file("sys/stat.h" HAVE_SYS_STAT_H)
check_include_file("sys/param.h" HAVE_SYS_PARAM_H)
check_include_file("sys/procfs.h" HAVE_SYS_PROCFS_H)
check_function_exists(dl_iterate_phdr HAVE_DL_ITERATE_PHDR)
Copy link

Choose a reason for hiding this comment

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

libunwind feature check misses libdl linkage

Medium Severity

check_function_exists(dl_iterate_phdr HAVE_DL_ITERATE_PHDR) runs before adding ${CMAKE_DL_LIBS}, so the probe can fail on platforms where dl_iterate_phdr is in libdl. That leaves HAVE_DL_ITERATE_PHDR unset even though the function exists, and libunwind then disables its iterate_phdr path in generated config.h.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nope, none of the supported Linux setups have dl_iterate_phdr in libdl, but in libc.so (even bionic, which currently doesn't deploy with libunwind as the default unwinder).

The ${CMAKE_DL_LIBS} linkage is needed for dlopen/dlsym/dladdr, not for dl_iterate_phdr.

@supervacuus
Copy link
Collaborator Author

@supervacuus supervacuus merged commit 8351ec2 into fix/split_inproc_handler_thread Feb 17, 2026
42 of 43 checks passed
@supervacuus supervacuus deleted the vendor_libunwind branch February 17, 2026 16:53
jpnurmi added a commit to getsentry/sentry-dotnet that referenced this pull request Feb 18, 2026
supervacuus added a commit that referenced this pull request Feb 18, 2026
* fix: split inproc with a handler thread

* prevent warning on write() return value

* get rid of unsused dispatch parameter

* and another unused return value for write()

* fix trivial windows compile def issues to run tests locally

* make tests a bit less brittle

* ensure that find_cp_path isn't built on windows.

* clean up backends

* use `std::nothrow` `new` consistently to keep exception-free semantics for allocation
* rename static crashpad_handler to have no module-public prefix
* use `nullptr` for arguments where we previously used 0 to clarify that those are pointers
* eliminate the `memset()` of the `crashpad_state_t` initialization since it now contains non-trivially constructable fields (`std::atomic`) and replace it with `new` and an empty value initializer.

* eliminate local handler strategy declaration in signal handler

* turn off painful warning noise

* add libunwind to the CI dependencies

* ensure we build the example with PIE disabled when doing a static build, since libraries like libunwind.a might be packaged without PIC.

* extend handler crash to windows

* further clean of inproc

* review and fix/remove remaining TODOs from branch changes

* eliminate multichar warning in crashpad backend when using GCC

* ensure libunwind in benchmark and codeql workflows

* fix Linux ARM64 build (warning in libunwind)

* provide an x86_64 ucontext stackwalker for macOS

* bump lower-end GCC to 10.5.0

* remove obsolete ASAN patch from CI

* ensure lower-end GCC also finds libunwind

* disable LTO for the example too

* use clang diagnostics only for __clang__

* install 32-bit libunwind package for 32-bit Linux CI test config

* fix weird linker asymmetry

* provide empty path-suffix so that CMake can find libunwind on platforms with architecture prefixes (32-bit Linux)

* remove can_lock mechanism from the handler block API

* use PRIx64 without ull cast in sentry__value_new_addr()

* fix off-by-one bug when returning the trace length after walking the stack

also ensure to get the first frame
harmonize libunwind usage

* clean up cmake configure stage in pytest

* skip tests if zlib wasn't found during cmake configure stage

* fix stupid comparison error that led to all failing Linux tests

* exclude `logger_level` from transport unit tests.

* let find_path search the library architecture

* support i386 multilib on Ubuntu/Debian

* add 32-bit lzma package to Linux 32-bit build

* typo

* make even more elaborate multilib triplet and suffice handling based on FORCE32

* be even more careful wrt to max_frames in the fp walker and check on every access  for overrun (+ track with while instead for-looping)

* eliminate the non-atomic access to g_handler_thread_ready from has_handler_thread_crashed

* bring spinlock and pageallocator into the atomic fold

* fix: add GNU-stack note to crashpad_info_note.S

* update crashpad

* remove deprecated macOS 13 and replace it with macOS 26

* update crashpad after merging to getsentry

* simplify signal handler blocker by leaving and entering around the dispatch to the handler thread

* remove left-over handler switch

* reintroduce handling thread into cleaned up signal blocker for the remaining backends

* add valgrind suppression for false-positive deep in libunwind

* track failing ARM64/Linux clang-tsan build across available toolchain versions

* let valgrind match any frame between the libunwind obj matcher and the error site.

* move the check for a crashed handler thread into dispatch_ucontext() where we execute the unsafe part directly in the signal-handler if the handler thread is dead as a last chance report

* isolate the libunwind walker as a tsan cause in CI

* exclude walker variables to prevent unused variables Werror

* isolation pinpoint fine, but only exclude when using ucontext.

* silence clang-20 warning for crashpad

* update `crashpad` after merging to `getsentry`

* we know the walk crashes in aarch64 tsan, now introduce stack bound reader in the libunwind walker for Linux and log as much as possible to understand where the actual crash happens

* format

* add unistd.h for musl

* eliminate the logs and ensure we never walk the callers if the SP is in unmapped memory

* format

* fix invalid `find_mem_range()` return value check

* remove macOS left-overs in the `libbacktrace` unwinder module

* extract fp_walk for the macOS unwinder so we can also provide a stack-trace from an arbitrary frame-pointer

* for targets that must use `backtrace()` as an unwinder we fall back und running the deferred code directly inside the signal handler. Nothing changes for them.

* instead of spinning on atomic in the signal-handler add ACK pipe/semaphore on the return channel and let the OS block and wait.

Also check the return value of startup_handler_thread in the initialization and propagate the failure.

* actually check the FP against mach_vm_region bounds in the validation

* check mach_vm_region bounds only on macOS builds

* ensure we conditionally return on acquire in block_for_signal_handler

* move chain-first strategy completely outside the signal handler reentrancy guard

* up to now, we've been serializing signal handling even though we didn't know whether it was a runtime signal or one we should be handling
* this meant that we blocked all our critical sections during a managed exception
* it also meant that we blocked any concurrent managed exceptions
* it also meant that we introduced a race window during the time when we chained, because incoming signal on other threads would have gotten next in line, before we even completed the current signal handler

by moving it completely outside our synchronization we truly chain at start and don't interfere until we know we must.

* update changelog

* add inproc module-level docs for developers

* fix: return the number of frames when doing a stack walk from an FP directly.

* clean up inline docs

* replace iOS build inproc for crashpad

* clean up PAC stripping and actually test it in an arm64e build config

* bulk commit of local branches:

* introduces state-machine for in-progress crash handling vs other crashing threads
* extract async-safe logging macros
* adds arm64e support PAC registers (access to opaque fp, lr, sp, and pc)
* skip on_crash if we handle a failing handler thread (TODO: also before_send)
* handle failed pipe and semaphore signaling to handler thread
* introduce even more fall backs to handling inside the signal handler where it makes sense (i.e. failed attempt to signal handler thread)

* gate UB read in the mac libunwind stack walker

* after switching to nothrow new in the crashpad database clean up we actually have to check return values.

* revert ci build for ios back to inproc (no clue why this was switched)

* remove stupid copypasta from ci workflow

* exclude breakpad from the arm64e tests on macOS

* strip PAC also from unit-test function pointers

* add inproc concurrent crash stress test

* split out build_config.py from cmake.py so we can build and run new integration test executables with the same parametrization as the core artifacts and "example".

* look up pthread correctly using find_package() rather than assuming pthread exists as a separate library

* allow the inproc stress tests to run on Android using adb shell

* make the inproc output test assertions a bit more defensive

* deduplicate CMAKE_DEFINES from build_config refactor

* print macOS architecture in CI

* bump PAC runner to macOS-26 (since macOS-15 counter to docs is x86-64)

* don't run the inproc stress tests via adb shell in CI, it is just too unstable... it works great locally and should remain for local testing and debugging though.

* mark test_inproc_handler_thread_crash flaky for now

* fix Windows build issue (noinline)

* ensure that on Windows the inproc stress test executable placed directly in the build directory

* provide a more comprehensive solution to recursive crash scenarios:

* reintroduce unblocked signal handling thread to sentry__enter_signal_handler()
* but also add a counter that tracks how often that thread entered the signal handler
* and(!) configure all signal handlers as SA_NODEFER to allow recursive detection
* add additional tests and test hook-points to both crash and abort() from within the signal handler
* reintroduce the skip_hook idea into inproc, where a handler on the second recursive crash no longer calls on_crash or before_send

* make the inproc stress tests widechar path friendly

* do not set SA_NODEFER for SIGABRT

* introduce sigaltstack also to the handler thread

and set up a sigabrt handler for Windows, so inproc can handle abort()

* disable abort pop up on windows which blocks the test in CI indefinitely

* disable any stdio logging in inproc signal handler fallback paths.

* don't allow recursive abort(), allowing a crash handler to handle a crash in the handler that is coming from an abort is extremely unstable and why might extend that to other signals as well, but those typically come from the kernel as hard-fault translations. However abort() comes from libc infra and is far from safe to keep running.

We always assumed reports for crashed handlers to be a best effort topic, now we have way to detect those in the recursion and can act on particularly bad crashes in the handler to just skip.

* guard the chain at start strategy against SIGABRT

* disable the abort dialog for Win32 in the example when abort() is triggered

* extract abort check into a function so we can handle UNIX/Windows differences there

* ensure handler thread started before we crash

* assume that inproc stress tests output UTF-8 on Windows and enable replace error handling

* temporarily disable CI gate for inproc stress on android

* use a time-based timeout for the init wait on inproc handler_thread_ready

* permanently disable CI gate for inproc stress on android

* clean up changelog

* temporarily remove SIGABRT recursion gate

* update changelog

* also hide is_abort() for all Werror builds

* delete data in crashpad_backend_free() rather than sentry_free since it was allocated with new.

* add signal-safe address formatter

* remove `abort()` recursion-gate completely

* update README.md to remove inproc exceptions.

* ensure top-frames are put verbatim into the stack-trace

* add libunwind as build dependency for inproc to the CONTRIBUTING.md

* Terminate abort() if only SIG_DFL or SIG_IGN are left.

* improve report by explicitly providing a string for STATUS_FATAL_APP_EXIT

* fully clean up after a handler thread start timed out

* reset previous SIGABRT handler on Windows unconditionally

* handle frame duplication when LR matches the LR in current frame record

* switch compile guard to only __APPLE__ because that is how we use it

* don't let the stack trace tests run on android (check for sys.platform is not enough, because the android tests run on macOS runners)

* On x86_64, without an LR register, there's no mechanism for the FP-based unwinder to recover a frame when the callee has no frame
 record. So, lets limit that particular test-case to aarch64.

* vendor libunwind for Linux (#1523)

* remove return value from sentry__addr_to_string()

* vendor libunwind with a minimal Linux cmake setup for Linux x86, x86_64 and aarch64

* set CMAKE_SYSTEM_PROCESSOR=i686 for SENTRY_BUILD_FORCE32

* build libunwind with C11 target props

* fix the source instead of setting C11 on target

* move unw_context_t out of the else block.

* enable ASM for libunwind

* remove the ASM in ENABLED_LANGUAGES check for the breakpad build

* remove unused files

* document all changes

* also get rid of humongous autoreconf generated release artifacts

* remove no-pie/no-lto options from example entirely

* Update CONTRIBUTING.md to remove libunwind note

Removed note about 'libunwind' requirement for Linux.

* Revise CHANGELOG for breaking changes

Update CHANGELOG with breaking changes and new features.

* test: eliminate `llvm-cov` flag duplication (cmake.py vs build_config.py)

* Changed depth >= 2 to `skip_hooks` in the Unix pipe-failure fallback path
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