Skip to content

fix(android): Preserve NDK stack on tombstone merge (JAVA-645) [on hold]#5771

Draft
runningcode wants to merge 2 commits into
mainfrom
no/java-645-tombstone-merge-fix
Draft

fix(android): Preserve NDK stack on tombstone merge (JAVA-645) [on hold]#5771
runningcode wants to merge 2 commits into
mainfrom
no/java-645-tombstone-merge-fix

Conversation

@runningcode

@runningcode runningcode commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Warning

On hold — premise corrected by a control experiment. A genuine native fault (null-deref inside libnative-sample.so) symbolicates to native-sample.cpp:13 on main without this change, and also with it. Native symbolication / uploaded symbols were never broken — the original "unreadable native crash" was an artifact of the sample's raise(SIGSEGV)-from-JNI crash (a managed-origin crash whose tombstone stack is the ART/OAT managed chain).

What this change actually does for a real native crash: main keeps the tombstone's full mixed stack (symbolicated native crash frame + the obfuscated managed call chain); this PR uses sentry-native's native-only crashing thread, which drops that call-chain context. So it is neutral-to-worse for genuine native crashes, not the win described below.

The higher-value fix is to deobfuscate the managed frames in the merged native event (they stay obfuscated only because the event platform == "native"). See JAVA-645 for the reframed analysis. Holding this PR pending that decision.


📜 Description

When io.sentry.tombstone.enable=true, a native crash that is also captured by sentry-native is reported through the ApplicationExitInfo tombstone path (mechanism: TombstoneMerged). TombstoneIntegration.mergeNativeCrashes kept the Native SDK event's contexts but overwrote its exceptions, threads, and debugMeta with the tombstone's. For JNI-raised crashes the tombstone's crashing-thread stack is the ART-unwound managed stack (OAT/VDEX frames), which cannot be symbolicated (native) or ProGuard-deobfuscated, so the merged event became unreadable.

This change keeps the Native SDK's crashing-thread stack-trace, threads and debug-meta — which are symbolicated against the uploaded debug files — and only enriches the Native SDK exception with the tombstone's signal metadata (and flags the merge via the mechanism type). It falls back to the tombstone's data when the Native SDK event carries no exception.

💡 Motivation and Context

Native crashes on affected builds were effectively unreadable: obfuscated managed frames plus a couple of system frames, with no app source lines. Uploading native symbols and the ProGuard mapping did not help, because the frames that would consume them were discarded/replaced.

Evidence from a raw TombstoneMerged event JSON:

  • Server symbolication works — libsentry.so shows debug_status: "found", and sentry-native's own worker threads symbolicate to source (sentry_backend_inproc.c:1238, sentry_batcher.c:339).
  • The crashing thread's frames were all base.vdex/base.odex/boot.oat with symbolicator_status: "unknown_image", retaining obfuscated names; libnative-sample.so was debug_status: "unused".
  • The same event still contained fully-symbolicated sentry-native worker threads while the crashing thread carried the tombstone's managed stack — direct evidence the merge overwrote the good crashing-thread data.

Reproduced on Android 12, 16 and 17 (Pixel 3, Galaxy A55, Pixel 10) and SDK 8.48.0 / 8.49.0. The sentry monolith and symbolicator behave correctly — the fix is entirely client-side.

Fixes JAVA-645

💚 How did you test it?

Manually, with the sentry-samples-android release build (SAGP enabled, native symbols + ProGuard mapping uploaded):

  • Before: io.sentry.tombstone.enable=trueTombstoneMerged, unsymbolicated / obfuscated crashing stack.
  • With io.sentry.tombstone.enable=falsemechanism: signalhandler, and the pipeline symbolicates — confirming the merge was the cause.

Automated tests for mergeNativeCrashes still need to be added/updated (see Next steps) — this is a draft.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

  • Add/adjust TombstoneIntegrationTest coverage asserting the Native SDK crashing-thread stack/threads/debug-meta are preserved and only the mechanism/signal metadata is merged.
  • Native-team review of the merge policy — in particular whether tombstone-only (no matching NDK event) crashes should retain managed frames tagged platform: java so ProGuard deobfuscation applies.

When a native crash is also captured via the ApplicationExitInfo tombstone, mergeNativeCrashes overwrote the Native SDK event's exceptions, threads and debug-meta with the tombstone's. For JNI-raised crashes the tombstone's crashing-thread stack is the ART-unwound managed (OAT/VDEX) stack, which cannot be symbolicated or ProGuard-deobfuscated, so the merged event became unreadable.

Keep the Native SDK's crashing-thread stack, threads and debug-meta (symbolicated against uploaded debug files) and only enrich the exception with the tombstone's signal metadata. Fall back to the tombstone data when the Native SDK event carries no exception.

Fixes JAVA-645

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmE8hdaj9H9K61opK2PZ6U
@linear-code

linear-code Bot commented Jul 16, 2026

Copy link
Copy Markdown

JAVA-645

@sentry

sentry Bot commented Jul 16, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.49.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 327.96 ms 372.89 ms 44.93 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
b193867 319.59 ms 403.09 ms 83.50 ms
2124a46 319.19 ms 415.04 ms 95.85 ms
a5ab36f 316.83 ms 394.54 ms 77.71 ms
dba088c 320.59 ms 361.29 ms 40.70 ms
4e3e79d 328.10 ms 395.64 ms 67.54 ms
abfcc92 309.54 ms 380.32 ms 70.78 ms
539ca63 313.51 ms 355.43 ms 41.92 ms
3d205d0 352.15 ms 432.53 ms 80.38 ms
bb0ff41 312.86 ms 363.78 ms 50.92 ms
2195398 319.02 ms 342.38 ms 23.36 ms

App size

Revision Plain With Sentry Diff
b193867 1.58 MiB 2.19 MiB 620.00 KiB
2124a46 1.58 MiB 2.12 MiB 551.51 KiB
a5ab36f 1.58 MiB 2.12 MiB 555.26 KiB
dba088c 1.58 MiB 2.13 MiB 558.99 KiB
4e3e79d 0 B 0 B 0 B
abfcc92 1.58 MiB 2.13 MiB 557.31 KiB
539ca63 1.58 MiB 2.12 MiB 551.41 KiB
3d205d0 1.58 MiB 2.10 MiB 532.97 KiB
bb0ff41 0 B 0 B 0 B
2195398 0 B 0 B 0 B

@runningcode runningcode changed the title fix(android): Preserve NDK stack on tombstone merge (JAVA-645) fix(android): Preserve NDK stack on tombstone merge (JAVA-645) [on hold] Jul 16, 2026
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