Skip to content

feat(native): Add Android support for out-of-process crash handling - #1725

Open
jpnurmi wants to merge 4 commits into
v1from
jpnurmi/feat/native-android
Open

jpnurmi wants to merge 4 commits into
v1from
jpnurmi/feat/native-android

Conversation

@jpnurmi

@jpnurmi jpnurmi commented May 15, 2026 •

Copy link
Copy Markdown
Collaborator

Warning

WIP    🚧🔨⏳⛔

sequenceDiagram
    participant App
    participant Service
    participant Android
    participant Sentry
    App->>Service: Start at SDK init
    Note over App: Native crash
    App->>Service: Notify crash
    activate App
    Service->>App: Capture crash
    deactivate App
    activate Service
    App->>Android: Propagate crash
    Note over Android: Write tombstone
    Android-xApp: Terminate process
    deactivate Service
    Note over Service: Merge tombstone
    Service->>Sentry: Send report
Loading

See also:

Close: #2115

Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread ndk/lib/src/main/jni/sentry_android_transport.c Outdated
Comment thread ndk/lib/build.gradle.kts Outdated
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 7ea304b to 11f94c3 Compare May 19, 2026 13:36
Comment thread src/backends/native/sentry_crash_ipc.c Outdated
@jpnurmi jpnurmi changed the title WIP: feat(native): Add Android support POC: feat(native): Add Android support May 19, 2026
Comment thread ndk/gradle.properties Outdated
Comment thread ndk/gradle.properties Outdated
Comment thread src/backends/native/sentry_crash_ipc.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_handler.c Outdated
Comment thread src/backends/native/sentry_crash_handler.c Outdated
Comment thread src/backends/native/sentry_crash_handler.c
Comment thread src/transports/sentry_transport_stdout.c Outdated
Comment thread src/transports/sentry_transport_stdout.c
@jpnurmi jpnurmi changed the title POC: feat(native): Add Android support poc(native): Add Android support for out-of-process crash handling May 20, 2026

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

Stale Bugbot comment from a previous run.

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_ipc.c

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
@linear-code

linear-code Bot commented Sep 23, 2026

Copy link
Copy Markdown

NATIVE-231

@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from d9b9c4f to 9a72f7e Compare October 20, 2026 10:42

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 9a72f7e to 8a8f24f Compare October 20, 2026 11:03

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
@jpnurmi
jpnurmi removed this pull request from stack #2214 October 20, 2026 16:58
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from ede6e89 to fb847b2 Compare October 20, 2026 17:24
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 7ad144a to 9abff94 Compare October 28, 2026 16:11
@jpnurmi
jpnurmi changed the base branch from v1 to jpnurmi/feat/libunwindstack-remote October 28, 2026 16:14
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 73c0dcf to 39ea704 Compare October 28, 2026 17:11
@jpnurmi
jpnurmi changed the base branch from jpnurmi/feat/libunwindstack-remote to jpnurmi/feat/native-preload October 28, 2026 17:11
jpnurmi added a commit that referenced this pull request Oct 29, 2026
* feat(native): Support ELF modules at non-zero file offsets

The minidump writer and `capture_modules_from_proc_maps()` grouped
`/proc/<pid>/maps` entries by file name and expected an ELF header at
offset 0. An ELF image can also start inside a larger file, such as an
uncompressed library that Android maps straight from an APK, and such
images were dropped from the module list.

Start a module at each mapping whose file offset holds an ELF header,
group the image's other segments by its load range, and read the build
ID, size, and SONAME at that offset. Move
`compute_elf_size_from_phdrs()` to `sentry_elf.h` to share it.

This prepares the native backend for Android, where AGP's default
packaging maps every app library from the APK.

Ref: #1725

* fix(native): Apply the ELF offset to the build ID section headers

The previous commit adjusted the section header offset in
`elf_locate_symtab()` instead of `extract_elf_build_id_for_module()`,
where `elf_offset` is not declared, so the crash daemon failed to
compile. Move the adjustment to where the build ID is read.

* fix(native): Keep embedded ELFs mapped below their file offset

The `offset > start` guard in `capture_modules_from_proc_maps()` dropped
mappings before an ELF image inside a larger file could be detected.
The guard only protects the `start - offset` load base of a regular ELF
file, so apply it there instead.
jpnurmi added a commit that referenced this pull request Oct 29, 2026
* feat(native): Support ELF modules at non-zero file offsets

The minidump writer and `capture_modules_from_proc_maps()` grouped
`/proc/<pid>/maps` entries by file name and expected an ELF header at
offset 0. An ELF image can also start inside a larger file, such as an
uncompressed library that Android maps straight from an APK, and such
images were dropped from the module list.

Start a module at each mapping whose file offset holds an ELF header,
group the image's other segments by its load range, and read the build
ID, size, and SONAME at that offset. Move
`compute_elf_size_from_phdrs()` to `sentry_elf.h` to share it.

This prepares the native backend for Android, where AGP's default
packaging maps every app library from the APK.

Ref: #1725

* fix(native): Apply the ELF offset to the build ID section headers

The previous commit adjusted the section header offset in
`elf_locate_symtab()` instead of `extract_elf_build_id_for_module()`,
where `elf_offset` is not declared, so the crash daemon failed to
compile. Move the adjustment to where the build ID is read.

* fix(native): Keep embedded ELFs mapped below their file offset

The `offset > start` guard in `capture_modules_from_proc_maps()` dropped
mappings before an ELF image inside a larger file could be detected.
The guard only protects the `start - offset` load base of a regular ELF
file, so apply it there instead.

* feat(unwinder): Unwind remote threads with libunwindstack

The crash daemon could only unwind the threads that did not crash with
`libunwind_remote`, which Android builds lack because they use
libunwindstack. Add a `libunwindstack_remote` backend, compiled in only
with the native backend, that attaches to a thread with ptrace, reads
its registers with `Regs::RemoteGet()`, and unwinds it. Share the
ptrace attach loop with `libunwind_remote`.

This prepares the native backend for Android.

* feat(native): Support preloading the crash handler

`sentry__backend_preload()` was a no-op for the native backend. Install
its signal handlers early, so that they keep their place in the signal
chain ahead of a managed runtime, and let a later `sentry_init()` adopt
them. Like with `inproc`, shutdown keeps the preloaded handlers
installed, forwarding crashes to the previous handlers until the next
initialization.
Base automatically changed from jpnurmi/feat/native-preload to v1 October 29, 2026 12:53
jpnurmi added a commit that referenced this pull request Oct 29, 2026
…2273)

Allow `SENTRY_HANDLER_STRATEGY_CHAIN_AT_START` in the native backend on
Android, as in the inproc backend, so that a signal that Mono turns into
a managed exception, such as a `NullReferenceException`, is not captured
as a native crash.

As in the inproc backend, fall back to the default strategy below API
26, where debuggerd kills the process after the chained handler runs.

This prepares the native backend for Android support (#1725).
jpnurmi added a commit that referenced this pull request Oct 29, 2026
Add `sentry__http_transport_set_dump_failed()`. When enabled, a network
error makes the HTTP transport write the envelope to the run folder,
like the shutdown dump does, instead of queuing it for retry or
discarding it, so that the next launch sends it.

This prepares the Android crash daemon to keep crash envelopes that
fail to upload (#1725).
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 39ea704 to ef29393 Compare October 29, 2026 18:22

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
Add `sentry__http_transport_set_dump_failed()`. When enabled, a network
error makes the HTTP transport write the envelope to the run folder,
like the shutdown dump does, instead of queuing it for retry or
discarding it, so that the next launch sends it.

This prepares the Android crash daemon to keep crash envelopes that
fail to upload (#1725).
Run the crash daemon on Android in one of two ways. With the Sentry
Android SDK, its crash service hands the IPC fds to the app with
`sentry_android_crash_daemon_init()` and runs the daemon from
`libsentry.so` with `sentry_android_crash_daemon_run()`. Otherwise, the
backend forks and execs `libsentry-crash.so` next to `libsentry.so`,
like the `sentry-crash` executable on Linux.

Share the Linux crash IPC, but back the shared memory with a file in
the database directory and guard initialization with a mutex, because
bionic has no `shm_open()` or `sem_open()`. Before notifying the
daemon, the crash handler makes the app dumpable and allows any
ptracer, as debuggerd does, because the kernel otherwise denies the
daemon `ptrace`, `process_vm_readv` and `/proc` access to
non-debuggable apps.

The service-hosted daemon writes the crash envelope to the run folder
and releases the crashed process so that the platform can write a
tombstone. It returns a handle that defers the upload to the service,
which can merge the tombstone into the envelope first, and keeps the
envelope for the next launch if the upload fails.

The native backend on Android is experimental.
Add the opt-in `sentry-native-ndk-outproc` package, which builds the
same Java and JNI sources as `sentry-native-ndk` with the experimental
native backend instead of inproc. Both provide `libsentry.so` and must
not be used together. Android SDK apps select it through the Sentry
Gradle plugin.

Expose the crash daemon to the sentry-java crash service through
`SentryNdk.initCrashDaemon()` and `SentryNdk.runCrashDaemon()`. The
returned `CrashDaemon` owns the daemon handle. `sendEnvelope()` uploads
the possibly tombstone-merged envelope and reports whether it was sent,
kept for the next launch or neither, and `close()` discards the
envelope if nothing was sent.

Upload through a `sentry_http_client_t` that calls `HttpURLConnection`
over JNI, so the crash service uses the platform network and TLS stack
instead of a curl build. Use `SENTRY_CRASH_REPORTING_MODE_NATIVE`, like
inproc, because a minidump takes precedence over the native stacktrace
during processing and makes crash envelopes about 3 MB.

Package `libsentry-crash.so` only for the instrumented tests, which
fork/exec it, and run them in the Android CI job.
Add an Android job to the e2e workflow. It builds a fixture app against
the local native and Java SDKs in an isolated Maven repository and runs
it on an API 36 emulator.

Verify native crash events with and without tombstone merging, for
debuggable and non-debuggable builds, including threads, debug images,
user/release metadata, and Java/native frames in merged stacks. Verify
that a process that a broadcast starts in the background gets the crash
service, and that the service survives more than a minute in the
background. The fixture uses default packaging, so native libraries
stay inside the APK as in consuming apps.

Skip the standalone e2e tests on Android, since they cannot send events
without the http transport.
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from ef29393 to 8ecb9a7 Compare October 30, 2026 07:03

@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 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

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 8ecb9a7. Configure here.

} else {
sentry_envelope_free(envelope);
envelope = NULL;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Standalone Android crashes drop envelopes

High Severity

After writing the crash envelope, the Android path always waits for a {envelope}.merged tombstone file and then frees the original envelope if that file never appears. The service-hosted daemon can defer upload to Java, but the standalone daemon (daemon is NULL) has no merger, so the wait times out and sentry__capture_envelope is skipped. Out-of-process Android crashes therefore never leave the daemon over HTTP.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8ecb9a7. Configure here.

Comment thread ndk/lib/src/main/jni/sentry.c

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1 Targets 1.0 (v1 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant