Skip to content

feat(errortracking): capture native ndk crashes from tombstones - #659

Draft
cat-ph wants to merge 4 commits into
mainfrom
cat/ndk-tombstone-capture
Draft

feat(errortracking): capture native ndk crashes from tombstones#659
cat-ph wants to merge 4 commits into
mainfrom
cat/ndk-tombstone-capture

Conversation

@cat-ph

@cat-ph cat-ph commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Native (NDK) crashes kill the process before any JVM handler runs, so today they are invisible to error tracking. This adds capture for them without shipping any native code in the SDK: on startup, the SDK reads the crash records the OS kept via ApplicationExitInfo (REASON_CRASH_NATIVE, Android 12+), parses the attached tombstone protobuf, and captures one $exception event per crash using the native stack frame contract PostHog already resolves for the Rust and Go SDKs.

Per crash, the event carries:

  • raw native frames (platform: "native", instruction_addr, image_addr, optional client-resolved function/symbol_addr) in canonical bottom-up order
  • $debug_images entries derived from the tombstone's per-frame GNU build ids, so the server matches frames to .so symbols uploaded with posthog-cli symbol-sets upload
  • signal metadata (SIGSEGV / SEGV_MAPERR at 0x..., abort message when present), $exception_level: fatal, and the original crash timestamp

Design notes:

  • Tombstone parsing is a minimal hand-rolled protobuf wire reader (field numbers are frozen in AOSP), avoiding a protobuf runtime dependency. Unknown fields are skipped.
  • instruction_addr is biased by +1: tombstone pcs are already the correct lookup address (the leaf is the faulting instruction and libunwindstack rewinds caller pcs to the call instruction), so the bias cancels the server's uniform -1 return-address adjustment. This is pinned by a cymbal fixture test on the server side.
  • Opt-in via errorTrackingConfig.captureNativeCrashes, additionally gated on the project's exception autocapture remote toggle. A persisted timestamp watermark prevents duplicate capture across launches, advancing per record so dying mid-scan cannot re-capture.
  • Frames from /data/ are in-app; /system, /apex, /vendor and unknown mappings are not.

Known limitations (draft): events are associated with the identity at next launch, not at crash time; $exception_steps recorded in the new process may attach to the previous run's crash; API 31+ only (tombstone protos attach from Android 12).

💚 How did you test it?

  • Unit tests for the tombstone parser (against independently hand-encoded proto wire bytes) and the event coercer (frame shape, address math, debug-id derivation pinned to the same vocabulary as the server fixture and CLI).
  • Full gradle test suite on a remote linux box; the new tests pass. Pre-existing PostHogAndroidTest conscrypt failures on that box reproduce on main (arm64 environment issue, no conscrypt aarch64 linux artifact) and are unrelated.
  • Not yet done (why this is a draft): end-to-end validation on a real device/emulator producing an actual tombstone, and validating against a Play-store build.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed. (docs PR is staged separately, gated on release)
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-07-28 17:02:56 UTC
Duration: 118252ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 350ms
Format Validation.Event Has Uuid 26ms
Format Validation.Event Has Lib Properties 23ms
Format Validation.Distinct Id Is String 23ms
Format Validation.Token Is Present 25ms
Format Validation.Custom Properties Preserved 24ms
Format Validation.Event Has Timestamp 25ms
Retry Behavior.Retries On 503 7024ms
Retry Behavior.Does Not Retry On 400 4022ms
Retry Behavior.Does Not Retry On 401 4023ms
Retry Behavior.Respects Retry After Header 7024ms
Retry Behavior.Implements Backoff 17032ms
Retry Behavior.Retries On 500 7018ms
Retry Behavior.Retries On 502 7016ms
Retry Behavior.Retries On 504 7016ms
Retry Behavior.Max Retries Respected 17034ms
Deduplication.Generates Unique Uuids 38ms
Deduplication.Preserves Uuid On Retry 7017ms
Deduplication.Preserves Uuid And Timestamp On Retry 12021ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7018ms
Deduplication.No Duplicate Events In Batch 31ms
Deduplication.Different Events Have Different Uuids 21ms
Compression.Sends Gzip When Enabled 19ms
Batch Format.Uses Proper Batch Structure 17ms
Batch Format.Flush With No Events Sends Nothing 13ms
Batch Format.Multiple Events Batched Together 30ms
Error Handling.Does Not Retry On 403 4016ms
Error Handling.Does Not Retry On 413 4018ms
Error Handling.Retries On 408 5023ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 33ms
Request Payload.Flags Request Uses V2 Query Param 17ms
Request Payload.Flags Request Hits Flags Path Not Decide 19ms
Request Payload.Flags Request Omits Authorization Header 22ms
Request Payload.Token In Flags Body Matches Init 21ms
Request Payload.Groups Round Trip 18ms
Request Payload.Groups Default To Empty Object 20ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 17ms
Request Payload.Disable Geoip Omitted Defaults To False 19ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 34ms
Request Lifecycle.No Flags Request On Init Alone 10ms
Request Lifecycle.No Flags Request On Normal Capture 17ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 29ms
Request Lifecycle.Mock Response Value Is Returned To Caller 20ms
Retry Behavior.Retries Flags On 502 325ms
Retry Behavior.Retries Flags On 504 320ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 22ms

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