← Back to list

Reading Android Tombstones: Native Crash Forensics

What is a Tombstone?

Pablo Ajo · 2026-03-26 09:58 · 8 claps · 2.9 min read
#android #reverse-engineering
Open on Medium ↗
Wiki topics: 📚 · Books & Reading

Reading Android Tombstones: Native Crash Forensics

What is a Tombstone?

When a native process crashes on Android (a SIGSEGV, SIGABRT, or similar signal), the system doesn’t just close the app and move on. It writes a structured crash report to disk. Android calls these files tombstones.

You’ll find them here:

> adb shell ls /data/tombstones/
tombstone_00
tombstone_01
tombstone_02
...
tombstone_09

The system keeps the last ten, cycling through tombstone_00 to tombstone_09. When the slot fills up, the oldest gets overwritten.

Pull the latest one:

adb pull /data/tombstones/tombstone_00 .

Or read it directly:

adb shell cat /data/tombstones/tombstone_00

Anatomy of a Tombstone

Here’s what a typical tombstone looks like:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sdk_gphone64_arm64/emulator64_arm64:14/UE1A.230829.036/11028240:userdebug/dev-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2025-03-20 11:42:07.312000000+0000
Process uptime: 2s
Cmdline: com.example.nativeapp
pid: 4512, tid: 4512, name: example.nativeapp  >>> com.example.nativeapp <<<
uid: 10185
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000000000010
    x0  0000007b3f2a1000  x1  0000000000000010  x2  0000000000000000  x3  0000000000000000
    x4  0000000000000000  x5  0000000000000000  x6  0000000000000000  x7  0000000000000000
    x8  0000000000000038  x9  0000000000000000  x10 0000000000000000  x11 0000000000000000
    x12 0000000000000000  x13 0000000000000000  x14 0000000000000000  x15 0000000000000000
    x16 0000007b3a2c4f80  x17 0000007b3a3d1234  x18 0000007b3f400000  x19 0000007b3f2a1000
    x20 0000000000000000  x21 0000000000000000  x22 0000000000000000  x23 0000000000000000
    x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
    x28 0000000000000000  x29 0000007ff3c21b80
    lr  0000007b3a1d4abc  sp  0000007ff3c21b60  pc  0000007b3a1d4ac4  pst 0000000060001000

backtrace:
  #00 pc 0000000000001ac4  /data/app/~~XZxN==/com.example.nativeapp-==/lib/arm64/libnative.so (Java_com_example_nativeapp_NativeLib_processData+132) (BuildId: 9a3f2c...)
  #01 pc 0000000000003210  /data/app/~~XZxN==/com.example.nativeapp-==/lib/arm64/libnative.so (BuildId: 9a3f2c...)
  #02 pc 00000000000874b0  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144)
  #03 pc 0000000000083a20  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+576)

Let’s break it down section by section.

Header

pid: 4512, tid: 4512, name: example.nativeapp  >>> com.example.nativeapp <<<
uid: 10185
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000000000010
  • pid/tid: process and thread that crashed
  • signal: the signal that killed it. SIGSEGV means a segmentation fault. SIGABRT usually means the process called abort() itself, often from an assertion or a sanitizer.
  • code: narrows down the cause. SEGV_MAPERR means the address is not mapped at all. SEGV_ACCERR means the address exists but the access was not permitted (e.g., writing to a read-only page).
  • fault addr: the memory address that caused the fault. 0x10 is a classic null pointer dereference with a small offset, a struct field access through a null pointer.

Register Dump

The full register state at the moment of the crash. In ARM64:

  • x0- x7: function arguments and return values
  • x8: indirect result register (also the syscall number)
  • x29: frame pointer
  • lr: (link register): return address, i.e., where the function would have returned to
  • sp: stack pointer
  • pc: program counter, the instruction that caused the crash

The pc is the most useful starting point. Subtract the library’s base address (visible in the backtrace or in /proc/<pid>/maps) to get the offset within the binary, then open it in your disassembler.

Backtrace

backtrace:
  #00 pc 0000000000001ac4  /data/app/.../lib/arm64/libnative.so (Java_com_example_nativeapp_NativeLib_processData+132)

Each frame shows:

  • Frame number
  • PC offset within the library
  • Library path
  • Symbol name and byte offset from the start of the function (if symbols are present)

The +132 means the crash happened 132 bytes into Java_com_example_nativeapp_NativeLib_processData. Open the library in radare2 or Ghidra, seek to that function, and count 132 bytes in.

If the binary is stripped (no symbols), you’ll just see the raw offset. That’s still enough to navigate with a disassembler.

What Tombstones Reveal During Reverse Engineering

When you’re analyzing a native library and something crashes, the tombstone gives you the exact instruction that faulted, the full register state at that instant, and the call chain that led there. A null-pointer crash at fault addr 0x10with x0 = 0x0in frame #00 at offset +132 in your target function tells you in seconds what would otherwise take hours of blind disassembly.

Forced Crashes as a Defense Mechanism

Not every tombstone is an accident. Some apps terminate themselves deliberately when they detect something they don’t like: a debugger attached, Frida in memory, an unexpected environment. Pairipcore does this, for example. The mechanism varies: abort(), raise() with an arbitrary signal, a deliberate null dereference, or killing a specific thread with pthread_kill().

The tell is in the backtrace. A genuine crash leads to code that was doing real work. A forced crash leads into a detection or integrity-check routine inside a protection library. The signal itself is less important than where the backtrace points.

When the backtrace from an unexpected crash points into an obfuscated or unfamiliar native library, treat it as a signal: you’ve triggered a tripwire. The tombstone tells you exactly which library initiated the kill and from what offset. That offset is your next target in the disassembler.


메타데이터
post_id
e51932ced077
slug
reading-android-tombstones-native-crash-forensics-e51932ced077
url
https://medium.com/@cr0nos/reading-android-tombstones-native-crash-forensics-e51932ced077
canonical_url
https://medium.com/@cr0nos/reading-android-tombstones-native-crash-forensics-e51932ced077
author_url
https://medium.com/@cr0nos
status
ok
fetched_at
2026-06-22 12:55:45