NDK Crash Reporting

Installation Instructions

To set up Crashlytics NDK within your app, please start at the NDK downloads page.

Universal AAR

We compile our native crash handler for all Android NDK supported architectures, namely, arm64-v8a, armeabi, armeabi-v7a, mips, mips64, x86, and x86_64, allowing us to support the widest variety of devices. As a consequence, in the process of packaging the APK, your build system will include all architecture directories, even if you are only compiling for a specific subset.

Due to Android’s preferential loading process, if, for example, your APK runs on an armv7 device, but your code is only compiled for armv5, the application will crash at startup with an UnsatisfiedLinkError; the architecture directory corresponding to armv7 exists and only contains Crashlytics-related shared objects.

When compiling with Gradle, you can take advantage of ABI splits (http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits#TOC-ABIs-Splits) to filter the appropriate architectures if necessary.

For Ant users, kit-libs/com-crashlytics-sdk-android_crashlytics-ndk/libs contains all architectures for which the NDK kit is compiled. It is safe to delete any unwanted architecture(s). Updating the version of the NDK kit in the kits.properties file and invoking ant crashlytics-update-dependencies will necessitate the removal of the unwanted architecture once more.

How the Ant plugin manages dependencies

We bundle our own Ant dependency management facilities within the Crashlytics Ant plugin. Invoking ant crashlytics-update-dependencies resolves all dependencies listed in kits.properties, putting all necessary artifacts into a root-level folder, kit-libs. In the case of the Crashlytics NDK, the folder kit-libs/com-crashlytics-sdk-android_crashlytics-ndk/libs contains the necessary shared objects and jar files linked to your project via the project.properties file. Any time kits.properties is modified, you should invoke the update task.

Mechanics of the Crashlytics NDK Kit

In the process of initialization via Fabric.with(), the NDK Kit uses System.loadLibrary to load libcrashlytics. The entry point installs a signal handler into the running process. In the spirit of being a good citizen, after a signal is handled, libcrashlytics restores the previous signal handler, enabling the use of alternate signal handling facilities, although running multiple handlers is not recommended. The handler collects necessary information into a temporary file on the filesystem, that gets read and processed once the application is restarted. In some circumstances, the crash report is not processed due to the extremely close proximity of a crash from the start of the application.

In the context of a crash, the Crashlytics signal handler relies on an on-device stack unwinder; libunwind on API levels 21 and above, libcorkscrew for API levels 16-19, and a simple single-frame unwinder for all others. The unwinding library that gets loaded is written to the logcat upon application startup.

Specifying the path to debug and release binaries

To properly symbolicate and process native crashes, we need the symbols from your native binaries. Typically, Android’s native binary build processes produce two sets of binaries: one set with debugging symbols, and one set to be packaged in your final APK. The Fabric plugin uses both sets of binaries to generate a symbol file on your machine. The symbol generation and upload process assumes your project will have two directories - one for the debug binaries (called obj below), and one for the release binaries (called libs below) - that are broken down by architecture-specific folders.

For example:

obj/
  — armeabi
    — lib1.so
    — lib2.so
  — x86
    — lib1.so
    — lib2.so

libs/
  — armeabi
    — lib1.so
    — lib2.so
  — x86
    — lib1.so
    — lib2.so

When building your project with the Android plugin for Gradle version 2.2.0+ with the externalNativeBuild DSL, the Fabric plugin is able to automatically detect the necessary directories for each native build variant in order to generate the appropriate symbol files.

The paths to the debug and release binaries can be manually controlled via the androidNdkOut (default: src/main/obj) and androidNdkLibsOut (default: src/main/libs) properties. Ant users can modify these in the fabric.properties file. Gradle users can control these via the crashlytics {} block in their build.gradle.

Ant: ant crashlytics-upload-symbols

Gradle: ./gradlew crashlyticsUploadSymbols{Variant}

For example: ./gradlew crashlyticsUploadSymbolsRelease

Note

If you were missing line numbers, once symbols are uploaded for a build, all future crashes will be properly symbolicated including line numbers.

Crashlytics additions to Ant projects

The Crashlytics Ant plugin consists of three files:

crashlytics-devtools.jar
crashlytics_build.xml
crashlytics_build_base.xml

We hook into your Ant build via custom_rules.xml, which should be imported by your build.xml. Our developer tools are configured using fabric.properties. Once all dependencies are resolved and the kit-libs folder is created, the standard project.properties file is used to link your project with the contents of kit-libs.

Custom Logs and Keys

The native API is a header-only, C and C++ compatible utility, with the following functions:

crashlytics_context_t* crashlytics_init();
void crashlytics_free(crashlytics_context_t** ctx);
crashlytics_context_t::set(crashlytics_context_t* ctx, const char* key, const char* value);
crashlytics_context_t::set_user_identifier(crashlytics_context_t* ctx, const char* identifier);
crashlytics_context_t::set_user_name(crashlytics_context_t* ctx, const char* name);
crashlytics_context_t::set_user_email(crashlytics_context_t* ctx, const char* email);
crashlytics_context_t::log(crashlytics_context_t* ctx, const char* message);

Calling crashlytics_init() will initialize the native context, which will remain valid for the lifetime of the program. This context is passed along to all other functions in the API. On program exit, it is advisable to call crashlytics_free()

Note

crashlytics_init() should always be called on the main thread of your application.

Here’s an example of the API’s usage in C:

#include “crashlytics.h”
// …
crashlytics_context_t* context = crashlytics_init();
// …
void register_user()
{
    context->set_user_identifier(context, get_user_identifier(...));
    context->set_user_name(context, get_user_name(...));
    context->set_user_email(context, get_user_email(...));

    context->log(context, "registered user!");
}
// …
void update_game_score(...)
{
    context->set(context, "game_score", get_game_score(...));
}
void on_exit()
{
    crashlytics_free(&context);
}

Custom Logs and Keys - mechanics

In order to make the integration process as seamless as possible, we decided to distribute a single, header-only library which dynamically loads libcrashlytics.so. The overhead for this approach is minimal, as libcrashlytics is brought into the address space earlier, via the CrashlyticsNdk kit. Still, crashlytics_init() is a heavy-weight function, invoking dlopen and dlsym to construct the context. It is recommended to call this function only once, at the start of the program.

One line stack traces for native crashes

On API levels lower than 16, the two unwind methods used: libcorkscrew and libunwind are not present, so we default to our own simple unwinder. Currently, the simple unwinder is only able to capture the top-most frame in a crash. If you notice this for a crash report coming from a device between API 16 and 23, the crash could be happening in a very inlined context.

To verify if you’re seeing full crash info on your dashboard, open the device logcat containing the tombstone of the native crash. If there’s a mismatch of information, please let us know on Stack Overflow

Uploading symbols for external dependencies

Our symbol upload tools assume that your native code is in your Android app project and compiled using CMake or ndk-build. If, instead, your native code is contained in a separate project, you can still upload symbols from that project with our Gradle or Ant plugins using a small workaround.

In order to upload the symbols, we need your app’s package name and API key in the AndroidManifest.xml file. To provide this, you can copy over the entire .xml file for your app, or simply provide a “dummy” AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="<YOUR.APP.PACKAGE.NAME>" >

    <application>
      <meta-data
          android:name="io.fabric.ApiKey"
          android:value="<YOUR APP API KEY>" />
  </application>
</manifest>

Using the Fabric Ant or Gradle plugin to upload symbols assumes a project structure as specified above in Specifying the path to debug and release binaries. When the native code is part of an external library, the project structure may differ. You’ll need to include an Ant or Gradle task to temporarily create the appropriate project structure before running the symbol upload.

Once you have assembled the appropriate project structure and you have your AndroidManifest.xml file available, you’ll need to set a few properties for the Fabric plugin to use.

Using Ant

For Ant, you should create a fabric.properties file with the following properties:

enableNDK=true
androidBaseManifest=AndroidManifest.xml
androidNdkOut=obj
androidNdkLibsOut=libs
externalCSymUpload=true

Then, to run the symbol upload, invoke the crashlytics-devtools.jar which can be found inside the Ant plugin zip file. Calling java -jar crashlytics-devtools.jar -properties fabric.properties will start the upload process.

The androidBaseManifest property defines the path to the AndroidManifest.xml file with your app’s API key and package name.

Using Gradle

For Gradle, you’ll need a minimal Android build.gradle file and you can define the properties directly in a crashlytics {} block:

Note

The Fabric Gradle plugin requires the Android Gradle plugin to be applied before it in your build.gradle.

apply plugin: 'com.android.library'
apply plugin: 'io.fabric'

android {
  compileSdkVersion <CURRENT COMPILESDKVERSION>
  buildToolsVersion "<YOUR BUILD TOOLS VERSION>"

  defaultConfig {
    applicationId "<YOUR APP'S PACKAGE NAME>"
  }
}

crashlytics {
  enableNdk true
  // If using the Android plugin for Gradle version 2.2.0+ with the externalNativeBuild DSL,
  // you should remove the androidNdkOut and androidNdkLibsOut properties, as these paths will
  // automatically be detected by the Fabric plugin.
  androidNdkOut 'obj'
  androidNdkLibsOut 'libs'
  manifestPath 'AndroidManifest.xml'
}

Then run ./gradlew crashlyticsUploadSymbolsRelease to upload your symbols.

The manifestPath property defines the path to the AndroidManifest.xml file with your app’s API key and package name.