Reading Android Tombstones: Native Crash Forensics
What is a Tombstone?
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.
SIGSEGVmeans a segmentation fault.SIGABRTusually means the process calledabort()itself, often from an assertion or a sanitizer. - code: narrows down the cause.
SEGV_MAPERRmeans the address is not mapped at all.SEGV_ACCERRmeans 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.
0x10is 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 valuesx8: indirect result register (also the syscall number)x29: frame pointerlr: (link register): return address, i.e., where the function would have returned tosp: stack pointerpc: 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