What is a Tombstone File in Android Causes and Solutions

When an Android application crashes, the system generates a report to help developers diagnose the problem. For crashes in Java or Kotlin code, this report is a familiar stack trace that appears in the Logcat crash buffer. However, when a crash occurs in native C or C++ code—often found in games, high-performance libraries, or the Android Native Development Kit (NDK)—the system generates a much more detailed and specialized crash dump file. This file is called a Tombstone.

The Problem: Debugging Native Code Crashes

Crashes in native code (like C++) are fundamentally different from crashes in a managed runtime environment like the Android Runtime (ART). Native code is compiled directly to machine code and interacts directly with system memory. This makes it extremely fast but also more prone to certain types of errors that are less common in memory-safe languages like Kotlin.

Common causes of native crashes include:

  • Memory Corruption: Writing data past the end of a buffer (a buffer overflow) or using a pointer after the memory it pointed to has been freed (a use-after-free error).
  • Null Pointer Dereference: Attempting to read from or write to a memory address of 0.
  • Illegal Instructions: The CPU attempts to execute an instruction that is invalid, often due to a corrupted function pointer.

When these errors occur, the Linux kernel sends a fatal signal (like `SIGSEGV` for a segmentation fault) to the crashing process. A simple Logcat stack trace is often insufficient to debug these complex, low-level issues. Developers need a much richer snapshot of the process’s state at the exact moment of the crash.

Introducing Tombstones: The Post-Mortem Report for Native Crashes

A Tombstone file is that detailed post-mortem report. When a fatal signal is received by a native process, a system daemon called the `debuggerd` takes over. Its job is to inspect the crashing process and write a comprehensive report—the tombstone—to a file on the device. This file contains a wealth of information that is invaluable for debugging native code.

Tombstones are typically written to the `/data/tombstones/` directory on the device. Accessing this directory usually requires root privileges or the use of `adb bugreport`, which bundles them into its output.

Key Information Found in a Tombstone File:

  • Crash Details: The specific signal that caused the crash (e.g., `SIGSEGV`), the fault address in memory, and a brief description of the error.
  • Processor and System Info: Details about the device’s architecture (ARM, ARM64, x86), kernel version, and Android build.
  • Process and Thread Information: The process ID (PID), thread ID (TID), and the name of the crashing process and thread.
  • Register Dump: The state of all the CPU registers for the crashing thread at the moment of the crash. This is a low-level snapshot of the CPU’s working memory.
  • Backtrace (Stack Trace): This is the most critical part. It’s a stack trace for the crashing thread, showing the sequence of function calls that led to the crash.
  • Memory Map: A complete map of the process’s virtual memory space, showing which libraries (`.so` files) were loaded and where.
  • Nearby Memory Dump: A hex dump of the memory addresses around the fault address and the values in the registers, which can help identify corrupted data.

How to Read and Analyze a Tombstone File

A raw tombstone file can be intimidating, but understanding its structure is key. The most important section is the backtrace.

 *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Build fingerprint: 'google/oriole/oriole:13/TQ2A.230505.002/9891397:user/release-keys' ABI: 'arm64' Timestamp: 2023-08-15 10:30:00.123456789Z Process: com.example.mygame PID: 12345 TID: 12367 Name: RenderThread >>> com.example.mygame <<< Signal: 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 Cause: null pointer dereference x0 0000000000000000 x1 0000007b8c0a3e80 x2 0000000000000001 x3 0000007b8c0a3f00 ... (register dump continues) ... backtrace: #00 pc 00000000000a1b2c /data/app/~~.../com.example.mygame-1/lib/arm64/libnative-lib.so (process_player_input(Player*)+24) #01 pc 00000000000a2c3d /data/app/~~.../com.example.mygame-1/lib/arm64/libnative-lib.so (update_game_state(GameState*)+112) #02 pc 00000000000a3d4e /data/app/~~.../com.example.mygame-1/lib/arm64/libnative-lib.so (Java_com_example_mygame_GameRenderer_onDrawFrame+56) ... (stack trace continues) ... 

This snippet tells us a lot:

  • The app `com.example.mygame` crashed in its `RenderThread`.
  • The cause was a `SIGSEGV` due to a `null pointer dereference` (fault addr 0x0).
  • The crash happened inside the `process_player_input` function in the `libnative-lib.so` library.
  • This function was called by `update_game_state`, which was called by the main render loop.

Symbolication: Making Sense of the Backtrace

The backtrace in a raw tombstone contains memory addresses (`pc 00...a1b2c`). These are not very useful on their own. To be truly useful, they need to be converted into human-readable file names, function names, and line numbers. This process is called symbolication.

To symbolicate a tombstone, you need the unstripped version of your native libraries (`.so` files) that contain the debugging symbols. These are generated during the build process. Tools like Android Studio's built-in crash analyzer or command-line scripts like `ndk-stack` can take a tombstone file and your unstripped libraries as input and produce a fully symbolicated, human-readable stack trace.

Tombstones vs. Logcat Crashes

It's important to understand the difference between these two types of crash reports.

Aspect Logcat Crash Report Tombstone File
Cause An unhandled exception in Java or Kotlin code. A fatal signal (e.g., SIGSEGV) from the kernel due to an error in native (C/C++) code.
Location Printed to the Logcat crash buffer. Written to a file in `/data/tombstones/`.
Content Java/Kotlin stack trace, exception type, and message. A full process dump: register state, memory map, native backtrace, etc.
Debugging Relatively straightforward to read directly. Requires symbolication with debugging symbols to be fully understood.

For a detailed walkthrough of debugging native crashes, the official Android Developer documentation is an excellent resource.

Frequently Asked Questions

How can I get tombstone files from a user's device?

Getting tombstones from a non-rooted production device is difficult for privacy and security reasons. The best approach is to use a crash reporting service that has an NDK component (like Firebase Crashlytics or Sentry). These services can capture the tombstone data at the time of the crash and upload it to a dashboard where it is automatically symbolicated for you, providing a much easier workflow for debugging native crashes in the wild.

What is an ANR (Application Not Responding) and is it related to a tombstone?

An ANR is a different type of error. It occurs when the main UI thread of an application is blocked for too long (typically 5 seconds), causing the app to become unresponsive. The system generates an ANR report, which includes stack traces of all the app's threads, and saves it in `/data/anr/`. While it's also a crash report file, it's caused by unresponsiveness, not a native code crash. However, a native deadlock or an infinite loop in native code could *cause* an ANR.

What does the name "tombstone" mean?

The name is a metaphor. Just as a tombstone in a cemetery marks the final resting place of a person and provides information about them, a tombstone file in Android marks the "death" of a process and provides detailed information about its state at the time of its demise. It's a permanent record of the crash event.

Do tombstones take up a lot of space?

A single tombstone file is a plain text file, typically a few hundred kilobytes in size. The system limits the number of tombstones it keeps (e.g., the last 32) to prevent them from filling up the device's storage. They do not pose a significant storage issue for users.