Skip to main content
Native crashes in Android NDK code can be challenging to debug. Understanding tombstone files, stack traces, and crash signals is essential for diagnosing and fixing issues.

Native crash basics

When native code crashes, Android generates a tombstone file containing detailed crash information. The crash also appears in logcat with a summary.

Common crash signals

Finding crash information

Viewing tombstones

Tombstone files are stored on the device:
Tombstone files are rotated. Pull them immediately after a crash before they’re overwritten.

Logcat crash output

Crashes also appear in logcat:

Reading tombstones

A tombstone contains several sections with crucial debugging information.

Example tombstone

Key sections explained

Signal information

  • Signal 11 (SIGSEGV) - Segmentation fault
  • Code 1 (SEGV_MAPERR) - Accessed unmapped memory
  • Fault addr 0x0 - Attempted to access address 0 (null pointer)

CPU registers

  • pc (program counter) - Where the crash occurred
  • sp (stack pointer) - Current stack position
  • lr (link register) - Return address
  • x0-x30 - General purpose registers (may contain function arguments)

Stack trace (backtrace)

Each frame shows:
  • Frame number
  • Program counter offset
  • Library path
  • Function name and offset (if symbols available)

Analyzing stack traces

Using addr2line

Convert addresses to source code locations:
Output:

Using ndk-stack

Automatic symbolication of entire stack traces:
Output:
ndk-stack requires unstripped .so files with debug symbols. Never strip libraries during development.

Using Android Studio

Android Studio can automatically symbolicate crashes:
  1. Go to View > Tool Windows > Logcat
  2. Crashes with available symbols show as clickable links
  3. Click to jump to the source line

Common crash patterns

Null pointer dereference

Code example:
Fix: Always check pointers before dereferencing:

Use after free

Fault address is non-zero but unmapped. Code example:
Detection: Use Address Sanitizer:

Stack overflow

Code example:
Fix:
  • Limit recursion depth
  • Use iteration instead of recursion
  • Reduce stack-allocated buffer sizes

Buffer overflow

Code example:
Detection: Use Address Sanitizer or enable stack protector:

Assertion failures

Code example:
The abort message tells you exactly which assertion failed.

Invalid JNI references

Code example:
Prevention:
  • Enable CheckJNI: adb shell setprop debug.checkjni 1
  • Never use JNI references after deleting them
  • Don’t cache local references across JNI calls

Debugging techniques

Using debuggers

LLDB in Android Studio

  1. Set breakpoint in native code
  2. Run app in debug mode
  3. Use debug controls when breakpoint hits

Command-line debugging

Sanitizers

Enable runtime error detection:
Sanitizers significantly increase app size and reduce performance. Use only during development.

Logging and diagnostics

Add strategic logging:

Crash reporting services

Use crash reporting to collect crashes from users:
  • Firebase Crashlytics - Google’s crash reporting
  • Sentry - Open source crash reporting
  • Bugsnag - Commercial crash reporting
These services automatically symbolicate and aggregate crashes.

Reproducing crashes

Make crashes consistent

  1. Clear app data before each test:
  2. Use same device/emulator - Different devices may behave differently
  3. Test with sanitizers - Makes crashes more reliable:
  4. Disable optimizations during debugging:

Minimize test case

Reduce the code to the smallest example that crashes:
  1. Remove unrelated code
  2. Simplify inputs
  3. Isolate the crashing function
  4. Create a standalone reproduction

Prevention strategies

Code review checklist

  • Check all pointers for null before dereferencing
  • Verify array bounds on all accesses
  • Match every new with delete, malloc with free
  • Delete JNI local references in loops
  • Use RAII (Resource Acquisition Is Initialization)
  • Avoid manual memory management when possible

Defensive programming

Modern C++ practices

Additional resources

Always keep unstripped .so files for every release build. You’ll need them to symbolicate crashes from users.