Skip to content

Commit

Permalink
Native crash handling
Browse files Browse the repository at this point in the history
  • Loading branch information
aitorvs committed May 7, 2024
1 parent 2b13d54 commit db2487f
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 2 deletions.
28 changes: 28 additions & 0 deletions anrs/anrs-impl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
project(crash-ndk)
cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
crash-ndk

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/ndk-crash.cpp
src/main/cpp/jni.cpp
)

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log)

target_link_libraries(
# Specifies the target library.
crash-ndk

# Links the target library to the log library
# included in the NDK.
${log-lib} )
11 changes: 10 additions & 1 deletion anrs/anrs-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation project(':browser-api')
implementation project(':statistics')
implementation project(':verified-installation-api')
implementation project(':library-loader-api')

implementation AndroidX.core.ktx
implementation KotlinX.coroutines.core
Expand All @@ -56,8 +57,16 @@ android {
anvil {
generateDaggerFactories = true // default is false
}
namespace 'com.duckduckgo.app.anr'

ndkVersion '21.4.7075529'
namespace 'com.duckduckgo.app.anr'
compileOptions {
coreLibraryDesugaringEnabled = true
}

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
14 changes: 14 additions & 0 deletions anrs/anrs-impl/src/main/cpp/android.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef ANDROID_JNI_H
#define ANDROID_JNI_H

#include <cstdio>
#include <android/log.h>

void __platform_log_print(int prio, const char *tag, const char *fmt, ...);

// loglevel will be assigned during library initialisation, it always has a default value
extern int loglevel;
// Use this method to print log messages into the console
#define log_print(prio, format, ...) do { if (prio >= loglevel) __platform_log_print(prio, "ndk-crash", format, ##__VA_ARGS__); } while (0)

#endif // ANDROID_JNI_H
166 changes: 166 additions & 0 deletions anrs/anrs-impl/src/main/cpp/jni.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include <jni.h>
#include <android/log.h>
#include <exception>

#include "android.h"
#include "ndk-crash.h"

///////////////////////////////////////////////////////////////////////////

static JavaVM *JVM = NULL;
jclass clsCrash;
static jmethodID MID_REPORT_INIT = NULL;
static jmethodID MID_REPORT_CRASH = NULL;
jobject CLASS_JVM_CRASH = NULL;


static jobject jniGlobalRef(JNIEnv *env, jobject cls);
static jclass jniFindClass(JNIEnv *env, const char *name);
static jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);
static void jni_report_native_crash_init();

int loglevel = 0;

///////////////////////////////////////////////////////////////////////////


void __platform_log_print(int prio, const char *tag, const char *fmt, ...) {
char line[1024];
va_list argptr;
va_start(argptr, fmt);
vsprintf(line, fmt, argptr);
__android_log_print(prio, tag, "%s", line);
va_end(argptr);
}

///////////////////////////////////////////////////////////////////////////
// JNI utils
///////////////////////////////////////////////////////////////////////////

static jobject jniGlobalRef(JNIEnv *env, jobject cls) {
jobject gcls = env->NewGlobalRef(cls);
if (gcls == NULL)
log_print(ANDROID_LOG_ERROR, "Global ref failed (out of memory?)");
return gcls;
}

static jclass jniFindClass(JNIEnv *env, const char *name) {
jclass cls = env->FindClass(name);
if (cls == NULL)
log_print(ANDROID_LOG_ERROR, "Class %s not found", name);
return cls;
}

static jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature) {
jmethodID method = env->GetMethodID(cls, name, signature);
if (method == NULL) {
log_print(ANDROID_LOG_ERROR, "Method %s %s not found", name, signature);
}
return method;
}

///////////////////////////////////////////////////////////////////////////
// JNI lifecycle
///////////////////////////////////////////////////////////////////////////

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((vm)->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
log_print(ANDROID_LOG_INFO, "JNI load GetEnv failed");
return -1;
}

jint rs = env->GetJavaVM(&JVM);
if (rs != JNI_OK) {
log_print(ANDROID_LOG_ERROR, "Could not get JVM");
return -1;
}

return JNI_VERSION_1_6;
}

void JNI_OnUnload(JavaVM *vm, void *reserved) {
log_print(ANDROID_LOG_INFO, "JNI unload");

JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
log_print(ANDROID_LOG_INFO, "JNI load GetEnv failed");
else {
env->DeleteGlobalRef(clsCrash);
}
}

///////////////////////////////////////////////////////////////////////////
// native<>JVM interface
///////////////////////////////////////////////////////////////////////////

extern "C" JNIEXPORT void JNICALL
Java_com_duckduckgo_app_anr_ndk_NativeCrashInit_jni_1register_1sighandler(
JNIEnv* env,
jobject instance,
jint loglevel_) {

if (!native_crash_handler_init()) {
log_print(ANDROID_LOG_ERROR, "Error initialising crash handler.");
return;
}
loglevel = loglevel_;

clsCrash = env->GetObjectClass(instance);
const char *emptyParamVoidSig = "()V";
MID_REPORT_INIT = env->GetMethodID(clsCrash, "reportNativeCrashInit", emptyParamVoidSig);
MID_REPORT_CRASH = env->GetMethodID(clsCrash, "reportNativeCrash", emptyParamVoidSig);
CLASS_JVM_CRASH = env->NewGlobalRef(instance);

jni_report_native_crash_init();

log_print(ANDROID_LOG_ERROR, "Native crash handler successfully initialized.");
}

extern "C" JNIEXPORT void JNICALL
Java_com_duckduckgo_app_anr_ndk_NativeCrashInit_jni_1unregister_sighandler(
JNIEnv* env,
jobject /* this */) {
native_crash_handler_fini();
}

///////////////////////////////////////////////////////////////////////////
// private API
///////////////////////////////////////////////////////////////////////////


static void jni_report_native_crash_init() {
if (JVM == NULL) return;

JNIEnv *threadEnv;
jint rs = JVM->AttachCurrentThread(&threadEnv, NULL);
if (rs != JNI_OK) {
log_print(ANDROID_LOG_ERROR, "Could not attach to JVM thread");
return;
}

const char *usage = "com/duckduckgo/app/anr/ndk/NativeCrashInit";
jobject instance = jniGlobalRef(threadEnv, jniFindClass(threadEnv, usage));

(threadEnv)->CallVoidMethod(CLASS_JVM_CRASH, MID_REPORT_INIT);
}

///////////////////////////////////////////////////////////////////////////
// public API
///////////////////////////////////////////////////////////////////////////

void jni_report_native_crash() {
if (JVM == NULL) return;

JNIEnv *threadEnv;
jint rs = JVM->AttachCurrentThread(&threadEnv, NULL);
if (rs != JNI_OK) {
log_print(ANDROID_LOG_ERROR, "Could not attach to JVM thread");
return;
}

const char *usage = "com/duckduckgo/app/anr/ndk/NativeCrashInit";
jobject instance = jniGlobalRef(threadEnv, jniFindClass(threadEnv, usage));

(threadEnv)->CallVoidMethod(CLASS_JVM_CRASH, MID_REPORT_CRASH);
}
8 changes: 8 additions & 0 deletions anrs/anrs-impl/src/main/cpp/jni.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Java
#ifndef CRASH_JNI_H
#define CRASH_JNI_H

// Call this method to report that a native crash happened back to JVM
void jni_report_native_crash();

#endif // CRASH_JNI_H
128 changes: 128 additions & 0 deletions anrs/anrs-impl/src/main/cpp/ndk-crash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "jni.h"
#include "android.h"

#include <csignal>
#include <cstdio>
#include <cstring>
#include <memory>
#include <cxxabi.h>
#include <unistd.h>

#define sizeofa(array) sizeof(array) / sizeof(array[0])

// sometimes signal handlers inlinux consume crashes entirely. For those cases we trigger a signal so that we ultimately
// crash properly
#define __NR_tgkill 270

// Caught signals
static const int SIGNALS_TO_CATCH[] = {
SIGABRT,
SIGBUS,
SIGFPE,
SIGSEGV,
SIGILL,
SIGSTKFLT,
SIGTRAP,
};

// Signal handler context
struct CrashInContext {
// Old handlers of signals that we restore on de-initialization. Keep values for all possible
// signals, for unused signals nullptr value is stored.
struct sigaction old_handlers[NSIG];
};

// Crash handler function signature
typedef void (*CrashSignalHandler)(int, siginfo*, void*);

// Global instance of context. As the app can only crash once per process lifetime, this can be global
static CrashInContext* crashInContext = nullptr;


// Main signal handling function.
static void native_crash_sig_handler(int signo, siginfo* siginfo, void* ctxvoid) {
// Restoring an old handler to make built-in Android crash mechanism work.
sigaction(signo, &crashInContext->old_handlers[signo], nullptr);

// Log crash message
__android_log_print(ANDROID_LOG_ERROR, "ndk-crash", "Terminating with uncaught exception of type %d", signo);
jni_report_native_crash();

// sometimes signal handlers inlinux consume crashes entirely. For those cases we trigger a signal so that we ultimately
// crash properly, ie. to run standard bionic handler
if (siginfo->si_code <= 0 || signo == SIGABRT) {
if (syscall(__NR_tgkill, getpid(), gettid(), signo) < 0) {
_exit(1);
}
}
}

// Register signal handler for crashes
static bool register_sig_handler(CrashSignalHandler handler, struct sigaction old_handlers[NSIG]) {
struct sigaction sigactionstruct;
memset(&sigactionstruct, 0, sizeof(sigactionstruct));
sigactionstruct.sa_flags = SA_SIGINFO;
sigactionstruct.sa_sigaction = handler;

// Register new handlers for all signals
for (int index = 0; index < sizeofa(SIGNALS_TO_CATCH); ++index) {
const int signo = SIGNALS_TO_CATCH[index];

if (sigaction(signo, &sigactionstruct, &old_handlers[signo])) {
return false;
}
}

return true;
}

// Unregister already register signal handler
static void unregister_sig_handler(struct sigaction old_handlers[NSIG]) {
// Recover old handler for all signals
for (int signo = 0; signo < NSIG; ++signo) {
const struct sigaction* old_handler = &old_handlers[signo];

if (!old_handler->sa_handler) {
continue;
}

sigaction(signo, old_handler, nullptr);
}
}

bool native_crash_handler_fini() {
// Check if already deinitialized
if (!crashInContext) return false;

// Unregister signal handlers
unregister_sig_handler(crashInContext->old_handlers);

// Free singleton crash handler context
free(crashInContext);
crashInContext = nullptr;

log_print(ANDROID_LOG_ERROR, "Native crash handler successfully deinitialized.");

return true;
}

bool native_crash_handler_init() {
// Check if already initialized
if (crashInContext) {
log_print(ANDROID_LOG_INFO, "Native crash handler is already initialized.");
return false;
}

// Initialize singleton crash handler context
crashInContext = static_cast<CrashInContext *>(malloc(sizeof(CrashInContext)));
memset(crashInContext, 0, sizeof(CrashInContext));

// Trying to register signal handler.
if (!register_sig_handler(&native_crash_sig_handler, crashInContext->old_handlers)) {
native_crash_handler_fini();
log_print(ANDROID_LOG_ERROR, "Native crash handler initialization failed.");
return false;
}

return true;
}
10 changes: 10 additions & 0 deletions anrs/anrs-impl/src/main/cpp/ndk-crash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Java
#ifndef NDK_CRASH_H
#define NDK_CRASH_H

// Call this method to register native crash handling
bool native_crash_handler_init();
// Call this method to de-register native crash handling
bool native_crash_handler_fini();

#endif // NDK_CRASH_H
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class CrashOfflinePixelSender @Inject constructor(
companion object {
private const val EXCEPTION_SHORT_NAME = "sn"
private const val EXCEPTION_MESSAGE = "m"
private const val EXCEPTION_PROCESS_NAME = "pn"
internal const val EXCEPTION_PROCESS_NAME = "pn"
private const val EXCEPTION_STACK_TRACE = "ss"
private const val EXCEPTION_APP_VERSION = "v"
private const val EXCEPTION_TIMESTAMP = "t"
Expand Down
Loading

0 comments on commit db2487f

Please sign in to comment.