Skip to content

Commit

Permalink
Update ANR plugin to use similar caching mechanism as the NDK plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
kstenerud committed Feb 4, 2022
1 parent fd44ba1 commit 87e9db1
Show file tree
Hide file tree
Showing 11 changed files with 513 additions and 283 deletions.
2 changes: 2 additions & 0 deletions bugsnag-plugin-android-anr/src/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ add_library( # Specifies the name of the library.
# Provides a relative path to your source file(s).
jni/anr_google.c
jni/anr_handler.c
jni/anr_jni_cache.c
jni/anr_safejni.c
jni/bugsnag_anr.c
jni/utils/string.c
)
Expand Down
261 changes: 58 additions & 203 deletions bugsnag-plugin-android-anr/src/main/jni/anr_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <unistd.h>

#include "anr_google.h"
#include "anr_jni_cache.h"
#include "anr_safejni.h"
#include "unwind_func.h"
#include "utils/string.h"

Expand All @@ -25,221 +27,62 @@ static bool should_wait_for_semaphore = false;
static sem_t watchdog_thread_semaphore;
static volatile bool watchdog_thread_triggered = false;

static JavaVM *bsg_jvm = NULL;
static jmethodID mthd_notify_anr_detected = NULL;
static jobject obj_plugin = NULL;
static jclass frame_class = NULL;
static jobject error_type = NULL;
static jmethodID frame_init = NULL;

static bugsnag_stackframe anr_stacktrace[BUGSNAG_FRAMES_MAX];
static ssize_t anr_stacktrace_length = 0;

static unwind_func unwind_stack_function;

// duplication required for JNI methods that originally came from
// bugsnag-plugin-android-ndk. Until a shared C module is available
// for sharing common code when PLAT-5794 is addressed, this
// duplication is a necessary evil.
static bool check_and_clear_exc(JNIEnv *env) {
if (env == NULL) {
return false;
}
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
return true;
}
return false;
}

static jclass safe_find_class(JNIEnv *env, const char *clz_name) {
if (env == NULL) {
return NULL;
}
if (clz_name == NULL) {
return NULL;
}
jclass clz = (*env)->FindClass(env, clz_name);
check_and_clear_exc(env);
return clz;
}

static jmethodID safe_get_method_id(JNIEnv *env, jclass clz, const char *name,
const char *sig) {
if (env == NULL || clz == NULL || name == NULL || sig == NULL) {
return NULL;
}
jmethodID methodId = (*env)->GetMethodID(env, clz, name, sig);
check_and_clear_exc(env);
return methodId;
}

static jfieldID safe_get_static_field_id(JNIEnv *env, jclass clz,
const char *name, const char *sig) {
if (env == NULL || clz == NULL || name == NULL || sig == NULL) {
return NULL;
}
jfieldID fid = (*env)->GetStaticFieldID(env, clz, name, sig);
check_and_clear_exc(env);
return fid;
}

static jobject safe_get_static_object_field(JNIEnv *env, jclass clz,
jfieldID field) {
if (env == NULL || clz == NULL) {
return NULL;
}
jobject obj = (*env)->GetStaticObjectField(env, clz, field);
check_and_clear_exc(env);
return obj;
}

static bool configure_anr_jni_impl(JNIEnv *env) {
// get a global reference to the AnrPlugin class
// https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class
if (env == NULL) {
return false;
}
int result = (*env)->GetJavaVM(env, &bsg_jvm);
if (result != 0) {
return false;
}

jclass clz = safe_find_class(env, "com/bugsnag/android/AnrPlugin");
if (check_and_clear_exc(env) || clz == NULL) {
return false;
}
mthd_notify_anr_detected =
safe_get_method_id(env, clz, "notifyAnrDetected", "(Ljava/util/List;)V");

// find ErrorType class
jclass error_type_class =
safe_find_class(env, "com/bugsnag/android/ErrorType");
jfieldID error_type_field = safe_get_static_field_id(
env, error_type_class, "C", "Lcom/bugsnag/android/ErrorType;");
error_type =
safe_get_static_object_field(env, error_type_class, error_type_field);
error_type = (*env)->NewGlobalRef(env, error_type);

// find NativeStackFrame class
frame_class = safe_find_class(env, "com/bugsnag/android/NativeStackframe");
frame_class = (*env)->NewGlobalRef(env, frame_class);

// find NativeStackframe ctor
frame_init = safe_get_method_id(
env, frame_class, "<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/"
"Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Lcom/bugsnag/"
"android/ErrorType;)V");
return true;
}
static jobject anr_plugin;

static void safe_delete_local_ref(JNIEnv *env, jobject obj) {
if (env != NULL) {
(*env)->DeleteLocalRef(env, obj);
}
}

static jobject safe_new_object(JNIEnv *env, jclass clz, jmethodID method, ...) {
if (env == NULL || clz == NULL) {
return NULL;
}
va_list args;
va_start(args, method);
jobject obj = (*env)->NewObjectV(env, clz, method, args);
va_end(args);
check_and_clear_exc(env);
return obj;
}

static jstring safe_new_string_utf(JNIEnv *env, const char *str) {
if (env == NULL || str == NULL) {
return NULL;
}
jstring jstr = (*env)->NewStringUTF(env, str);
check_and_clear_exc(env);
return jstr;
}

static bool configure_anr_jni(JNIEnv *env) {
bool success = configure_anr_jni_impl(env);
if (!success) {
BUGSNAG_LOG("Failed to fetch Java VM. ANR handler not installed.");
}
return success;
}

static void notify_anr_detected() {
if (!enabled) {
static void notify_anr_detected(JNIEnv *env) {
if (!enabled || env == NULL) {
return;
}

bool should_detach = false;
JNIEnv *env;
int result = (*bsg_jvm)->GetEnv(bsg_jvm, (void **)&env, JNI_VERSION_1_4);
switch (result) {
case JNI_OK:
break;
case JNI_EDETACHED:
result = (*bsg_jvm)->AttachCurrentThread(bsg_jvm, &env, NULL);
if (result != 0) {
BUGSNAG_LOG("Failed to call JNIEnv->AttachCurrentThread(): %d", result);
return;
}
should_detach = true;
break;
default:
BUGSNAG_LOG("Failed to call JNIEnv->GetEnv(): %d", result);
return;
}

jclass list_class = safe_find_class(env, "java/util/LinkedList");
jmethodID list_init = safe_get_method_id(env, list_class, "<init>", "()V");
jmethodID list_add =
safe_get_method_id(env, list_class, "add", "(Ljava/lang/Object;)Z");
jclass int_class = safe_find_class(env, "java/lang/Integer");
jmethodID int_init = safe_get_method_id(env, int_class, "<init>", "(I)V");
jclass long_class = safe_find_class(env, "java/lang/Long");
jmethodID long_init = safe_get_method_id(env, long_class, "<init>", "(J)V");
jobject jlist = bsg_anr_safe_new_object(env, bsg_anr_jni_cache->LinkedList,
bsg_anr_jni_cache->LinkedList_init);

jobject jlist = safe_new_object(env, list_class, list_init);
for (ssize_t i = 0; i < anr_stacktrace_length; i++) {
bugsnag_stackframe *frame = anr_stacktrace + i;
jobject jmethod = safe_new_string_utf(env, frame->method);
jobject jfilename = safe_new_string_utf(env, frame->filename);
jobject jline_number =
safe_new_object(env, int_class, int_init, (jint)frame->line_number);
jobject jframe_address = safe_new_object(env, long_class, long_init,
(jlong)frame->frame_address);
jobject jsymbol_address = safe_new_object(env, long_class, long_init,
(jlong)frame->symbol_address);
jobject jload_address =
safe_new_object(env, long_class, long_init, (jlong)frame->load_address);
jobject jframe = safe_new_object(
env, frame_class, frame_init, jmethod, jfilename, jline_number,
jframe_address, jsymbol_address, jload_address, NULL, error_type);
if (jlist != NULL && list_add != NULL && jframe != NULL) {
(*env)->CallBooleanMethod(env, jlist, list_add, jframe);
check_and_clear_exc(env);
jobject jmethod = bsg_anr_safe_new_string_utf(env, frame->method);
jobject jfilename = bsg_anr_safe_new_string_utf(env, frame->filename);
jobject jline_number = bsg_anr_safe_new_object(
env, bsg_anr_jni_cache->Integer, bsg_anr_jni_cache->Integer_init,
(jint)frame->line_number);
jobject jframe_address = bsg_anr_safe_new_object(
env, bsg_anr_jni_cache->Long, bsg_anr_jni_cache->Long_init,
(jlong)frame->frame_address);
jobject jsymbol_address = bsg_anr_safe_new_object(
env, bsg_anr_jni_cache->Long, bsg_anr_jni_cache->Long_init,
(jlong)frame->symbol_address);
jobject jload_address = bsg_anr_safe_new_object(
env, bsg_anr_jni_cache->Long, bsg_anr_jni_cache->Long_init,
(jlong)frame->load_address);
jobject jframe = bsg_anr_safe_new_object(
env, bsg_anr_jni_cache->NativeStackFrame,
bsg_anr_jni_cache->NativeStackFrame_init, jmethod, jfilename,
jline_number, jframe_address, jsymbol_address, jload_address, NULL,
bsg_anr_jni_cache->ErrorType_C);
if (jlist != NULL && jframe != NULL) {
(*env)->CallBooleanMethod(env, jlist, bsg_anr_jni_cache->LinkedList_add,
jframe);
bsg_anr_check_and_clear_exc(env);
}
safe_delete_local_ref(env, jmethod);
safe_delete_local_ref(env, jfilename);
safe_delete_local_ref(env, jline_number);
safe_delete_local_ref(env, jframe_address);
safe_delete_local_ref(env, jsymbol_address);
safe_delete_local_ref(env, jload_address);
safe_delete_local_ref(env, jframe);
bsg_anr_safe_delete_local_ref(env, jmethod);
bsg_anr_safe_delete_local_ref(env, jfilename);
bsg_anr_safe_delete_local_ref(env, jline_number);
bsg_anr_safe_delete_local_ref(env, jframe_address);
bsg_anr_safe_delete_local_ref(env, jsymbol_address);
bsg_anr_safe_delete_local_ref(env, jload_address);
bsg_anr_safe_delete_local_ref(env, jframe);
}

if (obj_plugin != NULL && mthd_notify_anr_detected != NULL && jlist != NULL) {
(*env)->CallVoidMethod(env, obj_plugin, mthd_notify_anr_detected, jlist);
check_and_clear_exc(env);
}

if (should_detach) {
(*bsg_jvm)->DetachCurrentThread(
bsg_jvm); // detach to restore initial condition
if (anr_plugin != NULL && jlist != NULL) {
(*env)->CallVoidMethod(
env, anr_plugin, bsg_anr_jni_cache->AnrPlugin_notifyAnrDetected, jlist);
bsg_anr_check_and_clear_exc(env);
}
bsg_anr_safe_delete_local_ref(env, jlist);
}

static inline void block_sigquit() {
Expand Down Expand Up @@ -295,15 +138,27 @@ static void watchdog_wait_for_trigger() {
_Noreturn static void *sigquit_watchdog_thread_main(__unused void *_) {
static const useconds_t delay_100ms = 100000;

JNIEnv *env = NULL;
if (bsg_anr_jni_cache->initialized) {
jint result = (*bsg_anr_jni_cache->jvm)
->AttachCurrentThread(bsg_anr_jni_cache->jvm, &env, NULL);
if (result != 0) {
BUGSNAG_LOG("Failed to call JNIEnv->AttachCurrentThread(): %d. ANRs will "
"not be reported.",
result);
env = NULL;
}
}

for (;;) {
watchdog_wait_for_trigger();

// Trigger Google ANR processing (occurs on a different thread).
bsg_google_anr_call();

if (enabled) {
if (enabled && env != NULL) {
// Do our ANR processing.
notify_anr_detected();
notify_anr_detected(env);
}

// Unblock SIGQUIT again so that handle_sigquit() will run again.
Expand Down Expand Up @@ -373,8 +228,8 @@ bool bsg_handler_install_anr(JNIEnv *env, jobject plugin) {
pthread_mutex_lock(&bsg_anr_handler_config);

enabled = true;
if (!installed && configure_anr_jni(env) && plugin != NULL) {
obj_plugin = (*env)->NewGlobalRef(env, plugin);
if (!installed && bsg_anr_jni_cache_init(env) && plugin != NULL) {
anr_plugin = (*env)->NewGlobalRef(env, plugin);
install_signal_handler();
installed = true;
}
Expand Down
Loading

0 comments on commit 87e9db1

Please sign in to comment.