Advanced AOSP Subsystems
2 min read

ndk-stack

Symbolizing Native Crashes: ndk-stack

When a native crash occurs and a tombstone is generated (or a crash dump is printed to Logcat), the stack trace usually contains raw hexadecimal addresses (Program Counters or PCs). To make this actionable, these addresses must be translated back into source code files, function names, and line numbers. This process is called symbolization.

Symbolizing Native Stack Traces

Consider a raw crash log from Logcat:

backtrace:
    #00 pc 0000000000012348  /data/app/.../libnative-lib.so
    #01 pc 0000000000012500  /data/app/.../libnative-lib.so

The system knows the crash happened inside libnative-lib.so at offset 0x12348, but the stripped library on the device doesn't contain the mapping back to your C++ code.

The ndk-stack tool, included in the Android NDK, automates the symbolization process. It parses the crash log, finds the corresponding unstripped shared libraries on your host machine, and replaces the hex addresses with human-readable symbols.

ndk-stack Usage with Logcat

The most common use case for ndk-stack is piping live Logcat output directly into it. You must provide the -sym argument, pointing to the directory containing your unstripped .so files.

# Using ndk-stack with live logcat
adb logcat | $NDK/ndk-stack -sym app/build/intermediates/cmake/debug/obj/arm64-v8a/

When a crash occurs, ndk-stack will intercept the dump and output a symbolized version:

********** Crash dump: **********
Build fingerprint: 'google/pixel_6/oriole:13/...'
...
backtrace:
    #00 0x0000000000012348 /data/app/.../libnative-lib.so (MyClass::doWork()+80)
                                                          MyClass.cpp:42
    #01 0x0000000000012500 /data/app/.../libnative-lib.so (Java_com_example_app_MainActivity_startWork+120)
                                                          native-lib.cpp:15

Now you know exactly that the crash happened on line 42 of MyClass.cpp.

Symbolizing Tombstones with addr2line

If you have pulled a tombstone file from the device (adb pull /data/tombstones/tombstone_00), you can pass it to ndk-stack using the -dump argument:

$NDK/ndk-stack -sym path/to/unstripped/libs -dump tombstone_00

Under the Hood: addr2line and llvm-symbolizer

ndk-stack is essentially a smart wrapper script. The actual heavy lifting of mapping addresses to lines of code is performed by underlying binary utilities.

Historically, this was addr2line (from GNU Binutils). In modern NDKs and AOSP builds, this is handled by llvm-symbolizer.

You can use llvm-symbolizer manually if you only need to look up a single address. You provide the unstripped library and the address offset:

# Manual symbolization using llvm-symbolizer
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-symbolizer \
    --obj=path/to/unstripped/libnative-lib.so \
    0x12348

This will output the function name, file, and line number for that specific memory offset.