-
Notifications
You must be signed in to change notification settings - Fork 930
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
497 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.