From 6a05fdf6f759942ab7bda39e61b3f92dcc358533 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Mon, 21 Feb 2022 18:17:34 +0000 Subject: [PATCH 01/20] fix(ndk): use updated libunwindstack * removes "unwind style" logic in favor of a unified interface * updates C++ standard to c++17 (required for unwindstack's use of optional, etc) * splits unwinding into separate functions for signal/exception-time and user-generated events --- bugsnag-plugin-android-ndk/CMakeLists.txt | 2 +- .../src/main/CMakeLists.txt | 14 +-- .../src/main/jni/bugsnag.c | 3 +- .../src/main/jni/bugsnag_ndk.c | 34 +----- .../src/main/jni/bugsnag_ndk.h | 14 --- .../src/main/jni/external/libunwindstack-ndk | 2 +- .../src/main/jni/handlers/cpp_handler.cpp | 5 +- .../src/main/jni/handlers/signal_handler.c | 5 +- .../src/main/jni/utils/stack_unwinder.c | 83 --------------- .../src/main/jni/utils/stack_unwinder.cpp | 100 ++++++++++++++++++ .../src/main/jni/utils/stack_unwinder.h | 49 +++++---- .../utils/stack_unwinder_libunwindstack.cpp | 65 ------------ .../jni/utils/stack_unwinder_libunwindstack.h | 14 --- 13 files changed, 139 insertions(+), 251 deletions(-) delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c create mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h diff --git a/bugsnag-plugin-android-ndk/CMakeLists.txt b/bugsnag-plugin-android-ndk/CMakeLists.txt index cd96ddfd6a..f16af6162d 100644 --- a/bugsnag-plugin-android-ndk/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4.1) +cmake_minimum_required(VERSION 3.8.0) project(TEST) add_subdirectory(src/main) diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt index ca95c1d525..af8c44ba28 100644 --- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt @@ -20,8 +20,7 @@ add_library( # Specifies the name of the library. jni/utils/serializer/event_reader.c jni/utils/serializer/event_writer.c jni/utils/serializer/json_writer.c - jni/utils/stack_unwinder.c - jni/utils/stack_unwinder_libunwindstack.cpp + jni/utils/stack_unwinder.cpp jni/utils/stack_unwinder_libcorkscrew.c jni/utils/stack_unwinder_libunwind.c jni/utils/stack_unwinder_simple.c @@ -47,14 +46,9 @@ target_link_libraries( # Specifies the target library. set_target_properties(bugsnag-ndk PROPERTIES - COMPILE_OPTIONS - -Werror -Wall -pedantic) + COMPILE_OPTIONS -Werror -Wall -pedantic + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES) add_subdirectory(jni/external/libunwindstack-ndk/cmake) target_link_libraries(bugsnag-ndk unwindstack) -if(${ANDROID_ABI} STREQUAL "armeabi" OR ${ANDROID_ABI} STREQUAL "armeabi-v7a") - add_library(libunwind STATIC IMPORTED) - set_target_properties(libunwind PROPERTIES IMPORTED_LOCATION - ${ANDROID_NDK}/sources/cxx-stl/llvm-libc++/libs/${ANDROID_ABI}/libunwind.a) - target_link_libraries(bugsnag-ndk libunwind) -endif() diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c index 9798e5b53c..4c642ff868 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c @@ -127,8 +127,7 @@ void bugsnag_notify_env(JNIEnv *env, const char *name, const char *message, bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX]; memset(stacktrace, 0, sizeof(stacktrace)); - ssize_t frame_count = - bsg_unwind_stack(bsg_configured_unwind_style(), stacktrace, NULL, NULL); + ssize_t frame_count = bsg_unwind_concurrent_stack(stacktrace, NULL, NULL); // create StackTraceElement array jtrace = bsg_safe_new_object_array(env, frame_count, diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c index c98e3a7640..11f0502ce0 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -38,24 +38,6 @@ static void release_env_write_lock(void) { pthread_mutex_unlock(&bsg_global_env_write_mutex); } -bsg_unwinder bsg_configured_unwind_style() { - if (bsg_global_env != NULL) - return bsg_global_env->unwind_style; - - return BSG_CUSTOM_UNWIND; -} - -/** - * Get the configured unwind style for async-safe environments such as signal - * handlers. - */ -bsg_unwinder bsg_configured_signal_unwind_style() { - if (bsg_global_env != NULL) - return bsg_global_env->signal_unwind_style; - - return BSG_CUSTOM_UNWIND; -} - void bugsnag_add_on_error(bsg_on_error on_error) { if (bsg_global_env != NULL) { bsg_global_env->on_error = on_error; @@ -157,9 +139,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( } bsg_environment *bugsnag_env = calloc(1, sizeof(bsg_environment)); - bsg_set_unwind_types((int)_api_level, (bool)is32bit, - &bugsnag_env->signal_unwind_style, - &bugsnag_env->unwind_style); + bsg_unwinder_init(); bugsnag_env->report_header.big_endian = htonl(47) == 47; // potentially too clever, see man 3 htonl bugsnag_env->report_header.version = BUGSNAG_EVENT_VERSION; @@ -728,20 +708,10 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata( release_env_write_lock(); } -// Unwind the stack using the configured unwind style for signal handlers. -// This function gets exposed via -// Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction() -static ssize_t -bsg_unwind_stack_signal(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) __asyncsafe { - return bsg_unwind_stack(bsg_configured_signal_unwind_style(), stacktrace, - info, user_context); -} - JNIEXPORT jlong JNICALL Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction( JNIEnv *env, jobject thiz) { - return (jlong)bsg_unwind_stack_signal; + return (jlong)bsg_unwind_crash_stack; } JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag( diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h index 50fccf5f81..66506884d4 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h @@ -16,14 +16,6 @@ extern "C" { #endif typedef struct { - /** - * Unwinding style used for signal-safe handling - */ - bsg_unwinder signal_unwind_style; - /** - * Preferred unwinding style - */ - bsg_unwinder unwind_style; /** * Records the version of the bugsnag NDK report being serialized to disk. */ @@ -76,12 +68,6 @@ typedef struct { bsg_thread_send_policy send_threads; } bsg_environment; -/** - * Get the configured unwind style for non-async-safe environments. - * DO NOT USE THIS IN A SIGNAL HANDLER! - */ -bsg_unwinder bsg_configured_unwind_style(); - /** * Invokes the user-supplied on_error callback, if it has been set. This allows * users to mutate the bugsnag_event payload before it is persisted to disk, and diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk index d781d7b325..81b3598c5c 160000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk +++ b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit d781d7b325a6f64e4f63fc4e269a19944c7fe688 +Subproject commit 81b3598c5c60044ed8a58bc8ce32257f2da0704d diff --git a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp index 59098b082c..268cdf0b88 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp +++ b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp @@ -92,9 +92,8 @@ void bsg_handle_cpp_terminate() { bsg_global_env->handling_crash = true; bsg_populate_event_as(bsg_global_env); bsg_global_env->next_event.unhandled = true; - bsg_global_env->next_event.error.frame_count = - bsg_unwind_stack(bsg_global_env->unwind_style, - bsg_global_env->next_event.error.stacktrace, NULL, NULL); + bsg_global_env->next_event.error.frame_count = bsg_unwind_crash_stack( + bsg_global_env->next_event.error.stacktrace, NULL, NULL); if (bsg_global_env->send_threads != SEND_THREADS_NEVER) { bsg_global_env->next_event.thread_count = bsg_capture_thread_states( diff --git a/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c b/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c index 5f53554c6a..5c4f0cc42b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c @@ -182,8 +182,7 @@ void bsg_handle_signal(int signum, siginfo_t *info, bsg_global_env->handling_crash = true; bsg_global_env->next_event.unhandled = true; bsg_populate_event_as(bsg_global_env); - bsg_global_env->next_event.error.frame_count = bsg_unwind_stack( - bsg_global_env->signal_unwind_style, + bsg_global_env->next_event.error.frame_count = bsg_unwind_crash_stack( bsg_global_env->next_event.error.stacktrace, info, user_context); if (bsg_global_env->send_threads != SEND_THREADS_NEVER) { @@ -212,4 +211,4 @@ void bsg_handle_signal(int signum, siginfo_t *info, } bsg_handler_uninstall_signal(); bsg_invoke_previous_signal_handler(signum, info, user_context); -} \ No newline at end of file +} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c deleted file mode 100644 index 1ee8e67b24..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "stack_unwinder.h" -#include "stack_unwinder_libcorkscrew.h" -#include "stack_unwinder_libunwind.h" -#include "stack_unwinder_libunwindstack.h" -#include "stack_unwinder_simple.h" -#include "string.h" -#include -#include -#include -#include - -#define BSG_LIBUNWIND_LEVEL 21 -#define BSG_LIBUNWINDSTACK_LEVEL 15 -#define BSG_LIBUNWIND_LEVEL_ARM32 16 -#define BSG_LIBCORKSCREW_MIN_LEVEL 16 -#define BSG_LIBCORKSCREW_MAX_LEVEL 19 - -void bsg_set_unwind_types(int apiLevel, bool is32bit, bsg_unwinder *signal_type, - bsg_unwinder *other_type) { -#if defined(__arm__) - if (apiLevel >= BSG_LIBUNWIND_LEVEL_ARM32 && is32bit && - bsg_configure_libunwind(is32bit)) { - if (apiLevel >= BSG_LIBUNWIND_LEVEL) { - *signal_type = BSG_LIBUNWIND; - } else if (bsg_configure_libcorkscrew()) { - *signal_type = BSG_LIBCORKSCREW; - } else { - *signal_type = BSG_CUSTOM_UNWIND; - } - *other_type = BSG_LIBUNWIND; - return; - } -#endif - if (apiLevel >= BSG_LIBUNWINDSTACK_LEVEL) { - bsg_configure_libunwind(is32bit); - *signal_type = BSG_LIBUNWINDSTACK; - *other_type = BSG_LIBUNWIND; - } else { - *signal_type = BSG_CUSTOM_UNWIND; - *other_type = BSG_CUSTOM_UNWIND; - } -} - -void bsg_insert_fileinfo(ssize_t frame_count, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX]) { - static Dl_info info; - for (int i = 0; i < frame_count; ++i) { - if (dladdr((void *)stacktrace[i].frame_address, &info) != 0) { - stacktrace[i].load_address = (uintptr_t)info.dli_fbase; - stacktrace[i].symbol_address = (uintptr_t)info.dli_saddr; - stacktrace[i].line_number = - stacktrace[i].frame_address - stacktrace[i].load_address; - if (info.dli_fname != NULL) { - bsg_strncpy(stacktrace[i].filename, (char *)info.dli_fname, - sizeof(stacktrace[i].filename)); - } - if (info.dli_sname != NULL) { - bsg_strncpy(stacktrace[i].method, (char *)info.dli_sname, - sizeof(stacktrace[i].method)); - } - } - } -} - -ssize_t bsg_unwind_stack(bsg_unwinder unwind_style, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - ssize_t frame_count = 0; - if (unwind_style == BSG_LIBUNWINDSTACK) { - frame_count = - bsg_unwind_stack_libunwindstack(stacktrace, info, user_context); - } else if (unwind_style == BSG_LIBUNWIND) { - frame_count = bsg_unwind_stack_libunwind(stacktrace, info, user_context); - } else if (unwind_style == BSG_LIBCORKSCREW) { - frame_count = bsg_unwind_stack_libcorkscrew(stacktrace, info, user_context); - } else { - frame_count = bsg_unwind_stack_simple(stacktrace, info, user_context); - } - bsg_insert_fileinfo(frame_count, - stacktrace); // none of this is safe ¯\_(ツ)_/¯ - - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp new file mode 100644 index 0000000000..715cad8a72 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp @@ -0,0 +1,100 @@ +#include "stack_unwinder.h" + +#include "string.h" + +#include +#include +#include +#include +#include +#include + +// unwinder intended for a potentially terminating context +static unwindstack::Unwinder *crash_time_unwinder; + +// Thread-safe, reusable unwinder - uses thread-specific memory caches +static unwindstack::LocalUnwinder *current_time_unwinder; + +static bool attempted_init; + +void bsg_unwinder_init() { + if (attempted_init) { + // already initialized or failed to init, cannot be done more than once + return; + } + attempted_init = true; + + auto crash_time_maps = new unwindstack::LocalMaps(); + if (crash_time_maps->Parse()) { + std::shared_ptr crash_time_memory( + new unwindstack::MemoryLocal); + crash_time_unwinder = new unwindstack::Unwinder( + BUGSNAG_FRAMES_MAX, crash_time_maps, + unwindstack::Regs::CreateFromLocal(), crash_time_memory); + auto arch = unwindstack::Regs::CurrentArch(); + auto dexfiles_ptr = unwindstack::CreateDexFiles(arch, crash_time_memory); + crash_time_unwinder->SetDexFiles(dexfiles_ptr.get()); + } + + current_time_unwinder = new unwindstack::LocalUnwinder(); + if (!current_time_unwinder->Init()) { + delete current_time_unwinder; + current_time_unwinder = nullptr; + } +} + +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context) { + if (crash_time_unwinder == nullptr) { + return 0; + } + if (user_context) { + crash_time_unwinder->SetRegs(unwindstack::Regs::CreateFromUcontext( + unwindstack::Regs::CurrentArch(), user_context)); + } else { + auto regs = unwindstack::Regs::CreateFromLocal(); + unwindstack::RegsGetLocal(regs); + crash_time_unwinder->SetRegs(regs); + } + + crash_time_unwinder->Unwind(); + int frame_count = 0; + for (auto &frame : crash_time_unwinder->frames()) { + stack[frame_count].frame_address = frame.pc; + stack[frame_count].line_number = frame.rel_pc; + stack[frame_count].load_address = frame.map_start; + stack[frame_count].symbol_address = frame.pc - frame.function_offset; + bsg_strncpy(stack[frame_count].filename, frame.map_name.c_str(), + sizeof(stack[frame_count].filename)); + bsg_strncpy(stack[frame_count].method, frame.function_name.c_str(), + sizeof(stack[frame_count].method)); + frame_count++; + } + return frame_count; +} + +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *_info, void *_context) { + if (current_time_unwinder == nullptr) { + return 0; + } + + std::vector frames; + current_time_unwinder->Unwind(&frames, BUGSNAG_FRAMES_MAX); + int frame_count = 0; + for (auto &frame : frames) { + stack[frame_count].frame_address = frame.pc; + if (frame.map_info != nullptr) { + stack[frame_count].line_number = frame.rel_pc; + stack[frame_count].load_address = frame.map_info->start(); + stack[frame_count].symbol_address = frame.pc - frame.map_info->offset(); + bsg_strncpy(stack[frame_count].filename, frame.map_info->name().c_str(), + sizeof(stack[frame_count].filename)); + } + bsg_strncpy(stack[frame_count].method, frame.function_name.c_str(), + sizeof(stack[frame_count].method)); + frame_count++; + } + return frame_count; +} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h index 161290370d..25078e898b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h @@ -1,5 +1,4 @@ -#ifndef BSG_STACK_UNWINDER_H -#define BSG_STACK_UNWINDER_H +#pragma once #include "build.h" #include @@ -9,36 +8,40 @@ extern "C" { #endif -typedef enum { - BSG_LIBUNWIND, - BSG_LIBUNWINDSTACK, - BSG_LIBCORKSCREW, - BSG_CUSTOM_UNWIND, -} bsg_unwinder; +/** + * Initialize the stack unwinder. Must be called prior to initial use. + */ +void bsg_unwinder_init(void); /** - * Based on the current environment, determine what unwinding library to use. + * Unwind a stack in a terminating context. If info and a user context pointer + * are provided, the exception stack will be walked. Otherwise, the current + * stack will be walked. The results will populate the stack. * - * Android API level 21+: libunwind - * Android API level 16-19: libunwind, unless in a signal handler. Then - * libcorkscrew. - * Everything else: custom unwinding logic + * @param stack buffer to contain the frame contents + * @param info signal info or null if none + * @param user_context crash context or null if none + * + * @return the number of frames */ -void bsg_set_unwind_types(int apiLevel, bool is32bit, bsg_unwinder *signal_type, - bsg_unwinder *other_type); +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context) __asyncsafe; /** - * Unwind the stack using the preferred tool/style. If info and a user - * context pointer are provided, the exception stack will be walked. Otherwise, - * the current stack will be walked instead. The results will populate the - * stacktrace + * Unwind a stack in a thread-safe context. If info and a user context pointer + * are provided, the exception stack will be walked. Otherwise, the current + * stack will be walked. The results will populate the stack. + * + * @param stack buffer to contain the frame contents + * @param info IGNORED - provided for signature compatibility + * @param user_context IGNORED - provided for signature compatibility + * * @return the number of frames */ -ssize_t bsg_unwind_stack(bsg_unwinder unwind_style, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) __asyncsafe; +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); #ifdef __cplusplus } #endif -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp deleted file mode 100644 index 30b5180230..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "stack_unwinder_libunwindstack.h" -#include "string.h" -#include -#include -#include -#include -#include -#include -#include - -ssize_t bsg_unwind_stack_libunwindstack( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context) { - if (user_context == NULL) { - return 0; // only handle unwinding from signals - } - - // Fetch register values from signal context. To get registers without - // a context, use unwindstack::Regs::CreateFromLocal() - const std::unique_ptr regs( - unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), - user_context)); - - std::string unw_function_name; - unwindstack::LocalMaps maps; - - if (!maps.Parse()) { - stacktrace[0].frame_address = regs->pc(); // only known frame - return 1; - } - - const std::shared_ptr memory( - new unwindstack::MemoryLocal); - - int frame_count = 0; - for (int i = 0; i < BUGSNAG_FRAMES_MAX; i++) { - stacktrace[frame_count++].frame_address = regs->pc(); - unwindstack::MapInfo *const map_info = maps.Find(regs->pc()); - if (!map_info) { - break; - } - unwindstack::Elf *const elf = map_info->GetElf(memory, false); - if (!elf) { - break; - } - - // Getting value of program counter relative module where a function is - // located. - const uint64_t rel_pc = elf->GetRelPc(regs->pc(), map_info); - uint64_t adjusted_rel_pc = rel_pc; - if (frame_count != 0) { - // If it's not a first frame we need to rewind program counter value to - // previous instruction. For the first frame pc from ucontext points - // exactly to a failed instruction, for other frames rel_pc will contain - // return address after function call instruction. - adjusted_rel_pc -= regs->GetPcAdjustment(rel_pc, elf); - } - bool finished = false; - if (!elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs.get(), - memory.get(), &finished)) { - break; - } - } - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h deleted file mode 100644 index 6f6aa884a2..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWINDSTACK_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWINDSTACK_H - -#include "../event.h" -#include - -#ifdef __cplusplus -extern "C" -#endif - ssize_t - bsg_unwind_stack_libunwindstack( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context); -#endif From e7d91ac4db1d4b43b58bf0cc1083b119e89b0ba1 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 22 Feb 2022 09:43:56 +0000 Subject: [PATCH 02/20] test(ndk): add simple assertions for unwind --- .../com/bugsnag/android/ndk/UnwindTest.kt | 45 ++++++++++++ .../src/test/CMakeLists.txt | 1 + .../src/test/cpp/UnwindTest.cpp | 72 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt create mode 100644 bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt new file mode 100644 index 0000000000..655cbc7e0d --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt @@ -0,0 +1,45 @@ +package com.bugsnag.android.ndk + +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Test + +class UnwindTest { + + @Test + fun testUnwindForNotify() { + val frames = unwindForNotify() + // the top of the stack includes the unwinder itself, etc, so first + // scan for the expected contents, which must be a sequence + var offset = frames.indexOf("unwind_func_four") + if (offset == -1) { + fail("did not find initial stack frame in list: $frames") + } + assertEquals("unwind_func_four", frames[offset]) + assertEquals("unwind_func_three", frames[offset + 1]) + assertEquals("unwind_func_two", frames[offset + 2]) + assertEquals("unwind_func_one", frames[offset + 3]) + assertEquals("Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify", frames[offset + 4]) + } + + @Test + fun testUnwindForCrash() { + val frames = unwindForCrash() + // the top of the stack includes the unwinder itself, etc, so first + // scan for the expected contents, which must be a sequence + var offset = frames.indexOf("unwind_func_four") + if (offset == -1) { + fail("did not find initial stack frame in list: $frames") + } + assertEquals("unwind_func_four", frames[offset]) + assertEquals("unwind_func_three", frames[offset + 1]) + assertEquals("unwind_func_two", frames[offset + 2]) + assertEquals("unwind_func_one", frames[offset + 3]) + assertEquals("Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash", frames[offset + 4]) + } + + // unwind a known set of functions and validate the method names in the + // stack + external fun unwindForNotify(): List + external fun unwindForCrash(): List +} diff --git a/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt index fb883f598b..b8688cfb1a 100644 --- a/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt @@ -17,5 +17,6 @@ add_library(bugsnag-ndk-test SHARED cpp/migrations/EventMigrationV6Tests.cpp cpp/migrations/EventMigrationV7Tests.cpp cpp/migrations/EventMigrationV8Tests.cpp + cpp/UnwindTest.cpp ) target_link_libraries(bugsnag-ndk-test bugsnag-ndk) diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp new file mode 100644 index 0000000000..ecfcfcce36 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef ssize_t (*unwinder)(bugsnag_stackframe *, siginfo *, void *); + +ssize_t +bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); + +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); + +// the following functions are marked as optnone to make stack contents +// assertions uniform across architectures, etc +// https://clang.llvm.org/docs/AttributeReference.html#optnone + +jobject __attribute__((optnone)) unwind_func_four(JNIEnv *env, unwinder func) { + bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX]; + ssize_t count = func(stack, nullptr, nullptr); + + // find JNI references + auto ArrayList = (*env).FindClass("java/util/ArrayList"); + auto init = (*env).GetMethodID(ArrayList, "", "()V"); + auto add = (*env).GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z"); + + // make list instance + auto items = (*env).NewObject(ArrayList, init); + + for (int index = 0; index < count; index++) { + // using empty string as a sentinel if method is null + auto method = (*env).NewStringUTF(stack[index].method ?: ""); + (*env).CallBooleanMethod(items, add, method); + } + return items; +} + +jobject __attribute__((optnone)) unwind_func_three(JNIEnv *env, unwinder func) { + return unwind_func_four(env, func); +} + +jobject __attribute__((optnone)) unwind_func_two(JNIEnv *env, unwinder func) { + return unwind_func_three(env, func); +} + +jobject __attribute__((optnone)) unwind_func_one(JNIEnv *env, unwinder func) { + return unwind_func_two(env, func); +} + +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify(JNIEnv *env, + jobject _this) { + bsg_unwinder_init(); + return unwind_func_one(env, bsg_unwind_concurrent_stack); +} + +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash(JNIEnv *env, + jobject _this) { + bsg_unwinder_init(); + return unwind_func_one(env, bsg_unwind_crash_stack); +} + +#ifdef __cplusplus +} +#endif From c79cb388a2c8cb6afda2e17ed04b5b58853b88c1 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Mon, 21 Feb 2022 18:20:53 +0000 Subject: [PATCH 03/20] build(ndk): remove unused files --- .../src/main/CMakeLists.txt | 4 - .../libunwind/include/__libunwind_config.h | 55 -- .../external/libunwind/include/libunwind.h | 508 ------------------ .../jni/utils/stack_unwinder_libcorkscrew.c | 124 ----- .../jni/utils/stack_unwinder_libcorkscrew.h | 12 - .../main/jni/utils/stack_unwinder_libunwind.c | 116 ---- .../main/jni/utils/stack_unwinder_libunwind.h | 13 - .../main/jni/utils/stack_unwinder_simple.c | 31 -- .../main/jni/utils/stack_unwinder_simple.h | 10 - 9 files changed, 873 deletions(-) delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c delete mode 100644 bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt index af8c44ba28..2eec067ed9 100644 --- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt @@ -21,9 +21,6 @@ add_library( # Specifies the name of the library. jni/utils/serializer/event_writer.c jni/utils/serializer/json_writer.c jni/utils/stack_unwinder.cpp - jni/utils/stack_unwinder_libcorkscrew.c - jni/utils/stack_unwinder_libunwind.c - jni/utils/stack_unwinder_simple.c jni/utils/serializer.c jni/utils/string.c jni/utils/threads.c @@ -33,7 +30,6 @@ add_library( # Specifies the name of the library. include_directories( jni jni/deps - jni/external/libunwind/include jni/external/libunwindstack-ndk/include ) diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h deleted file mode 100644 index bc4e696c0c..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h +++ /dev/null @@ -1,55 +0,0 @@ -//===------------------------- __libunwind_config.h -----------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -#ifndef ____LIBUNWIND_CONFIG_H__ -#define ____LIBUNWIND_CONFIG_H__ -#if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \ - !defined(__ARM_DWARF_EH__) -#define _LIBUNWIND_ARM_EHABI 1 -#else -#define _LIBUNWIND_ARM_EHABI 0 -#endif -#if defined(_LIBUNWIND_IS_NATIVE_ONLY) -# if defined(__i386__) -# define _LIBUNWIND_TARGET_I386 1 -# define _LIBUNWIND_CONTEXT_SIZE 8 -# define _LIBUNWIND_CURSOR_SIZE 19 -# elif defined(__x86_64__) -# define _LIBUNWIND_TARGET_X86_64 1 -# define _LIBUNWIND_CONTEXT_SIZE 21 -# define _LIBUNWIND_CURSOR_SIZE 33 -# elif defined(__ppc__) -# define _LIBUNWIND_TARGET_PPC 1 -# define _LIBUNWIND_CONTEXT_SIZE 117 -# define _LIBUNWIND_CURSOR_SIZE 128 -# elif defined(__aarch64__) -# define _LIBUNWIND_TARGET_AARCH64 1 -# define _LIBUNWIND_CONTEXT_SIZE 66 -# define _LIBUNWIND_CURSOR_SIZE 78 -# elif defined(__arm__) -# define _LIBUNWIND_TARGET_ARM 1 -# define _LIBUNWIND_CONTEXT_SIZE 60 -# define _LIBUNWIND_CURSOR_SIZE 67 -# elif defined(__or1k__) -# define _LIBUNWIND_TARGET_OR1K 1 -# define _LIBUNWIND_CONTEXT_SIZE 16 -# define _LIBUNWIND_CURSOR_SIZE 28 -# else -# error "Unsupported architecture." -# endif -#else // !_LIBUNWIND_IS_NATIVE_ONLY -# define _LIBUNWIND_TARGET_I386 1 -# define _LIBUNWIND_TARGET_X86_64 1 -# define _LIBUNWIND_TARGET_PPC 1 -# define _LIBUNWIND_TARGET_AARCH64 1 -# define _LIBUNWIND_TARGET_ARM 1 -# define _LIBUNWIND_TARGET_OR1K 1 -# define _LIBUNWIND_CONTEXT_SIZE 128 -# define _LIBUNWIND_CURSOR_SIZE 140 -#endif // _LIBUNWIND_IS_NATIVE_ONLY -#endif // ____LIBUNWIND_CONFIG_H__ \ No newline at end of file diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h deleted file mode 100644 index 29917662a8..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h +++ /dev/null @@ -1,508 +0,0 @@ -//===---------------------------- libunwind.h -----------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -// -// Compatible with libuwind API documented at: -// http://www.nongnu.org/libunwind/man/libunwind(3).html -// -//===----------------------------------------------------------------------===// -#ifndef __LIBUNWIND__ -#define __LIBUNWIND__ -#include "__libunwind_config.h" -#include -#include -#ifdef __APPLE__ - #include - #ifdef __arm__ - #define LIBUNWIND_AVAIL __attribute__((unavailable)) - #else - #define LIBUNWIND_AVAIL __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0) - #endif -#else - #define LIBUNWIND_AVAIL -#endif -/* error codes */ -enum { - UNW_ESUCCESS = 0, /* no error */ - UNW_EUNSPEC = -6540, /* unspecified (general) error */ - UNW_ENOMEM = -6541, /* out of memory */ - UNW_EBADREG = -6542, /* bad register number */ - UNW_EREADONLYREG = -6543, /* attempt to write read-only register */ - UNW_ESTOPUNWIND = -6544, /* stop unwinding */ - UNW_EINVALIDIP = -6545, /* invalid IP */ - UNW_EBADFRAME = -6546, /* bad frame */ - UNW_EINVAL = -6547, /* unsupported operation or bad value */ - UNW_EBADVERSION = -6548, /* unwind info has unsupported version */ - UNW_ENOINFO = -6549 /* no unwind info found */ -}; -struct unw_context_t { - uint64_t data[_LIBUNWIND_CONTEXT_SIZE]; -}; -typedef struct unw_context_t unw_context_t; -struct unw_cursor_t { - uint64_t data[_LIBUNWIND_CURSOR_SIZE]; -}; -typedef struct unw_cursor_t unw_cursor_t; -typedef struct unw_addr_space *unw_addr_space_t; -typedef int unw_regnum_t; -#if _LIBUNWIND_ARM_EHABI -typedef uint32_t unw_word_t; -typedef uint64_t unw_fpreg_t; -#else -typedef uint64_t unw_word_t; -typedef double unw_fpreg_t; -#endif -struct unw_proc_info_t { - unw_word_t start_ip; /* start address of function */ - unw_word_t end_ip; /* address after end of function */ - unw_word_t lsda; /* address of language specific data area, */ - /* or zero if not used */ - unw_word_t handler; /* personality routine, or zero if not used */ - unw_word_t gp; /* not used */ - unw_word_t flags; /* not used */ - uint32_t format; /* compact unwind encoding, or zero if none */ - uint32_t unwind_info_size; /* size of dwarf unwind info, or zero if none */ - unw_word_t unwind_info; /* address of dwarf unwind info, or zero */ - unw_word_t extra; /* mach_header of mach-o image containing func */ -}; -typedef struct unw_proc_info_t unw_proc_info_t; -#ifdef __cplusplus -extern "C" { -#endif -extern int unw_getcontext(unw_context_t *) LIBUNWIND_AVAIL; -extern int unw_init_local(unw_cursor_t *, unw_context_t *) LIBUNWIND_AVAIL; -extern int unw_step(unw_cursor_t *) LIBUNWIND_AVAIL; -extern int unw_get_reg(unw_cursor_t *, unw_regnum_t, unw_word_t *) LIBUNWIND_AVAIL; -extern int unw_get_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t *) LIBUNWIND_AVAIL; -extern int unw_set_reg(unw_cursor_t *, unw_regnum_t, unw_word_t) LIBUNWIND_AVAIL; -extern int unw_set_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t) LIBUNWIND_AVAIL; -extern int unw_resume(unw_cursor_t *) LIBUNWIND_AVAIL; -#ifdef __arm__ -/* Save VFP registers in FSTMX format (instead of FSTMD). */ -extern void unw_save_vfp_as_X(unw_cursor_t *) LIBUNWIND_AVAIL; -#endif -extern const char *unw_regname(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; -extern int unw_get_proc_info(unw_cursor_t *, unw_proc_info_t *) LIBUNWIND_AVAIL; -extern int unw_is_fpreg(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; -extern int unw_is_signal_frame(unw_cursor_t *) LIBUNWIND_AVAIL; -extern int unw_get_proc_name(unw_cursor_t *, char *, size_t, unw_word_t *) LIBUNWIND_AVAIL; -//extern int unw_get_save_loc(unw_cursor_t*, int, unw_save_loc_t*); -extern unw_addr_space_t unw_local_addr_space; -#ifdef UNW_REMOTE -/* - * Mac OS X "remote" API for unwinding other processes on same machine - * - */ -extern unw_addr_space_t unw_create_addr_space_for_task(task_t); -extern void unw_destroy_addr_space(unw_addr_space_t); -extern int unw_init_remote_thread(unw_cursor_t *, unw_addr_space_t, thread_t *); -#endif /* UNW_REMOTE */ -/* - * traditional libuwind "remote" API - * NOT IMPLEMENTED on Mac OS X - * - * extern int unw_init_remote(unw_cursor_t*, unw_addr_space_t, - * thread_t*); - * extern unw_accessors_t unw_get_accessors(unw_addr_space_t); - * extern unw_addr_space_t unw_create_addr_space(unw_accessors_t, int); - * extern void unw_flush_cache(unw_addr_space_t, unw_word_t, - * unw_word_t); - * extern int unw_set_caching_policy(unw_addr_space_t, - * unw_caching_policy_t); - * extern void _U_dyn_register(unw_dyn_info_t*); - * extern void _U_dyn_cancel(unw_dyn_info_t*); - */ -#ifdef __cplusplus -} -#endif -// architecture independent register numbers -enum { - UNW_REG_IP = -1, // instruction pointer - UNW_REG_SP = -2, // stack pointer -}; -// 32-bit x86 registers -enum { - UNW_X86_EAX = 0, - UNW_X86_ECX = 1, - UNW_X86_EDX = 2, - UNW_X86_EBX = 3, - UNW_X86_EBP = 4, - UNW_X86_ESP = 5, - UNW_X86_ESI = 6, - UNW_X86_EDI = 7 -}; -// 64-bit x86_64 registers -enum { - UNW_X86_64_RAX = 0, - UNW_X86_64_RDX = 1, - UNW_X86_64_RCX = 2, - UNW_X86_64_RBX = 3, - UNW_X86_64_RSI = 4, - UNW_X86_64_RDI = 5, - UNW_X86_64_RBP = 6, - UNW_X86_64_RSP = 7, - UNW_X86_64_R8 = 8, - UNW_X86_64_R9 = 9, - UNW_X86_64_R10 = 10, - UNW_X86_64_R11 = 11, - UNW_X86_64_R12 = 12, - UNW_X86_64_R13 = 13, - UNW_X86_64_R14 = 14, - UNW_X86_64_R15 = 15 -}; -// 32-bit ppc register numbers -enum { - UNW_PPC_R0 = 0, - UNW_PPC_R1 = 1, - UNW_PPC_R2 = 2, - UNW_PPC_R3 = 3, - UNW_PPC_R4 = 4, - UNW_PPC_R5 = 5, - UNW_PPC_R6 = 6, - UNW_PPC_R7 = 7, - UNW_PPC_R8 = 8, - UNW_PPC_R9 = 9, - UNW_PPC_R10 = 10, - UNW_PPC_R11 = 11, - UNW_PPC_R12 = 12, - UNW_PPC_R13 = 13, - UNW_PPC_R14 = 14, - UNW_PPC_R15 = 15, - UNW_PPC_R16 = 16, - UNW_PPC_R17 = 17, - UNW_PPC_R18 = 18, - UNW_PPC_R19 = 19, - UNW_PPC_R20 = 20, - UNW_PPC_R21 = 21, - UNW_PPC_R22 = 22, - UNW_PPC_R23 = 23, - UNW_PPC_R24 = 24, - UNW_PPC_R25 = 25, - UNW_PPC_R26 = 26, - UNW_PPC_R27 = 27, - UNW_PPC_R28 = 28, - UNW_PPC_R29 = 29, - UNW_PPC_R30 = 30, - UNW_PPC_R31 = 31, - UNW_PPC_F0 = 32, - UNW_PPC_F1 = 33, - UNW_PPC_F2 = 34, - UNW_PPC_F3 = 35, - UNW_PPC_F4 = 36, - UNW_PPC_F5 = 37, - UNW_PPC_F6 = 38, - UNW_PPC_F7 = 39, - UNW_PPC_F8 = 40, - UNW_PPC_F9 = 41, - UNW_PPC_F10 = 42, - UNW_PPC_F11 = 43, - UNW_PPC_F12 = 44, - UNW_PPC_F13 = 45, - UNW_PPC_F14 = 46, - UNW_PPC_F15 = 47, - UNW_PPC_F16 = 48, - UNW_PPC_F17 = 49, - UNW_PPC_F18 = 50, - UNW_PPC_F19 = 51, - UNW_PPC_F20 = 52, - UNW_PPC_F21 = 53, - UNW_PPC_F22 = 54, - UNW_PPC_F23 = 55, - UNW_PPC_F24 = 56, - UNW_PPC_F25 = 57, - UNW_PPC_F26 = 58, - UNW_PPC_F27 = 59, - UNW_PPC_F28 = 60, - UNW_PPC_F29 = 61, - UNW_PPC_F30 = 62, - UNW_PPC_F31 = 63, - UNW_PPC_MQ = 64, - UNW_PPC_LR = 65, - UNW_PPC_CTR = 66, - UNW_PPC_AP = 67, - UNW_PPC_CR0 = 68, - UNW_PPC_CR1 = 69, - UNW_PPC_CR2 = 70, - UNW_PPC_CR3 = 71, - UNW_PPC_CR4 = 72, - UNW_PPC_CR5 = 73, - UNW_PPC_CR6 = 74, - UNW_PPC_CR7 = 75, - UNW_PPC_XER = 76, - UNW_PPC_V0 = 77, - UNW_PPC_V1 = 78, - UNW_PPC_V2 = 79, - UNW_PPC_V3 = 80, - UNW_PPC_V4 = 81, - UNW_PPC_V5 = 82, - UNW_PPC_V6 = 83, - UNW_PPC_V7 = 84, - UNW_PPC_V8 = 85, - UNW_PPC_V9 = 86, - UNW_PPC_V10 = 87, - UNW_PPC_V11 = 88, - UNW_PPC_V12 = 89, - UNW_PPC_V13 = 90, - UNW_PPC_V14 = 91, - UNW_PPC_V15 = 92, - UNW_PPC_V16 = 93, - UNW_PPC_V17 = 94, - UNW_PPC_V18 = 95, - UNW_PPC_V19 = 96, - UNW_PPC_V20 = 97, - UNW_PPC_V21 = 98, - UNW_PPC_V22 = 99, - UNW_PPC_V23 = 100, - UNW_PPC_V24 = 101, - UNW_PPC_V25 = 102, - UNW_PPC_V26 = 103, - UNW_PPC_V27 = 104, - UNW_PPC_V28 = 105, - UNW_PPC_V29 = 106, - UNW_PPC_V30 = 107, - UNW_PPC_V31 = 108, - UNW_PPC_VRSAVE = 109, - UNW_PPC_VSCR = 110, - UNW_PPC_SPE_ACC = 111, - UNW_PPC_SPEFSCR = 112 -}; -// 64-bit ARM64 registers -enum { - UNW_ARM64_X0 = 0, - UNW_ARM64_X1 = 1, - UNW_ARM64_X2 = 2, - UNW_ARM64_X3 = 3, - UNW_ARM64_X4 = 4, - UNW_ARM64_X5 = 5, - UNW_ARM64_X6 = 6, - UNW_ARM64_X7 = 7, - UNW_ARM64_X8 = 8, - UNW_ARM64_X9 = 9, - UNW_ARM64_X10 = 10, - UNW_ARM64_X11 = 11, - UNW_ARM64_X12 = 12, - UNW_ARM64_X13 = 13, - UNW_ARM64_X14 = 14, - UNW_ARM64_X15 = 15, - UNW_ARM64_X16 = 16, - UNW_ARM64_X17 = 17, - UNW_ARM64_X18 = 18, - UNW_ARM64_X19 = 19, - UNW_ARM64_X20 = 20, - UNW_ARM64_X21 = 21, - UNW_ARM64_X22 = 22, - UNW_ARM64_X23 = 23, - UNW_ARM64_X24 = 24, - UNW_ARM64_X25 = 25, - UNW_ARM64_X26 = 26, - UNW_ARM64_X27 = 27, - UNW_ARM64_X28 = 28, - UNW_ARM64_X29 = 29, - UNW_ARM64_FP = 29, - UNW_ARM64_X30 = 30, - UNW_ARM64_LR = 30, - UNW_ARM64_X31 = 31, - UNW_ARM64_SP = 31, - // reserved block - UNW_ARM64_D0 = 64, - UNW_ARM64_D1 = 65, - UNW_ARM64_D2 = 66, - UNW_ARM64_D3 = 67, - UNW_ARM64_D4 = 68, - UNW_ARM64_D5 = 69, - UNW_ARM64_D6 = 70, - UNW_ARM64_D7 = 71, - UNW_ARM64_D8 = 72, - UNW_ARM64_D9 = 73, - UNW_ARM64_D10 = 74, - UNW_ARM64_D11 = 75, - UNW_ARM64_D12 = 76, - UNW_ARM64_D13 = 77, - UNW_ARM64_D14 = 78, - UNW_ARM64_D15 = 79, - UNW_ARM64_D16 = 80, - UNW_ARM64_D17 = 81, - UNW_ARM64_D18 = 82, - UNW_ARM64_D19 = 83, - UNW_ARM64_D20 = 84, - UNW_ARM64_D21 = 85, - UNW_ARM64_D22 = 86, - UNW_ARM64_D23 = 87, - UNW_ARM64_D24 = 88, - UNW_ARM64_D25 = 89, - UNW_ARM64_D26 = 90, - UNW_ARM64_D27 = 91, - UNW_ARM64_D28 = 92, - UNW_ARM64_D29 = 93, - UNW_ARM64_D30 = 94, - UNW_ARM64_D31 = 95, -}; -// 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1. -// Naming scheme uses recommendations given in Note 4 for VFP-v2 and VFP-v3. -// In this scheme, even though the 64-bit floating point registers D0-D31 -// overlap physically with the 32-bit floating pointer registers S0-S31, -// they are given a non-overlapping range of register numbers. -// -// Commented out ranges are not preserved during unwinding. -enum { - UNW_ARM_R0 = 0, - UNW_ARM_R1 = 1, - UNW_ARM_R2 = 2, - UNW_ARM_R3 = 3, - UNW_ARM_R4 = 4, - UNW_ARM_R5 = 5, - UNW_ARM_R6 = 6, - UNW_ARM_R7 = 7, - UNW_ARM_R8 = 8, - UNW_ARM_R9 = 9, - UNW_ARM_R10 = 10, - UNW_ARM_R11 = 11, - UNW_ARM_R12 = 12, - UNW_ARM_SP = 13, // Logical alias for UNW_REG_SP - UNW_ARM_R13 = 13, - UNW_ARM_LR = 14, - UNW_ARM_R14 = 14, - UNW_ARM_IP = 15, // Logical alias for UNW_REG_IP - UNW_ARM_R15 = 15, - // 16-63 -- OBSOLETE. Used in VFP1 to represent both S0-S31 and D0-D31. - UNW_ARM_S0 = 64, - UNW_ARM_S1 = 65, - UNW_ARM_S2 = 66, - UNW_ARM_S3 = 67, - UNW_ARM_S4 = 68, - UNW_ARM_S5 = 69, - UNW_ARM_S6 = 70, - UNW_ARM_S7 = 71, - UNW_ARM_S8 = 72, - UNW_ARM_S9 = 73, - UNW_ARM_S10 = 74, - UNW_ARM_S11 = 75, - UNW_ARM_S12 = 76, - UNW_ARM_S13 = 77, - UNW_ARM_S14 = 78, - UNW_ARM_S15 = 79, - UNW_ARM_S16 = 80, - UNW_ARM_S17 = 81, - UNW_ARM_S18 = 82, - UNW_ARM_S19 = 83, - UNW_ARM_S20 = 84, - UNW_ARM_S21 = 85, - UNW_ARM_S22 = 86, - UNW_ARM_S23 = 87, - UNW_ARM_S24 = 88, - UNW_ARM_S25 = 89, - UNW_ARM_S26 = 90, - UNW_ARM_S27 = 91, - UNW_ARM_S28 = 92, - UNW_ARM_S29 = 93, - UNW_ARM_S30 = 94, - UNW_ARM_S31 = 95, - // 96-103 -- OBSOLETE. F0-F7. Used by the FPA system. Superseded by VFP. - // 104-111 -- wCGR0-wCGR7, ACC0-ACC7 (Intel wireless MMX) - UNW_ARM_WR0 = 112, - UNW_ARM_WR1 = 113, - UNW_ARM_WR2 = 114, - UNW_ARM_WR3 = 115, - UNW_ARM_WR4 = 116, - UNW_ARM_WR5 = 117, - UNW_ARM_WR6 = 118, - UNW_ARM_WR7 = 119, - UNW_ARM_WR8 = 120, - UNW_ARM_WR9 = 121, - UNW_ARM_WR10 = 122, - UNW_ARM_WR11 = 123, - UNW_ARM_WR12 = 124, - UNW_ARM_WR13 = 125, - UNW_ARM_WR14 = 126, - UNW_ARM_WR15 = 127, - // 128-133 -- SPSR, SPSR_{FIQ|IRQ|ABT|UND|SVC} - // 134-143 -- Reserved - // 144-150 -- R8_USR-R14_USR - // 151-157 -- R8_FIQ-R14_FIQ - // 158-159 -- R13_IRQ-R14_IRQ - // 160-161 -- R13_ABT-R14_ABT - // 162-163 -- R13_UND-R14_UND - // 164-165 -- R13_SVC-R14_SVC - // 166-191 -- Reserved - UNW_ARM_WC0 = 192, - UNW_ARM_WC1 = 193, - UNW_ARM_WC2 = 194, - UNW_ARM_WC3 = 195, - // 196-199 -- wC4-wC7 (Intel wireless MMX control) - // 200-255 -- Reserved - UNW_ARM_D0 = 256, - UNW_ARM_D1 = 257, - UNW_ARM_D2 = 258, - UNW_ARM_D3 = 259, - UNW_ARM_D4 = 260, - UNW_ARM_D5 = 261, - UNW_ARM_D6 = 262, - UNW_ARM_D7 = 263, - UNW_ARM_D8 = 264, - UNW_ARM_D9 = 265, - UNW_ARM_D10 = 266, - UNW_ARM_D11 = 267, - UNW_ARM_D12 = 268, - UNW_ARM_D13 = 269, - UNW_ARM_D14 = 270, - UNW_ARM_D15 = 271, - UNW_ARM_D16 = 272, - UNW_ARM_D17 = 273, - UNW_ARM_D18 = 274, - UNW_ARM_D19 = 275, - UNW_ARM_D20 = 276, - UNW_ARM_D21 = 277, - UNW_ARM_D22 = 278, - UNW_ARM_D23 = 279, - UNW_ARM_D24 = 280, - UNW_ARM_D25 = 281, - UNW_ARM_D26 = 282, - UNW_ARM_D27 = 283, - UNW_ARM_D28 = 284, - UNW_ARM_D29 = 285, - UNW_ARM_D30 = 286, - UNW_ARM_D31 = 287, - // 288-319 -- Reserved for VFP/Neon - // 320-8191 -- Reserved - // 8192-16383 -- Unspecified vendor co-processor register. -}; -// OpenRISC1000 register numbers -enum { - UNW_OR1K_R0 = 0, - UNW_OR1K_R1 = 1, - UNW_OR1K_R2 = 2, - UNW_OR1K_R3 = 3, - UNW_OR1K_R4 = 4, - UNW_OR1K_R5 = 5, - UNW_OR1K_R6 = 6, - UNW_OR1K_R7 = 7, - UNW_OR1K_R8 = 8, - UNW_OR1K_R9 = 9, - UNW_OR1K_R10 = 10, - UNW_OR1K_R11 = 11, - UNW_OR1K_R12 = 12, - UNW_OR1K_R13 = 13, - UNW_OR1K_R14 = 14, - UNW_OR1K_R15 = 15, - UNW_OR1K_R16 = 16, - UNW_OR1K_R17 = 17, - UNW_OR1K_R18 = 18, - UNW_OR1K_R19 = 19, - UNW_OR1K_R20 = 20, - UNW_OR1K_R21 = 21, - UNW_OR1K_R22 = 22, - UNW_OR1K_R23 = 23, - UNW_OR1K_R24 = 24, - UNW_OR1K_R25 = 25, - UNW_OR1K_R26 = 26, - UNW_OR1K_R27 = 27, - UNW_OR1K_R28 = 28, - UNW_OR1K_R29 = 29, - UNW_OR1K_R30 = 30, - UNW_OR1K_R31 = 31, -}; -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c deleted file mode 100644 index ecf59c03d0..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "stack_unwinder_libcorkscrew.h" -#include -#include -#include -#include -#include - -#include "string.h" - -typedef struct { - uintptr_t absolute_pc; - uintptr_t stack_top; - size_t stack_size; -} backtrace_frame_t; - -typedef struct { - uintptr_t relative_pc; - uintptr_t relative_symbol_addr; - char *map_name; - char *symbol_name; - char *demangled_name; -} backtrace_symbol_t; - -/* Extracted from Android's include/corkscrew/backtrace.h */ -typedef struct map_info_t map_info_t; - -struct bsg_unwind_config { - void *cork_unwind_backtrace_signal_arch; - void *cork_unwind_backtrace_thread; - void *cork_acquire_my_map_info_list; - void *cork_release_my_map_info_list; - void *cork_get_backtrace_symbols; - void *cork_free_backtrace_symbols; -}; - -static struct bsg_unwind_config *bsg_global_unwind_cfg; - -bool bsg_libcorkscrew_configured() { - return bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch != NULL && - bsg_global_unwind_cfg->cork_unwind_backtrace_thread != NULL && - bsg_global_unwind_cfg->cork_acquire_my_map_info_list != NULL && - bsg_global_unwind_cfg->cork_release_my_map_info_list != NULL && - bsg_global_unwind_cfg->cork_get_backtrace_symbols != NULL && - bsg_global_unwind_cfg->cork_free_backtrace_symbols != NULL; -} - -bool bsg_configure_libcorkscrew(void) { - bsg_global_unwind_cfg = calloc(1, sizeof(struct bsg_unwind_config)); - void *libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL); - if (libcorkscrew != NULL) { - bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch = - dlsym(libcorkscrew, "unwind_backtrace_signal_arch"); - bsg_global_unwind_cfg->cork_acquire_my_map_info_list = - dlsym(libcorkscrew, "acquire_my_map_info_list"); - bsg_global_unwind_cfg->cork_release_my_map_info_list = - dlsym(libcorkscrew, "release_my_map_info_list"); - bsg_global_unwind_cfg->cork_get_backtrace_symbols = - dlsym(libcorkscrew, "get_backtrace_symbols"); - bsg_global_unwind_cfg->cork_free_backtrace_symbols = - dlsym(libcorkscrew, "free_backtrace_symbols"); - bsg_global_unwind_cfg->cork_unwind_backtrace_thread = - dlsym(libcorkscrew, "unwind_backtrace_thread"); - } - - return bsg_libcorkscrew_configured(); -} - -ssize_t -bsg_unwind_stack_libcorkscrew(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - backtrace_frame_t frames[BUGSNAG_FRAMES_MAX]; - backtrace_symbol_t symbols[BUGSNAG_FRAMES_MAX]; - map_info_t *(*acquire_my_map_info_list)(void) = - bsg_global_unwind_cfg->cork_acquire_my_map_info_list; - ssize_t (*unwind_backtrace_signal_arch)( - siginfo_t *, void *, const map_info_t *, backtrace_frame_t *, size_t, - size_t) = bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch; - ssize_t (*unwind_backtrace_thread)(pid_t, backtrace_frame_t *, size_t, - size_t) = - bsg_global_unwind_cfg->cork_unwind_backtrace_thread; - void (*release_my_map_info_list)(map_info_t *) = - bsg_global_unwind_cfg->cork_release_my_map_info_list; - void (*get_backtrace_symbols)(const backtrace_frame_t *, size_t, - backtrace_symbol_t *) = - bsg_global_unwind_cfg->cork_get_backtrace_symbols; - void (*free_backtrace_symbols)(backtrace_symbol_t *, size_t) = - bsg_global_unwind_cfg->cork_free_backtrace_symbols; - - ssize_t size; - if (user_context != NULL) { - map_info_t *const info_list = acquire_my_map_info_list(); - size = unwind_backtrace_signal_arch(info, user_context, info_list, frames, - 0, (size_t)BUGSNAG_FRAMES_MAX); - release_my_map_info_list(info_list); - } else { - size = unwind_backtrace_thread(getpid(), frames, 0, - (size_t)BUGSNAG_FRAMES_MAX); - } - - get_backtrace_symbols(frames, (size_t)size, symbols); - int frame_count = 0; - for (int i = 0; i < size; i++) { - backtrace_frame_t backtrace_frame = frames[i]; - backtrace_symbol_t backtrace_symbol = symbols[i]; - - if ((void *)backtrace_frame.absolute_pc == NULL) { - continue; // nobody's home - } - if (frame_count > 0 && backtrace_frame.absolute_pc == - stacktrace[frame_count - 1].frame_address) { - continue; // already seen this - } - if (backtrace_symbol.symbol_name != NULL) { - bsg_strncpy(stacktrace[frame_count].method, backtrace_symbol.symbol_name, - sizeof(stacktrace[frame_count].method)); - } - - stacktrace[frame_count].frame_address = backtrace_frame.absolute_pc; - frame_count++; - } - free_backtrace_symbols(symbols, (size_t)size); - - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h deleted file mode 100644 index 8d8a8b7eea..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBCORKSCREW_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBCORKSCREW_H - -#include "../event.h" -#include - -bool bsg_configure_libcorkscrew(void); - -ssize_t -bsg_unwind_stack_libcorkscrew(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c deleted file mode 100644 index 4828469586..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "stack_unwinder_libunwind.h" -#include "build.h" -#include -#include -#include - -#if defined(__arm__) -#include -#endif - -typedef struct { - size_t frame_count; - uintptr_t frame_addresses[BUGSNAG_FRAMES_MAX]; -} bsg_libunwind_state; - -bsg_libunwind_state *bsg_global_libunwind_state; -bool bsg_libunwind_global_is32bit = false; - -bool bsg_configure_libunwind(bool is32bit) { - bsg_global_libunwind_state = calloc(1, sizeof(bsg_libunwind_state)); - bsg_libunwind_global_is32bit = is32bit; - return true; -} - -static _Unwind_Reason_Code -bsg_libunwind_callback(struct _Unwind_Context *context, void *arg) __asyncsafe { - bsg_libunwind_state *state = (bsg_libunwind_state *)arg; - - uintptr_t ip = _Unwind_GetIP(context); - - if (state->frame_count >= BUGSNAG_FRAMES_MAX) { - return _URC_END_OF_STACK; - } else if (state->frame_count > 0 && (void *)ip == NULL) { // nobody's home - return _URC_NO_REASON; - } - state->frame_addresses[state->frame_count] = ip; - state->frame_count++; - - return _URC_NO_REASON; -} - -#if defined(__arm__) -ssize_t bsg_unwind_stack_libunwind_arm32( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context) __asyncsafe { - unw_cursor_t cursor; - unw_context_t uc; - int index = 0; - - unw_getcontext(&uc); - unw_init_local(&cursor, &uc); - // Initialize cursor state with register data, if any - if (user_context != NULL) { - /** - * Set the registers and initial frame to the values from the signal - * handler user context. - * - * When a signal is raised on 32-bit ARM, the current context is the signal - * stack rather than the crash stack. To work around this, set the register - * state before unwinding the first frame (using the program counter as the - * first frame). Then the stack can be unwound normally. - */ - const ucontext_t *signal_ucontext = (const ucontext_t *)user_context; - const struct sigcontext *signal_mcontext = &(signal_ucontext->uc_mcontext); - unw_set_reg(&cursor, UNW_ARM_R0, signal_mcontext->arm_r0); - unw_set_reg(&cursor, UNW_ARM_R1, signal_mcontext->arm_r1); - unw_set_reg(&cursor, UNW_ARM_R2, signal_mcontext->arm_r2); - unw_set_reg(&cursor, UNW_ARM_R3, signal_mcontext->arm_r3); - unw_set_reg(&cursor, UNW_ARM_R4, signal_mcontext->arm_r4); - unw_set_reg(&cursor, UNW_ARM_R5, signal_mcontext->arm_r5); - unw_set_reg(&cursor, UNW_ARM_R6, signal_mcontext->arm_r6); - unw_set_reg(&cursor, UNW_ARM_R7, signal_mcontext->arm_r7); - unw_set_reg(&cursor, UNW_ARM_R8, signal_mcontext->arm_r8); - unw_set_reg(&cursor, UNW_ARM_R9, signal_mcontext->arm_r9); - unw_set_reg(&cursor, UNW_ARM_R10, signal_mcontext->arm_r10); - unw_set_reg(&cursor, UNW_ARM_R11, signal_mcontext->arm_fp); - unw_set_reg(&cursor, UNW_ARM_R12, signal_mcontext->arm_ip); - unw_set_reg(&cursor, UNW_ARM_R13, signal_mcontext->arm_sp); - unw_set_reg(&cursor, UNW_ARM_R14, signal_mcontext->arm_lr); - unw_set_reg(&cursor, UNW_ARM_R15, signal_mcontext->arm_pc); - unw_set_reg(&cursor, UNW_REG_IP, signal_mcontext->arm_pc); - unw_set_reg(&cursor, UNW_REG_SP, signal_mcontext->arm_sp); - // Manually insert first frame to avoid being skipped in step() - stacktrace[index++].frame_address = signal_mcontext->arm_pc; - } - - while (unw_step(&cursor) > 0 && index < BUGSNAG_FRAMES_MAX) { - unw_word_t ip = 0; - unw_get_reg(&cursor, UNW_REG_IP, &ip); - stacktrace[index++].frame_address = ip; - } - - return index; -} -#endif -ssize_t -bsg_unwind_stack_libunwind(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { -#if defined(__arm__) - if (bsg_libunwind_global_is32bit) { // avoid this code path if a 64-bit device - // is running 32-bit - return bsg_unwind_stack_libunwind_arm32(stacktrace, info, user_context); - } -#endif - if (bsg_global_libunwind_state == NULL) { - return 0; - } - bsg_global_libunwind_state->frame_count = 0; - // The return value of _Unwind_Backtrace sits on a throne of lies - _Unwind_Backtrace(bsg_libunwind_callback, bsg_global_libunwind_state); - for (int i = 0; i < bsg_global_libunwind_state->frame_count; ++i) { - stacktrace[i].frame_address = - bsg_global_libunwind_state->frame_addresses[i]; - } - return bsg_global_libunwind_state->frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h deleted file mode 100644 index 961d363671..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWIND_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWIND_H - -#include "../event.h" -#include - -bool bsg_configure_libunwind(bool is32bit); - -ssize_t -bsg_unwind_stack_libunwind(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); - -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c deleted file mode 100644 index 99c566719c..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "stack_unwinder_simple.h" -#include "string.h" -#include -#include - -ssize_t -bsg_unwind_stack_simple(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - if (user_context != NULL) { - // program counter / instruction pointer - uintptr_t ip = 0; - ucontext_t *ctx = (ucontext_t *)user_context; -#if defined(__i386__) - ip = (uintptr_t)ctx->uc_mcontext.gregs[REG_EIP]; -#elif defined(__x86_64__) - ip = (uintptr_t)ctx->uc_mcontext.gregs[REG_RIP]; -#elif defined(__arm__) - ip = (uintptr_t)ctx->uc_mcontext.arm_ip; -#elif defined(__aarch64__) - ip = (uintptr_t)ctx->uc_mcontext.regs[15]; -#else - ip = (uintptr_t)info->si_addr; -#endif - if (ip != 0) { - stacktrace[0].frame_address = ip; - return 1; - } - } - - return 0; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h deleted file mode 100644 index 528e786bf7..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_SIMPLE_H -#define BUGSNAG_UTILS_STACK_UNWINDER_SIMPLE_H - -#include "../event.h" -#include - -ssize_t -bsg_unwind_stack_simple(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); -#endif From 92a0b6e2fa13ff8155c8d2087c24714bdfdadccf Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 22 Feb 2022 08:37:53 +0000 Subject: [PATCH 04/20] build: update ndk revision Bumping is required to compile with c++17 language features Use latest LTS, per guidance for middleware vendors > NDK versions are largely compatible with each other, but occasionally there are changes that break compatibility. If you know that all of your users are using the same version of the NDK, it's best to use the same version that they do. Otherwise, use the newest version. https://developer.android.com/ndk/guides/middleware-vendors change requires update to root_detection to use 3-param open(2) [full ci] --- bugsnag-android-core/src/main/jni/root_detection.c | 2 +- buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag-android-core/src/main/jni/root_detection.c b/bugsnag-android-core/src/main/jni/root_detection.c index 5ba5b9360a..6152285f29 100644 --- a/bugsnag-android-core/src/main/jni/root_detection.c +++ b/bugsnag-android-core/src/main/jni/root_detection.c @@ -59,7 +59,7 @@ static inline bool is_path_writable(const char* path) { static inline bool can_create_file(const char* path) { unlink(path); - const int fd = open(path, O_CREAT); + const int fd = open(path, O_CREAT, O_RDONLY); if (fd < 0) { return false; } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt index 194ee5237a..5a5ac1c4a4 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt @@ -9,7 +9,7 @@ object Versions { // Note minSdkVersion must be >=21 for 64 bit architectures val minSdkVersion = 14 val compileSdkVersion = 31 - val ndk = "17.2.4988734" + val ndk = "23.1.7779620" val java = JavaVersion.VERSION_1_7 val kotlin = "1.4.32" From d8a2f30c0c5103709824dc9cc5f8c29b20073097 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Mon, 21 Feb 2022 18:21:40 +0000 Subject: [PATCH 05/20] build(ndk): define version scripts to avoid exporting unneeded symbols reduce the surface of API, which expanded quite a bit with updated unwindstack. https://developer.android.com/ndk/guides/middleware-vendors debug builds export additional symbols used in the tests This change requires removal of code which re-throws types defined in binaries other than libbugsnag-ndk, because if the type has been hidden, then the throw invocation will trigger an immediate termination (skipping the crash report), or if the type is custom (e.g. a subclass of std::runtime_error, etc), then no useful message was detected anyway, as the type information is likely not available. Useful reading on the topic: * https://gcc.gnu.org/wiki/Visibility (specifically "Problems with C++ exceptions") * https://developer.android.com/ndk/guides/common-problems#rttiexceptions_not_working_across_library_boundaries * https://developer.android.com/ndk/guides/cpp-support#ic * https://en.cppreference.com/w/cpp/language/definition (section on ODR) Technically this lib should not export std:: symbols but this at least preserves existing behavior until its re-evaluated. [full ci] --- .../src/main/CMakeLists.txt | 5 ++ .../main/exported_native_symbols-Debug.txt | 13 +++++ ...exported_native_symbols-RelWithDebInfo.txt | 25 ++++++++++ .../src/main/jni/handlers/cpp_handler.cpp | 47 ------------------- features/smoke_tests/unhandled.feature | 2 +- features/steps/android_steps.rb | 7 +++ 6 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt create mode 100644 bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt index 2eec067ed9..c3c7f84a84 100644 --- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt @@ -40,9 +40,14 @@ target_link_libraries( # Specifies the target library. # Links the log library to the target library. log) +# Avoid exporting symbols in release mode to keep internals private +# More symbols are exported in debug mode for the sake of unit testing +set(EXTRA_LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/exported_native_symbols-${CMAKE_BUILD_TYPE}.txt") + set_target_properties(bugsnag-ndk PROPERTIES COMPILE_OPTIONS -Werror -Wall -pedantic + LINK_FLAGS "${EXTRA_LINK_FLAGS}" CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES) diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt new file mode 100644 index 0000000000..90a7ad9ab0 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt @@ -0,0 +1,13 @@ +LIBBUGSNAG_NDK { +global: + bugsnag_*; + Java_*; + bsg_*; + json_*; + BSG_MIGRATOR_CURRENT_VERSION; + migrate_app_v2; + __gxx_*; + __cxa_*; +local: + *; +}; diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt new file mode 100644 index 0000000000..94204bd8fa --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt @@ -0,0 +1,25 @@ +LIBBUGSNAG_NDK { +global: + bugsnag_*; + __cxa_*; + __dynamic_cast; + __emutls_get_address; + __gxx_personality_v0; + Java_*; + extern "C++" { + "std::get_terminate()"; + "std::set_terminate(void (*)())"; + "std::set_unexpected(void (*)())"; + "std::get_new_handler()"; + "std::set_new_handler(void (*)())"; + "std::rethrow_exception(std::exception_ptr)"; + "std::__throw_bad_alloc()"; + "std::uncaught_exception()"; + "std::uncaught_exceptions()"; + "std::nothrow"; + "std::terminate()"; + }; + +local: + *; +}; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp index 268cdf0b88..61b1c80646 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp +++ b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp @@ -43,48 +43,6 @@ void bsg_handler_uninstall_cpp() { bsg_global_env = NULL; } -void bsg_write_current_exception_message(char *message, size_t length) { - try { - throw; - } catch (std::exception &exc) { - bsg_strncpy(message, (char *)exc.what(), length); - } catch (std::exception *exc) { - bsg_strncpy(message, (char *)exc->what(), length); - } catch (std::string obj) { - bsg_strncpy(message, (char *)obj.c_str(), length); - } catch (char *obj) { - snprintf(message, length, "%s", obj); - } catch (char obj) { - snprintf(message, length, "%c", obj); - } catch (short obj) { - snprintf(message, length, "%d", obj); - } catch (int obj) { - snprintf(message, length, "%d", obj); - } catch (long obj) { - snprintf(message, length, "%ld", obj); - } catch (long long obj) { - snprintf(message, length, "%lld", obj); - } catch (long double obj) { - snprintf(message, length, "%Lf", obj); - } catch (double obj) { - snprintf(message, length, "%f", obj); - } catch (float obj) { - snprintf(message, length, "%f", obj); - } catch (unsigned char obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned short obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned int obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned long obj) { - snprintf(message, length, "%lu", obj); - } catch (unsigned long long obj) { - snprintf(message, length, "%llu", obj); - } catch (...) { - // no way to describe what this is - } -} - void bsg_handle_cpp_terminate() { if (bsg_global_env == NULL || bsg_global_env->handling_crash) return; @@ -108,11 +66,6 @@ void bsg_handle_cpp_terminate() { (char *)tinfo->name(), sizeof(bsg_global_env->next_event.error.errorClass)); } - size_t message_length = sizeof(bsg_global_env->next_event.error.errorMessage); - char message[message_length]; - bsg_write_current_exception_message(message, message_length); - bsg_strncpy(bsg_global_env->next_event.error.errorMessage, (char *)message, - message_length); if (bsg_run_on_error()) { bsg_increment_unhandled_count(&bsg_global_env->next_event); diff --git a/features/smoke_tests/unhandled.feature b/features/smoke_tests/unhandled.feature index 4d06fdfbde..5a7bc701d6 100644 --- a/features/smoke_tests/unhandled.feature +++ b/features/smoke_tests/unhandled.feature @@ -190,7 +190,7 @@ Scenario: C++ exception thrown with overwritten config # Exception details And the error payload field "events" is an array with 1 elements - And the exception "message" equals "How about NO" + And the exception "errorClass" demangles to "std::runtime_error*" And the exception "type" equals "c" And the event "unhandled" is true And the event "severity" equals "error" diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index 95c0c87153..a76c97201a 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -98,6 +98,13 @@ Maze.check.include(possible_values.raw.flatten, value) end +Then("the exception {string} demangles to {string}") do |keypath, expected_value| + actual_value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.exceptions.0.#{keypath}") + demangled_value = `c++filt --types --no-strip-underscore '#{actual_value}'`.chomp + Maze.check.true(demangled_value == expected_value, + "expected '#{demangled_value}' to equal '#{expected_value}'") +end + # Checks whether the first significant frames match several given frames # # @param expected_values [Array] A table dictating the expected files and methods of the frames From d84ec76c15a8f7531c3ff5c400da758c1d831201 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Wed, 23 Feb 2022 10:48:43 +0000 Subject: [PATCH 06/20] test(ndk): assert stack contents in c++ exception scenario --- .../cxx-scenarios-bugsnag/CMakeLists.txt | 3 +- .../main/cpp/CXXExceptionSmokeScenario.cpp | 29 +++++++++++++++++++ .../src/main/cpp/cxx-scenarios-bugsnag.cpp | 10 ------- features/smoke_tests/unhandled.feature | 13 ++++----- features/steps/android_steps.rb | 3 +- 5 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt index 8389ceb556..577809f3fd 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required(VERSION 3.4.1) add_library(cxx-scenarios-bugsnag SHARED - src/main/cpp/cxx-scenarios-bugsnag.cpp) + src/main/cpp/cxx-scenarios-bugsnag.cpp + src/main/cpp/CXXExceptionSmokeScenario.cpp) add_library(lib_bugsnag SHARED IMPORTED) set(BUGSNAG_LIB_DIR diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp new file mode 100644 index 0000000000..977ccc90d7 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp @@ -0,0 +1,29 @@ +#include +#include + +namespace magicstacks { + +class FatalProblem : std::runtime_error { + using std::runtime_error::runtime_error; + + virtual ~FatalProblem() {} +}; + +void __attribute__((optnone)) top() { + throw new FatalProblem("well there it is!"); +} + +void __attribute__((optnone)) middle() { top(); } + +void __attribute__((optnone)) start() { middle(); } +} // namespace magicstacks + +extern "C" { +JNIEXPORT int JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash( + JNIEnv *env, jobject instance) { + magicstacks::start(); + + return 90; +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp index 6c1ad586a4..72fc6b6581 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp @@ -153,16 +153,6 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionOnErrorFalseScenario_c return 22; } -JNIEXPORT int JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash( - JNIEnv *env, - jobject instance) { - int x = 61; - printf("This one here: %ld\n", (long) f_trigger_an_exception(x > 0)); - printf("This one here: %ld\n", (long) f_throw_an_object(x > 0, x)); - return 55; -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXStartScenario_activate( JNIEnv *env, diff --git a/features/smoke_tests/unhandled.feature b/features/smoke_tests/unhandled.feature index 5a7bc701d6..7d19e61a23 100644 --- a/features/smoke_tests/unhandled.feature +++ b/features/smoke_tests/unhandled.feature @@ -190,7 +190,7 @@ Scenario: C++ exception thrown with overwritten config # Exception details And the error payload field "events" is an array with 1 elements - And the exception "errorClass" demangles to "std::runtime_error*" + And the exception "errorClass" demangles to "magicstacks::FatalProblem*" And the exception "type" equals "c" And the event "unhandled" is true And the event "severity" equals "error" @@ -202,12 +202,11 @@ Scenario: C++ exception thrown with overwritten config # Stacktrace validation And the error payload field "events.0.exceptions.0.stacktrace" is a non-empty array And the event stacktrace identifies the program counter - And the event "exceptions.0.stacktrace.0.method" is not null - And the event "exceptions.0.stacktrace.0.file" is not null - And the error payload field "events.0.exceptions.0.stacktrace.0.frameAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.symbolAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.loadAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.lineNumber" is greater than 0 + And the first significant stack frame methods and files should match: + | magicstacks::top() | | libcxx-scenarios-bugsnag.so | + | magicstacks::middle() | | libcxx-scenarios-bugsnag.so | + | magicstacks::start() | | libcxx-scenarios-bugsnag.so | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash | | libcxx-scenarios-bugsnag.so | # App data And the event binary arch field is valid diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index a76c97201a..238afd31c2 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -117,7 +117,8 @@ method = frame["method"] if method == "_#{frame["method"]}" insignificant = method.start_with?("bsg_") || method.start_with?("std::") || - method.start_with?("__cxx") || + method.start_with?("__cx") || + method.start_with?("0x") || frame["file"].start_with?("/system/") || frame["file"].end_with?("libbugsnag-ndk.so") { :index => index, :method => method, :file => frame["file"] } unless insignificant From 013f555f81582c1deeb07f81c5742aa3c90cbd69 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 1 Mar 2022 15:24:24 +0000 Subject: [PATCH 07/20] test(ndk): add expectations unwinder symbol info --- .../com/bugsnag/android/ndk/UnwindTest.kt | 165 +++++++++++++++--- .../src/test/cpp/UnwindTest.cpp | 90 ++++++++-- 2 files changed, 216 insertions(+), 39 deletions(-) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt index 655cbc7e0d..5d89d0df4d 100644 --- a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt @@ -1,45 +1,160 @@ package com.bugsnag.android.ndk +import android.os.Build import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Test +import kotlin.math.abs class UnwindTest { @Test fun testUnwindForNotify() { - val frames = unwindForNotify() - // the top of the stack includes the unwinder itself, etc, so first - // scan for the expected contents, which must be a sequence - var offset = frames.indexOf("unwind_func_four") - if (offset == -1) { - fail("did not find initial stack frame in list: $frames") - } - assertEquals("unwind_func_four", frames[offset]) - assertEquals("unwind_func_three", frames[offset + 1]) - assertEquals("unwind_func_two", frames[offset + 2]) - assertEquals("unwind_func_one", frames[offset + 3]) - assertEquals("Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify", frames[offset + 4]) + assertFramesMatchExpected( + unwindForNotify(), + "Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify", + ) } @Test fun testUnwindForCrash() { - val frames = unwindForCrash() + assertFramesMatchExpected( + unwindForCrash(), + "Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash", + ) + } + + // find a frame matching a method, returning the index, or fail the test + fun findIndexByMethod(items: List>, method: String): Int { + val index = items.indices.find { items[it]["method"] == method } + if (index == null) { + fail("did not find method '$method' in list: $items") + } + return index!! + } + + // verify that a stack trace contains the entries in EXPECTED_FUNCTIONS and + // is followed by the expected JNI function + fun assertFramesMatchExpected(frames: List>, jniFunction: String) { // the top of the stack includes the unwinder itself, etc, so first // scan for the expected contents, which must be a sequence - var offset = frames.indexOf("unwind_func_four") - if (offset == -1) { - fail("did not find initial stack frame in list: $frames") + val offset = findIndexByMethod(frames, EXPECTED_FUNCTIONS[0]) + + // check if frames have enough remaining room for expected contents + assertTrue( + "missing expected stack frames in $frames", + frames.size - offset > EXPECTED_FUNCTIONS.size + 1 + ) + + EXPECTED_FUNCTIONS.forEachIndexed { index, name -> + assertFrameMatches(index, name, frames[offset + index]) } - assertEquals("unwind_func_four", frames[offset]) - assertEquals("unwind_func_three", frames[offset + 1]) - assertEquals("unwind_func_two", frames[offset + 2]) - assertEquals("unwind_func_one", frames[offset + 3]) - assertEquals("Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash", frames[offset + 4]) + + assertFrameMatches( // JNI function must immediately follow + EXPECTED_FUNCTIONS.size, + jniFunction, + frames[offset + EXPECTED_FUNCTIONS.size] + ) } - // unwind a known set of functions and validate the method names in the - // stack - external fun unwindForNotify(): List - external fun unwindForCrash(): List + fun assertFrameMatches(frameIndex: Int, expectedFunctionName: String, frame: Map) { + val method = frame["method"] as String + val file = frame["file"] as String + val symbolAddress = frame["symbolAddress"] as Long + val lineNumber = frame["lineNumber"] as Long + val loadAddress = frame["loadAddress"] as Long + + assertEquals( + """ + expected function name '$expectedFunctionName' at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + expectedFunctionName, + method + ) + + assertTrue( + """ + expected file suffix '$EXPECTED_NATIVE_LIB_NAME' at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + file.endsWith(EXPECTED_NATIVE_LIB_NAME) + ) + + // these are smol functions. the symbol and return addresses should be + // close to the local representation, with there being a bit of a larger + // gap on x86 than the other three ABIs, which are near exact. + val addressDelta = wordsize * 4 + + assertAlmostEquals( + """ + expected function address to nearly equal ${nativeInfo[expectedFunctionName]} at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + nativeInfo[expectedFunctionName]!!, + symbolAddress, + addressDelta + ) + + // sanity check that the line number doesn't include load addr or + // vice versa + assertTrue( + """ + expected line number and load address to be > 0 at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + loadAddress > 0 && lineNumber > 0 + ) + + assertAlmostEquals( + """ + expected line number + load address to nearly equal ${nativeInfo[expectedFunctionName]} at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + nativeInfo[expectedFunctionName]!!, + lineNumber + loadAddress, + addressDelta + ) + } + + // assert two values are equal within an acceptable delta + fun assertAlmostEquals(message: String, expectedValue: Long, actualValue: Long, delta: Long) { + val diff = abs(expectedValue - actualValue) + assertTrue(message + " \ndiff: $diff delta: $delta", diff <= delta) + } + + // unwind a known set of functions and return the stack frame contents + external fun unwindForNotify(): List> + external fun unwindForCrash(): List> + + companion object BuildInfo { + // these are the names and ordering of the native functions expected to + // be in the detected stack traces (see UnwindTest.cpp) + val EXPECTED_FUNCTIONS = listOf( + "unwind_func_four", + "unwind_func_three", + "unwind_func_two", + "unwind_func_one" + ) + + // name of the library containing the EXPECTED_FUNCTIONS + const val EXPECTED_NATIVE_LIB_NAME = "libbugsnag-ndk-test.so" + + // get expected frame info. returns a map of frame names to function addresses + external fun getNativeFunctionInfo(): Map + + val nativeInfo = getNativeFunctionInfo() + + val is64bit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + Build.SUPPORTED_64_BIT_ABIS.size > 0 + else + false + val wordsize: Long = if (is64bit) 64 else 32 + } } diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp index ecfcfcce36..f07d11bae9 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp +++ b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp @@ -1,17 +1,15 @@ +#include #include #include #include -#ifdef __cplusplus extern "C" { -#endif typedef ssize_t (*unwinder)(bugsnag_stackframe *, siginfo *, void *); -ssize_t -bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); ssize_t bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], @@ -27,18 +25,52 @@ jobject __attribute__((optnone)) unwind_func_four(JNIEnv *env, unwinder func) { // find JNI references auto ArrayList = (*env).FindClass("java/util/ArrayList"); - auto init = (*env).GetMethodID(ArrayList, "", "()V"); - auto add = (*env).GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z"); + auto HashMap = (*env).FindClass("java/util/HashMap"); + auto Long = (*env).FindClass("java/lang/Long"); + auto listInit = (*env).GetMethodID(ArrayList, "", "()V"); + auto listAdd = (*env).GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z"); + auto longInit = (*env).GetMethodID(Long, "", "(J)V"); + auto mapInit = (*env).GetMethodID(HashMap, "", "()V"); + auto mapPut = (*env).GetMethodID( + HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); // make list instance - auto items = (*env).NewObject(ArrayList, init); + auto frames = (*env).NewObject(ArrayList, listInit); for (int index = 0; index < count; index++) { - // using empty string as a sentinel if method is null - auto method = (*env).NewStringUTF(stack[index].method ?: ""); - (*env).CallBooleanMethod(items, add, method); + auto frame = (*env).NewObject(HashMap, mapInit); + + // using empty string as a sentinel if method or file is null + auto file = stack[index].filename[0] ? stack[index].filename : ""; + auto method = stack[index].method[0] ? stack[index].method : ""; + jlong lineNumber = stack[index].line_number; + jlong symbolAddress = stack[index].symbol_address; + jlong frameAddress = stack[index].frame_address; + jlong loadAddress = stack[index].load_address; + + (*env).CallObjectMethod(frame, mapPut, (*env).NewStringUTF("file"), + (*env).NewStringUTF(file)); + (*env).CallObjectMethod(frame, mapPut, (*env).NewStringUTF("method"), + (*env).NewStringUTF(method)); + + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("lineNumber"), + (*env).NewObject(Long, longInit, lineNumber)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("symbolAddress"), + (*env).NewObject(Long, longInit, symbolAddress)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("frameAddress"), + (*env).NewObject(Long, longInit, frameAddress)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("loadAddress"), + (*env).NewObject(Long, longInit, loadAddress)); + + (*env).CallBooleanMethod(frames, listAdd, frame); } - return items; + + return frames; } jobject __attribute__((optnone)) unwind_func_three(JNIEnv *env, unwinder func) { @@ -67,6 +99,36 @@ Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash(JNIEnv *env, return unwind_func_one(env, bsg_unwind_crash_stack); } -#ifdef __cplusplus +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_00024BuildInfo_getNativeFunctionInfo( + JNIEnv *env, jobject _this) { + // find JNI references + auto HashMap = (*env).FindClass("java/util/HashMap"); + auto Long = (*env).FindClass("java/lang/Long"); + auto longInit = (*env).GetMethodID(Long, "", "(J)V"); + auto mapInit = (*env).GetMethodID(HashMap, "", "()V"); + auto put = (*env).GetMethodID( + HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + // insert each function as a mapping between function name and address +#define add_function_info(map, func) \ + (*env).CallObjectMethod(map, put, (*env).NewStringUTF(#func), \ + (*env).NewObject(Long, longInit, (jlong)&func)) + + // make list of addresses + auto items = (*env).NewObject(HashMap, mapInit); + add_function_info(items, unwind_func_four); + add_function_info(items, unwind_func_three); + add_function_info(items, unwind_func_two); + add_function_info(items, unwind_func_one); + add_function_info(items, + Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash); + add_function_info(items, + Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify); + +#undef add_function_info + + return items; +} } -#endif From 0272e230ecb173088c1a244f809408d0bc478408 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Tue, 1 Mar 2022 15:37:44 +0000 Subject: [PATCH 08/20] fix(ndk): prevent simultaneous unwind at crash time --- .../src/main/jni/utils/stack_unwinder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp index 715cad8a72..1a488c2182 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp @@ -11,6 +11,10 @@ // unwinder intended for a potentially terminating context static unwindstack::Unwinder *crash_time_unwinder; +// soft lock for using the crash time unwinder - if active, return without +// attempting to unwind. This isn't a "real" lock to avoid deadlocking in the +// event of a crash while handling an ANR or the reverse. +static bool unwinding_crash_stack; // Thread-safe, reusable unwinder - uses thread-specific memory caches static unwindstack::LocalUnwinder *current_time_unwinder; @@ -45,9 +49,10 @@ void bsg_unwinder_init() { ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], siginfo_t *info, void *user_context) { - if (crash_time_unwinder == nullptr) { + if (crash_time_unwinder == nullptr || unwinding_crash_stack) { return 0; } + unwinding_crash_stack = true; if (user_context) { crash_time_unwinder->SetRegs(unwindstack::Regs::CreateFromUcontext( unwindstack::Regs::CurrentArch(), user_context)); @@ -70,6 +75,7 @@ ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], sizeof(stack[frame_count].method)); frame_count++; } + unwinding_crash_stack = false; return frame_count; } From eb5040923e2a4ccf487439fad3694851107cafca Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Wed, 2 Mar 2022 14:12:32 +0000 Subject: [PATCH 09/20] test(ndk): assert addresses resolve to correct locations Removes the double free scenario, as the exact crashing condition is dependent on undefined behavior, and thus can't assert an exact location. Refactors several existing tests to have their own files for their native components with the intent to: 1. Make the native functions easier to find - one big file is getting unwieldy 2. Make line number assertions stable, avoiding changes if unrelated tests change Avoids asserting on inconsistent behavior - for example CXXStackoverflowScenario always crashes in the same function but line info may not be available on arm32. --- Makefile | 12 + .../mazerunner/cxx-scenarios/CMakeLists.txt | 10 + .../src/main/cpp/CXXAbortScenario.cpp | 15 ++ .../CXXCallNullFunctionPointerScenario.cpp | 20 ++ .../main/cpp/CXXDereferenceNullScenario.cpp | 16 ++ .../cpp/CXXExternalStackElementScenario.cpp | 19 ++ .../main/cpp/CXXImproperTypecastScenario.cpp | 22 ++ .../src/main/cpp/CXXInvalidRethrow.cpp | 20 ++ .../src/main/cpp/CXXStackoverflowScenario.cpp | 22 ++ .../src/main/cpp/CXXThrowFromNoexcept.cpp | 23 ++ .../src/main/cpp/CXXTrapScenario.cpp | 22 ++ .../cpp/CXXWriteReadOnlyMemoryScenario.cpp | 25 ++ .../src/main/cpp/cxx-scenarios.cpp | 102 ------- .../CXXCallNullFunctionPointerScenario.kt | 24 ++ ...o.java => CXXDereferenceNullScenario.java} | 10 +- .../mazerunner/scenarios/CXXInvalidRethrow.kt | 24 ++ .../scenarios/CXXNullPointerScenario.java | 29 -- .../scenarios/CXXThrowFromNoexcept.kt | 24 ++ .../full_tests/native_crash_handling.feature | 250 ++++++++++-------- .../full_tests/native_throw_crash.feature | 62 +++-- features/smoke_tests/unhandled.feature | 10 +- features/steps/android_steps.rb | 41 --- features/steps/symbol_steps.rb | 114 ++++++++ 23 files changed, 602 insertions(+), 314 deletions(-) create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt rename features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/{CXXDoubleFreeScenario.java => CXXDereferenceNullScenario.java} (59%) create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt delete mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java create mode 100644 features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt create mode 100644 features/steps/symbol_steps.rb diff --git a/Makefile b/Makefile index a5c64e2a0e..bb4e053377 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,18 @@ test-fixtures: # Build the full test fixture @./gradlew -PTEST_FIXTURE_NDK_VERSION=$(TEST_FIXTURE_NDK_VERSION) -p=features/fixtures/mazerunner/ assembleRelease -x check @cp features/fixtures/mazerunner/app/build/outputs/apk/release/fixture.apk build/fixture.apk + # copy the unstripped scenarios libs (for stack unwinding validation) + # grabs the newest files as the likely just built ones + LIB_CXX_BUGSNAG=$$(ls -dtr features/fixtures/mazerunner/cxx-scenarios-bugsnag/build/intermediates/cxx/RelWithDebInfo/* | head -n 1) && \ + LIB_CXX=$$(ls -dtr features/fixtures/mazerunner/cxx-scenarios/build/intermediates/cxx/RelWithDebInfo/* | head -n 1) && \ + cp $$LIB_CXX/obj/x86_64/libcxx-scenarios.so build/libcxx-scenarios-x86_64.so && \ + cp $$LIB_CXX/obj/x86/libcxx-scenarios.so build/libcxx-scenarios-x86.so && \ + cp $$LIB_CXX/obj/arm64-v8a/libcxx-scenarios.so build/libcxx-scenarios-arm64.so && \ + cp $$LIB_CXX/obj/armeabi-v7a/libcxx-scenarios.so build/libcxx-scenarios-arm32.so && \ + cp $$LIB_CXX_BUGSNAG/obj/x86_64/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-x86_64.so && \ + cp $$LIB_CXX_BUGSNAG/obj/x86/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-x86.so && \ + cp $$LIB_CXX_BUGSNAG/obj/arm64-v8a/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-arm64.so && \ + cp $$LIB_CXX_BUGSNAG/obj/armeabi-v7a/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-arm32.so # And the minimal (no NDK or ANR plugin) test fixture @./gradlew -PMINIMAL_FIXTURE=true -PTEST_FIXTURE_NAME=fixture-minimal.apk -p=features/fixtures/mazerunner/ assembleRelease -x check diff --git a/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt b/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt index 295556880b..38eb9315fa 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt +++ b/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt @@ -2,6 +2,16 @@ cmake_minimum_required(VERSION 3.4.1) add_library(cxx-scenarios SHARED src/main/cpp/cxx-scenarios.cpp + src/main/cpp/CXXAbortScenario.cpp + src/main/cpp/CXXCallNullFunctionPointerScenario.cpp + src/main/cpp/CXXDereferenceNullScenario.cpp + src/main/cpp/CXXExternalStackElementScenario.cpp + src/main/cpp/CXXImproperTypecastScenario.cpp + src/main/cpp/CXXInvalidRethrow.cpp + src/main/cpp/CXXStackoverflowScenario.cpp + src/main/cpp/CXXThrowFromNoexcept.cpp + src/main/cpp/CXXTrapScenario.cpp + src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp src/main/cpp/bugsnag-java-scenarios.cpp) add_library(lib_monochrome SHARED IMPORTED) diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp new file mode 100644 index 0000000000..68313e00e7 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace evictor { +void __attribute__((optnone)) exit_with_style() { abort(); } +} // namespace evictor + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash( + JNIEnv *env, jobject instance) { + // added namespaces for variety between tests + evictor::exit_with_style(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp new file mode 100644 index 0000000000..848916240a --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp @@ -0,0 +1,20 @@ +#include + +void (*definitely_valid_func)(jobject) = 0; + +namespace dispatch { +class Handler { +public: + static void __attribute__((optnone)) handle(jobject obj) { + definitely_valid_func(obj); + } +}; +} // namespace dispatch + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXCallNullFunctionPointerScenario_crash( + JNIEnv *env, jobject instance) { + dispatch::Handler::handle(instance); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp new file mode 100644 index 0000000000..637fabfd45 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp @@ -0,0 +1,16 @@ +#include + +static volatile int *the_value; + +int __attribute__((optnone)) get_the_null_value() { + // assume this function is very interesting + return *the_value; +} + +extern "C" { +JNIEXPORT jint JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXDereferenceNullScenario_crash( + JNIEnv *env, jobject instance) { + return get_the_null_value(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp new file mode 100644 index 0000000000..d1d18ed26e --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp @@ -0,0 +1,19 @@ +#include +#include + +extern "C" { +// defined in libs/[ABI]/libmonochrome.so +int something_innocuous(int input); + +JNIEXPORT int JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash( + JNIEnv *env, jobject instance, jint counter) { + printf("Captain, why are we out here chasing comets?\n%d\n", counter); + int value = counter * 4; + if (counter > 0) { + value = something_innocuous(counter); + } + printf("Something innocuous this way comes: %d\n", value); + return value; +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp new file mode 100644 index 0000000000..b7c06f4d70 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +typedef struct { + char *text; +} weird_obj_t; + +char *__attribute__((optnone)) crash_improper_cast(void *counter) { + weird_obj_t *obj = (weird_obj_t *)counter; + + return obj->text; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash( + JNIEnv *env, jobject instance) { + printf("This one here: %s\n", crash_improper_cast((void *)39)); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp new file mode 100644 index 0000000000..d35244f242 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +void __attribute__((optnone)) print_last_exception() { + try { + throw; + } catch (std::exception *ex) { + // should never get here, since there is no exception. + printf("ex: %p", ex); + } +} + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXInvalidRethrow_crash( + JNIEnv *env, jobject instance) { + print_last_exception(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp new file mode 100644 index 0000000000..fbe5fce359 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +extern "C" { +int __attribute__((optnone)) __attribute__((noinline)) crash_stack_overflow(int counter, char *input) { + char stack[7]; + + strcpy(stack, input); + + return 4 / counter; +} + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXStackoverflowScenario_crash( + JNIEnv *env, jobject instance, jint counter, jstring text_) { + char *text = (char *)(*env).GetStringUTFChars(text_, 0); + printf("This one here: %ld\n", + (long)crash_stack_overflow((int)counter, text)); + (*env).ReleaseStringUTFChars(text_, text); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp new file mode 100644 index 0000000000..f0dfa28a46 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +class FunkError : public std::runtime_error { + using std::runtime_error::runtime_error; + + const char *toss_an_exception() const { + throw new FunkError("you done it now!"); + } + +public: + virtual const char *what() const noexcept { return toss_an_exception(); } +}; + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXThrowFromNoexcept_crash( + JNIEnv *env, jobject instance) { + auto err = new FunkError("mistakes were made"); + printf("what? %s", err->what()); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp new file mode 100644 index 0000000000..439b3e9466 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int trap_it() { + time_t now; + now = time(&now); + if (now > 0) + __builtin_trap(); + + return 0; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapScenario_crash( + JNIEnv *env, jobject instance) { + printf("This one here: %ld\n", (long)trap_it()); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp new file mode 100644 index 0000000000..618ec74761 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +static char * __attribute__((used)) somefakefunc(void) { + return NULL; +}; + +int crash_write_read_only_mem(int counter) { + if (counter > 2) { + int *pointer = (int *)&somefakefunc; + *pointer = counter; + } + return counter / 14; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash(JNIEnv *env, + jobject instance) { + printf("This one here: %d\n", crash_write_read_only_mem(42)); +} + +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp index 020393b2ea..ec1bafa36e 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp @@ -23,15 +23,6 @@ bool __attribute__((noinline)) run_back(int value, int boundary) { extern "C" { -static char * __attribute__((used)) somefakefunc(void) { - return NULL; -}; - -typedef struct { - int field1; - char *field2; -} reporter_t; - int crash_abort(bool route) { if (route) abort(); @@ -73,14 +64,6 @@ int crash_anr(bool route) { return 7; } -int crash_write_read_only_mem(int counter) { - if (counter > 2) { - int *pointer = (int *)&somefakefunc; - *pointer = counter; - } - return counter / 14; -} - int __attribute__((noinline)) throw_an_object(bool value, int boundary) { if (value) { printf("Now we know what they mean by 'advanced' tactical training: %d", boundary); @@ -97,27 +80,6 @@ int __attribute__((noinline)) trigger_an_exception(bool value) { return 405; } -char *crash_improper_cast(intptr_t counter) { - reporter_t *report = (reporter_t *)counter; - - return report->field2; -} - -int crash_double_free(int counter) { - for (int i = 0; i < 30; i++) { - reporter_t *reporter = (reporter_t *)malloc(sizeof(reporter_t)); - reporter->field1 = 22 + counter; - char *field2 = reporter->field2; - strcpy(field2, "Indeed"); - printf("%d field1 is: %d", i, reporter->field1); - printf("%d field2 is: %s", i, field2); - free(field2); - free(reporter); - } - - return counter / -8; -} - int crash_trap() { time_t now; now = time(&now); @@ -127,14 +89,6 @@ int crash_trap() { return 0; } -int crash_stack_overflow(int counter, char *input) { - char stack[7]; - - strcpy(stack, input); - - return 4 / counter; -} - JNIEXPORT int JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXSigtrapScenario_crash(JNIEnv *env, jobject instance, @@ -275,68 +229,12 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapOutsideReleaseStagesScenari printf("This one here: %ld\n", (long) crash_trap()); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXStackoverflowScenario_crash(JNIEnv *env, - jobject instance, - jint counter, - jstring text_) { - char *text = (char *)(*env).GetStringUTFChars(text_, 0); - printf("This one here: %ld\n", (long) crash_stack_overflow((int)counter, text)); - (*env).ReleaseStringUTFChars(text_, text); -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapLaterDisabledScenario_crash(JNIEnv *env, jobject instance) { printf("This one here: %ld\n", (long) crash_trap()); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapScenario_crash(JNIEnv *env, jobject instance) { - printf("This one here: %ld\n", (long) crash_trap()); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXDoubleFreeScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %d\n", crash_double_free(42)); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %d\n", crash_write_read_only_mem(42)); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %s\n", crash_improper_cast(39)); -} - -// defined in libs/[ABI]/libmonochrome.so -int something_innocuous(int input); - -JNIEXPORT int JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash(JNIEnv *env, - jobject instance, - jint counter) { - printf("Captain, why are we out here chasing comets?\n%d\n", counter); - int value = counter * 4; - if (counter > 0) { - value = something_innocuous(counter); - } - printf("Something innocuous this way comes: %d\n", value); - return value; -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash(JNIEnv *env, - jobject instance) { - int x = 47; - printf("This one here: %ld\n", (long) crash_abort(x > 0)); -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXNaughtyStringsScenario_crash(JNIEnv *env, jobject instance) { abort(); diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt new file mode 100644 index 0000000000..8e778bce64 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXCallNullFunctionPointerScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java similarity index 59% rename from features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java rename to features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java index b6c4cb003f..21f22838b2 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java @@ -7,17 +7,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -public class CXXDoubleFreeScenario extends Scenario { +public class CXXDereferenceNullScenario extends Scenario { static { System.loadLibrary("cxx-scenarios"); } - public native void crash(); + public native int crash(); - public CXXDoubleFreeScenario(@NonNull Configuration config, - @NonNull Context context, - @Nullable String eventMetadata) { + public CXXDereferenceNullScenario(@NonNull Configuration config, + @NonNull Context context, + @Nullable String eventMetadata) { super(config, context, eventMetadata); } diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt new file mode 100644 index 0000000000..329325f555 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXInvalidRethrow( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java deleted file mode 100644 index 1208fe46c5..0000000000 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios; - -import com.bugsnag.android.Configuration; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class CXXNullPointerScenario extends Scenario { - - static { - System.loadLibrary("cxx-scenarios"); - } - - public native void crash(); - - public CXXNullPointerScenario(@NonNull Configuration config, - @NonNull Context context, - @Nullable String eventMetadata) { - super(config, context, eventMetadata); - } - - @Override - public void startScenario() { - super.startScenario(); - crash(); - } -} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt new file mode 100644 index 0000000000..7c6001a12d --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXThrowFromNoexcept( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/full_tests/native_crash_handling.feature b/features/full_tests/native_crash_handling.feature index 2cb254d8da..a591be2f3c 100644 --- a/features/full_tests/native_crash_handling.feature +++ b/features/full_tests/native_crash_handling.feature @@ -1,124 +1,146 @@ Feature: Native crash reporting -Scenario: Dereference a null pointer - When I run "CXXNullPointerScenario" and relaunch the app - And I configure Bugsnag for "CXXNullPointerScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true - And the event "app.binaryArch" is not null - And the event "device.totalMemory" is not null - And the error payload field "events.0.device.cpuAbi" is a non-empty array - And the error payload field "events.0.metaData.app.memoryLimit" is greater than 0 + Scenario: Dereference a null pointer + When I run "CXXDereferenceNullScenario" and relaunch the app + And I configure Bugsnag for "CXXDereferenceNullScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "app.binaryArch" is not null + And the event "device.totalMemory" is not null + And the error payload field "events.0.device.cpuAbi" is a non-empty array + And the error payload field "events.0.metaData.app.memoryLimit" is greater than 0 + And the first significant stack frames match: + | get_the_null_value() | CXXDereferenceNullScenario.cpp | 7 | - # This scenario will not pass on API levels < 18, as stack corruption - # is handled without calling atexit handlers, etc. - # In the device logs you will see: - # system/bin/app_process([proc id]): stack corruption detected: aborted - # Original code here: - # https://android.googlesource.com/platform/bionic/+/d0f2b7e7e65f19f978c59abcbb522c08e76b1508/libc/bionic/ssp.c - # Refactored here to use abort() on newer versions: - # https://android.googlesource.com/platform/bionic/+/fb7eb5e07f43587c2bedf2aaa53b21fa002417bb - @skip_below_api18 - Scenario: Stack buffer overflow - When I run "CXXStackoverflowScenario" and relaunch the app - And I configure Bugsnag for "CXXStackoverflowScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception reflects a signal was raised - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + # This scenario will not pass on API levels < 18, as stack corruption + # is handled without calling atexit handlers, etc. + # In the device logs you will see: + # system/bin/app_process([proc id]): stack corruption detected: aborted + # Original code here: + # https://android.googlesource.com/platform/bionic/+/d0f2b7e7e65f19f978c59abcbb522c08e76b1508/libc/bionic/ssp.c + # Refactored here to use abort() on newer versions: + # https://android.googlesource.com/platform/bionic/+/fb7eb5e07f43587c2bedf2aaa53b21fa002417bb + @skip_below_api18 + Scenario: Stack buffer overflow + When I run "CXXStackoverflowScenario" and relaunch the app + And I configure Bugsnag for "CXXStackoverflowScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception reflects a signal was raised + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_stack_overflow | CXXStackoverflowScenario.cpp | - Scenario: Program trap() - When I run "CXXTrapScenario" and relaunch the app - And I configure Bugsnag for "CXXTrapScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals one of: - | SIGILL | - | SIGTRAP | - And the exception "message" equals one of: - | Illegal instruction | - | Trace/breakpoint trap | - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Program trap() + When I run "CXXTrapScenario" and relaunch the app + And I configure Bugsnag for "CXXTrapScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals one of: + | SIGILL | + | SIGTRAP | + And the exception "message" equals one of: + | Illegal instruction | + | Trace/breakpoint trap | + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | trap_it() | CXXTrapScenario.cpp | 10 | - Scenario: Write to read-only memory - When I run "CXXWriteReadOnlyMemoryScenario" and relaunch the app - And I configure Bugsnag for "CXXWriteReadOnlyMemoryScenario" - And I wait to receive an error - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Write to read-only memory + When I run "CXXWriteReadOnlyMemoryScenario" and relaunch the app + And I configure Bugsnag for "CXXWriteReadOnlyMemoryScenario" + And I wait to receive an error + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_write_read_only_mem(int) | CXXWriteReadOnlyMemoryScenario.cpp | 12 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash | CXXWriteReadOnlyMemoryScenario.cpp | 22 | - Scenario: Double free() allocated memory - When I run "CXXDoubleFreeScenario" and relaunch the app - And I configure Bugsnag for "CXXDoubleFreeScenario" - And I wait to receive an error - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true - # Fix as part of PLAT-5643 - # And the exception "errorClass" equals "SIGSEGV" - # And the exception "message" equals "Segmentation violation (invalid memory reference)" + Scenario: Improper object type cast + When I run "CXXImproperTypecastScenario" and relaunch the app + And I configure Bugsnag for "CXXImproperTypecastScenario" + And I wait to receive an error + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_improper_cast(void*) | CXXImproperTypecastScenario.cpp | 12 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash | CXXImproperTypecastScenario.cpp | 20 | - Scenario: Improper object type cast - When I run "CXXImproperTypecastScenario" and relaunch the app - And I configure Bugsnag for "CXXImproperTypecastScenario" - And I wait to receive an error - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Program abort() + When I run "CXXAbortScenario" and relaunch the app + And I configure Bugsnag for "CXXAbortScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals one of: + | SIGABRT | + | SIGSEGV | + And the exception "message" equals one of: + | Abort program | + | Segmentation violation (invalid memory reference) | + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | evictor::exit_with_style() | CXXAbortScenario.cpp | 5 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash | CXXAbortScenario.cpp | 13 | - Scenario: Program abort() - When I run "CXXAbortScenario" and relaunch the app - And I configure Bugsnag for "CXXAbortScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals one of: - | SIGABRT | - | SIGSEGV | - And the exception "message" equals one of: - | Abort program | - | Segmentation violation (invalid memory reference) | - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Undefined JNI method + When I run "UnsatisfiedLinkErrorScenario" and relaunch the app + And I configure Bugsnag for "UnsatisfiedLinkErrorScenario" + And I wait to receive an error + And the report contains the required fields + And the exception "errorClass" equals "java.lang.UnsatisfiedLinkError" + And the exception "type" equals "android" + And the event "severity" equals "error" + And the event "unhandled" is true - Scenario: Undefined JNI method - When I run "UnsatisfiedLinkErrorScenario" and relaunch the app - And I configure Bugsnag for "UnsatisfiedLinkErrorScenario" - And I wait to receive an error - And the report contains the required fields - And the exception "errorClass" equals "java.lang.UnsatisfiedLinkError" - And the exception "type" equals "android" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Causing a crash in a separate library + When I run "CXXExternalStackElementScenario" and relaunch the app + And I configure Bugsnag for "CXXExternalStackElementScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals one of: + | SIGILL | + | SIGTRAP | + And the exception "message" equals one of: + | Illegal instruction | + | Trace/breakpoint trap | + And the exception "type" equals "c" + And the first significant stack frames match: + | something_innocuous | libmonochrome.so | (ignore) | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash | CXXExternalStackElementScenario.cpp | 14 | - Scenario: Causing a crash in a separate library - When I run "CXXExternalStackElementScenario" and relaunch the app - And I configure Bugsnag for "CXXExternalStackElementScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals one of: - | SIGILL | - | SIGTRAP | - And the exception "message" equals one of: - | Illegal instruction | - | Trace/breakpoint trap | - And the exception "type" equals "c" - And the first significant stack frame methods and files should match: - | something_innocuous | | libmonochrome.so | - | Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash | | libcxx-scenarios.so | + Scenario: Call null function pointer + A null pointer should be the first element of a stack trace, + followed by the calling function + + When I run "CXXCallNullFunctionPointerScenario" and relaunch the app + And I configure Bugsnag for "CXXCallNullFunctionPointerScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the "method" of stack frame 0 equals "0x0" + And the "lineNumber" of stack frame 0 equals 0 + And the first significant stack frames match: + | dispatch::Handler::handle(_jobject*) | CXXCallNullFunctionPointerScenario.cpp | 9 | diff --git a/features/full_tests/native_throw_crash.feature b/features/full_tests/native_throw_crash.feature index 4c72ea1874..4c5ba720f6 100644 --- a/features/full_tests/native_throw_crash.feature +++ b/features/full_tests/native_throw_crash.feature @@ -1,21 +1,47 @@ Feature: Native crash reporting with thrown objects - Scenario: Throwing an exception in C++ - When I run "CXXExceptionScenario" and relaunch the app - And I configure Bugsnag for "CXXExceptionScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals "SIGABRT" - And the exception "message" equals "Abort program" + Scenario: Throwing an exception in C++ + When I run "CXXExceptionScenario" and relaunch the app + And I configure Bugsnag for "CXXExceptionScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals "SIGABRT" + And the exception "message" equals "Abort program" + + Scenario: Throwing an object in C++ + When I run "CXXThrowSomethingScenario" and relaunch the app + And I configure Bugsnag for "CXXThrowSomethingScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals "SIGABRT" + And the exception "message" equals "Abort program" + + Scenario: Rethrow in C++ without initial exception + When I run "CXXInvalidRethrow" and relaunch the app + And I configure Bugsnag for "CXXInvalidRethrow" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGABRT" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | print_last_exception() | CXXInvalidRethrow.cpp | 7 | + + Scenario: Throw from C++ noexcept function + When I run "CXXThrowFromNoexcept" and relaunch the app + And I configure Bugsnag for "CXXThrowFromNoexcept" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGABRT" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And on arm64, the first significant stack frames match: + | FunkError::what() const | CXXThrowFromNoexcept.cpp | 13 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXThrowFromNoexcept_crash | CXXThrowFromNoexcept.cpp | 21 | - Scenario: Throwing an object in C++ - When I run "CXXThrowSomethingScenario" and relaunch the app - And I configure Bugsnag for "CXXThrowSomethingScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals "SIGABRT" - And the exception "message" equals "Abort program" diff --git a/features/smoke_tests/unhandled.feature b/features/smoke_tests/unhandled.feature index 7d19e61a23..72a3d5d884 100644 --- a/features/smoke_tests/unhandled.feature +++ b/features/smoke_tests/unhandled.feature @@ -202,11 +202,11 @@ Scenario: C++ exception thrown with overwritten config # Stacktrace validation And the error payload field "events.0.exceptions.0.stacktrace" is a non-empty array And the event stacktrace identifies the program counter - And the first significant stack frame methods and files should match: - | magicstacks::top() | | libcxx-scenarios-bugsnag.so | - | magicstacks::middle() | | libcxx-scenarios-bugsnag.so | - | magicstacks::start() | | libcxx-scenarios-bugsnag.so | - | Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash | | libcxx-scenarios-bugsnag.so | + And the first significant stack frames match: + | magicstacks::top() | CXXExceptionSmokeScenario.cpp | 13 | + | magicstacks::middle() | CXXExceptionSmokeScenario.cpp | 16 | + | magicstacks::start() | CXXExceptionSmokeScenario.cpp | 18 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash | CXXExceptionSmokeScenario.cpp | 25 | # App data And the event binary arch field is valid diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index 4e0c12b99a..4ad2e86e76 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -96,47 +96,6 @@ Maze.check.include(possible_values.raw.flatten, value) end -Then("the exception {string} demangles to {string}") do |keypath, expected_value| - actual_value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.exceptions.0.#{keypath}") - demangled_value = `c++filt --types --no-strip-underscore '#{actual_value}'`.chomp - Maze.check.true(demangled_value == expected_value, - "expected '#{demangled_value}' to equal '#{expected_value}'") -end - -# Checks whether the first significant frames match several given frames -# -# @param expected_values [Array] A table dictating the expected files and methods of the frames -# The first two entries are methods (enabling flexibility across SDKs), the third is the file name -Then("the first significant stack frame methods and files should match:") do |expected_values| - stacktrace = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.exceptions.0.stacktrace") - expected_frame_values = expected_values.raw - significant_frames = stacktrace.each_with_index.map do |frame, index| - method = `c++filt -_ _#{frame["method"]}`.chomp - method = frame["method"] if method == "_#{frame["method"]}" - insignificant = method.start_with?("bsg_") || - method.start_with?("std::") || - method.start_with?("__cx") || - method.start_with?("0x") || - frame["file"].start_with?("/system/") || - frame["file"].end_with?("libbugsnag-ndk.so") - { :index => index, :method => method, :file => frame["file"] } unless insignificant - end - significant_frames.select! { |frame| frame } - expected_frame_values.each_with_index do |expected_frame, index| - test_frame = significant_frames[index] - method_match_a = expected_frame[0] == test_frame[:method] - method_match_b = expected_frame[1] == test_frame[:method] - Maze.check.true( - method_match_a || method_match_b, - "'#{test_frame[:method]}' in frame #{test_frame[:index]} is not equal to '#{expected_frame[0]}' or '#{expected_frame[1]}'" - ) - Maze.check.true( - test_frame[:file].end_with?(expected_frame[2]), - "'#{test_frame[:file]}' in frame #{test_frame[:index]} does not end with '#{expected_frame[2]}'" - ) - end -end - Then("the report contains the required fields") do steps %Q{ And the error payload field "notifier.name" is not null diff --git a/features/steps/symbol_steps.rb b/features/steps/symbol_steps.rb new file mode 100644 index 0000000000..4e24aef08b --- /dev/null +++ b/features/steps/symbol_steps.rb @@ -0,0 +1,114 @@ +# Steps for validating native stack trace contents. +# Depends on the the following binaries being available in $PATH: +# +# * c++filt +# * addr2line + +# Sentinel value which can be used in stack frame matching to ignore location info +IGNORED_VALUE = '(ignore)' + +# Checks whether the first significant frames in an event match provided frames +# +# @param expected_values [Array] A table dictating the expected files and methods of the frames +# The table is formatted as any of: +# | method | +# or +# | method | binary filename | +# or +# | method | source file name | line number | +# or +# | method | (ignore) | +# or +# | method | (ignore) | (ignore) | +Then("the first significant stack frames match:") do |expected_values| + body = Maze::Server.errors.current[:body] + arch = Maze::Helper.read_key_path(body, "events.0.app.binaryArch") + stacktrace = Maze::Helper.read_key_path(body, "events.0.exceptions.0.stacktrace") + + significant_frames = stacktrace.map { |frame| symbolicate(arch, frame) }.flatten.compact + + expected_values.raw.each_with_index do |expected_frame, index| + raise "No matching significant frame at index #{index}" if significant_frames.length <= index + + test_frame = significant_frames[index] + Maze.check.equal( + expected_frame[0], test_frame[:method], + "'#{test_frame[:method]}' in frame #{index} is not equal to '#{expected_frame[0]}'" + ) + if expected_frame.length > 1 && expected_frame[1] != IGNORED_VALUE + Maze.check.true( + test_frame[:file].end_with?(expected_frame[1]), + "'#{test_frame[:file]}' in frame #{index} does not end with '#{expected_frame[1]}'" + ) + end + if expected_frame.length > 2 && expected_frame[2] != IGNORED_VALUE + Maze.check.equal(test_frame[:lineNumber], expected_frame[2], + "line number #{test_frame[:lineNumber]} in frame #{index} does not equal #{expected_frame[2]}" + ) + end + end +end + +# skip step unless on specified architecture +Then(/^on (arm32|arm64|x86|x86_64), (.+)$/) do |arch, step_text, table| + body = Maze::Server.errors.current[:body] + actual_arch = Maze::Helper.read_key_path(body, "events.0.app.binaryArch") + step(step_text, table) if arch == actual_arch +end + +Then("the exception {string} demangles to {string}") do |keypath, expected_value| + body = Maze::Server.errors.current[:body] + actual_value = Maze::Helper.read_key_path(body, "events.0.exceptions.0.#{keypath}") + demangled_value = demangle(actual_value) + Maze.check.equal(demangled_value, expected_value, + "expected '#{actual_value}' to demangle to '#{expected_value}' but was '#{demangled_value}'") +end + +def is_out_of_project? file, method + # no binary was found to match the address + file.nil? || + # in native functions from bugsnag-plugin-android-ndk + method.start_with?("bsg_") || file.end_with?("libbugsnag-ndk.so") || + # c++ standard library + hooks (__cxx_*, __cxa_*, etc) + method.start_with?("std::") || method.start_with?("__cx") || + # failed to resolve a symbol location + method.start_with?("0x") || + # sneaky libc functions + method.start_with?("str") || method.start_with?("abort") || + # android built-in libraries + file.start_with?("/system/") || file.start_with?("/apex/") +end + +def demangle symbol + `c++filt --types --no-strip-underscore #{symbol}`.chomp +end + +def lookup_address binary, address + info = `addr2line --exe '#{binary}' --inlines --basenames --functions --demangle 0x#{address.to_s(16)}`.chomp + return nil if info.start_with? '??' # failed to resolve + # can return multiple if there are inlined frames + frames = info.split("\n").each_slice(2).map do |function_name, location| + file, line = location.split(':') + { file: file, lineNumber: line, method: function_name } + end.reject { |frame| is_out_of_project?(frame[:file], frame[:method]) } + + return nil if frames.length == 0 + + frames +end + +# Resolve file and method name for in-project contents, returning nil +# if the frame is not in project. +def symbolicate arch, frame + method = demangle(frame["method"]) + binary_file = frame["file"] + + return nil if is_out_of_project?(binary_file, method) + + symbol_file = "build/#{File.basename(binary_file, '.so')}-#{arch}.so" + + if File.exist?(symbol_file) and sym_info = lookup_address(symbol_file, frame["lineNumber"]) + return sym_info + end + [{ :method => method, :file => binary_file }] +end From ff43126c9cfea231a40fa00e488e3d3efd01bdfc Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Thu, 3 Mar 2022 14:54:25 +0000 Subject: [PATCH 10/20] ci(ndk): support symbolicating native traces [full ci] --- .buildkite/pipeline.full.yml | 112 ++++++++++++++++++++----- .buildkite/pipeline.yml | 33 ++++++-- docker-compose.yml | 2 + dockerfiles/Dockerfile.android-builder | 11 ++- features/steps/symbol_steps.rb | 4 +- 5 files changed, 131 insertions(+), 31 deletions(-) diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 7349ff0281..42cc009692 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -11,7 +11,8 @@ steps: MAVEN_VERSION: "3.6.1" MINIMAL_FIXTURE: true TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-minimal.apk" + TEST_FIXTURE_NAME: "fixture-minimal" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Build Example App' @@ -26,7 +27,9 @@ steps: key: "fixture-debug" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/debug/fixture-debug.apk + artifact_paths: + - "build/debug/fixture-debug.apk" + - "build/debug/fixture-debug/*" plugins: - docker-compose#v3.7.0: run: android-builder @@ -34,7 +37,8 @@ steps: MAVEN_VERSION: "3.6.1" MINIMAL_FIXTURE: false TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-debug.apk" + TEST_FIXTURE_NAME: "fixture-debug" + TEST_FIXTURE_CONFIGURATION: "debug" BUILD_TASK: "assembleDebug" - label: ':android: Build Scan' @@ -70,7 +74,9 @@ steps: timeout_in_minutes: 30 plugins: artifacts#v1.2.0: - download: "build/debug/fixture-debug.apk" + download: + - "build/debug/fixture-debug.apk" + - "build/debug/fixture-debug/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -82,6 +88,8 @@ steps: - "--farm=bs" - "--device=ANDROID_9_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/debug/fixture-debug" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -99,7 +107,9 @@ steps: timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -112,6 +122,8 @@ steps: - "--farm=bs" - "--device=ANDROID_5_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -124,7 +136,9 @@ steps: timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -136,6 +150,8 @@ steps: - "--farm=bs" - "--device=ANDROID_5_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -148,7 +164,9 @@ steps: timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -163,6 +181,8 @@ steps: - "--device=ANDROID_6_0_SAMSUNG_GALAXY_S7" - "--device=ANDROID_6_0_GOOGLE_NEXUS_6" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -173,7 +193,9 @@ steps: timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -187,6 +209,8 @@ steps: - "--device=ANDROID_6_0_SAMSUNG_GALAXY_S7" - "--device=ANDROID_6_0_GOOGLE_NEXUS_6" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -197,7 +221,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -210,6 +236,8 @@ steps: - "--farm=bs" - "--device=ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -220,7 +248,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -232,6 +262,8 @@ steps: - "--farm=bs" - "--device=ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -242,7 +274,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -255,6 +289,8 @@ steps: - "--farm=bs" - "--device=ANDROID_8_1" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -265,7 +301,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -277,6 +315,8 @@ steps: - "--farm=bs" - "--device=ANDROID_8_1" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -287,7 +327,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -300,6 +342,8 @@ steps: - "features/smoke_tests" - "features/full_tests" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -310,7 +354,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -322,6 +368,8 @@ steps: - "--farm=bs" - "--device=ANDROID_9_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -332,7 +380,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -345,6 +395,8 @@ steps: - "--farm=bs" - "--device=ANDROID_10_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -355,7 +407,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -368,6 +422,8 @@ steps: - "--farm=bs" - "--device=ANDROID_10_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -382,7 +438,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -395,6 +453,8 @@ steps: - "--farm=bs" - "--device=ANDROID_11_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -405,7 +465,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -418,6 +480,8 @@ steps: - "--farm=bs" - "--device=ANDROID_11_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -432,7 +496,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -445,6 +511,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -455,7 +523,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -467,6 +537,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 1952411433..8bfcfeeaef 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -54,14 +54,17 @@ steps: key: "fixture-r16" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r16.apk + artifact_paths: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-r16.apk" + TEST_FIXTURE_NAME: "fixture-r16" + TEST_FIXTURE_CONFIGURATION: "release" USE_LEGACY_OKHTTP: "true" BUILD_TASK: "assembleRelease" @@ -69,28 +72,34 @@ steps: key: "fixture-r19" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r19.apk + artifact_paths: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "19.2.5345600" - TEST_FIXTURE_NAME: "fixture-r19.apk" + TEST_FIXTURE_NAME: "fixture-r19" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Build fixture APK r21' key: "fixture-r21" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r21.apk + artifact_paths: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "21.3.6528147" - TEST_FIXTURE_NAME: "fixture-r21.apk" + TEST_FIXTURE_NAME: "fixture-r21" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Lint' @@ -163,7 +172,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -174,6 +185,8 @@ steps: - "--farm=bs" - "--device=ANDROID_4_4" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -188,7 +201,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -201,6 +216,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager diff --git a/docker-compose.yml b/docker-compose.yml index 7534f7f0f8..4dc34d47c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: MINIMAL_FIXTURE: TEST_FIXTURE_NDK_VERSION: TEST_FIXTURE_NAME: + TEST_FIXTURE_CONFIGURATION: USE_LEGACY_OKHTTP: BUILD_TASK: volumes: @@ -78,6 +79,7 @@ services: BROWSER_STACK_ACCESS_KEY: SAUCE_LABS_USERNAME: SAUCE_LABS_ACCESS_KEY: + TEST_FIXTURE_SYMBOL_DIR: volumes: - ./build:/app/build - ./features/:/app/features/ diff --git a/dockerfiles/Dockerfile.android-builder b/dockerfiles/Dockerfile.android-builder index d61c23e89e..72cd0dbb80 100644 --- a/dockerfiles/Dockerfile.android-builder +++ b/dockerfiles/Dockerfile.android-builder @@ -10,5 +10,12 @@ CMD ./gradlew -p=${FIXTURE_PROJECT} ${BUILD_TASK} \ -PUSE_LEGACY_OKHTTP=${USE_LEGACY_OKHTTP} \ -PMINIMAL_FIXTURE=${MINIMAL_FIXTURE} \ -PTEST_FIXTURE_NDK_VERSION=${TEST_FIXTURE_NDK_VERSION} \ - -PTEST_FIXTURE_NAME=${TEST_FIXTURE_NAME} \ - && cp -R ${FIXTURE_PROJECT}/app/build/outputs/apk/* /app/build + -PTEST_FIXTURE_NAME=${TEST_FIXTURE_NAME}.apk \ + && FIXTURE_SYMBOL_DIR=/app/build/${TEST_FIXTURE_CONFIGURATION}/${TEST_FIXTURE_NAME} \ + && mkdir -p $FIXTURE_SYMBOL_DIR \ + && cp -R ${FIXTURE_PROJECT}/app/build/outputs/apk/* /app/build \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios-bugsnag/build/intermediates/cxx/*/*/obj/arm64-v8a/libcxx-scenarios-bugsnag.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-bugsnag-arm64.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios-bugsnag/build/intermediates/cxx/*/*/obj/armeabi-v7a/libcxx-scenarios-bugsnag.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-bugsnag-arm32.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios/build/intermediates/cxx/*/*/obj/arm64-v8a/libcxx-scenarios.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-arm64.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios/build/intermediates/cxx/*/*/obj/armeabi-v7a/libcxx-scenarios.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-arm32.so + diff --git a/features/steps/symbol_steps.rb b/features/steps/symbol_steps.rb index 4e24aef08b..4fecae4666 100644 --- a/features/steps/symbol_steps.rb +++ b/features/steps/symbol_steps.rb @@ -6,6 +6,8 @@ # Sentinel value which can be used in stack frame matching to ignore location info IGNORED_VALUE = '(ignore)' +# Location on disk of native symbol files +SYMBOL_DIR = ENV['TEST_FIXTURE_SYMBOL_DIR'] || 'build' # Checks whether the first significant frames in an event match provided frames # @@ -105,7 +107,7 @@ def symbolicate arch, frame return nil if is_out_of_project?(binary_file, method) - symbol_file = "build/#{File.basename(binary_file, '.so')}-#{arch}.so" + symbol_file = File.join(SYMBOL_DIR, "#{File.basename(binary_file, '.so')}-#{arch}.so") if File.exist?(symbol_file) and sym_info = lookup_address(symbol_file, frame["lineNumber"]) return sym_info From 421b1da5b9e02d919b3cd49edd3b0ef47042c2cb Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 12:04:26 +0000 Subject: [PATCH 11/20] ci: disable android 5 builds This tests fail for spurious reasons so frequently that their results are not useful, because of either inherent OS instability or the device farm quality. [full ci] --- .buildkite/pipeline.full.yml | 49 --------------------------------- features/steps/android_steps.rb | 3 +- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 7349ff0281..14d1317035 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -93,55 +93,6 @@ steps: - docker-compose#v3.7.0: run: android-sizer - - label: ':android: Android 5 NDK r16 end-to-end tests - batch 1' - depends_on: - - "fixture-r16" - timeout_in_minutes: 90 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/smoke_tests" - - "features/full_tests" - - "--exclude=features/full_tests/[^a-m].*.feature" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - - label: ':android: Android 5 NDK r16 end-to-end tests - batch 2' - depends_on: - - "fixture-r16" - timeout_in_minutes: 90 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/full_tests" - - "--exclude=features/full_tests/[^n-z].*.feature" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - label: ':android: Android 6 NDK r16 end-to-end tests - batch 1' depends_on: - "fixture-r16" diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index c89ae15da2..bce11aa943 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -5,8 +5,7 @@ When('any dialog is cleared and the element {string} is present') do |element_id| count = 0 present = false - # Give Android 5 more time to find elements in an attempt to combat flakes - timeout = (Maze.config.os_version == 5 ? 15 : 3) + timeout = 3 until present || count > 5 present = Maze.driver.wait_for_element(element_id, timeout = timeout) break if present From 8718f2dd66577dcea9da9ee8c2c37bd280233748 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 12:09:10 +0000 Subject: [PATCH 12/20] test: allow larger delta for foreground ms range This test fails regularly on lower spec'd x86 runs due to variance in the duration. --- .../com/bugsnag/android/AppDataCollectorForegroundTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt index c87fe1926e..b853655648 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt @@ -62,10 +62,10 @@ class AppDataCollectorForegroundTest { val appWithState = appDataCollector.generateAppWithState() assertEquals(true, appWithState.inForeground) - // allow for 20ms of error + // allow for 200ms of error assertTrue( "Unexpected durationInForeground: ${appWithState.durationInForeground}", - appWithState.durationInForeground in 1000L..1020L + appWithState.durationInForeground in 1000L..1200L ) verify(sessionTracker, times(1)).isInForeground From 7ef76843e5dad4601974485c42b1a90f5772ae04 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 12:22:30 +0000 Subject: [PATCH 13/20] build: remove unused ABI from size reports --- features/fixtures/minimalapp/Dangerfile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/features/fixtures/minimalapp/Dangerfile b/features/fixtures/minimalapp/Dangerfile index d941401f86..6661078b0f 100644 --- a/features/fixtures/minimalapp/Dangerfile +++ b/features/fixtures/minimalapp/Dangerfile @@ -9,7 +9,6 @@ UNBUNDLED_RELEASE = "app-release.apks" STANDALONE_DIR = BUNDLE_DIR + "standalones/" STANDALONE_HDPI_BASE = "standalone-hdpi.apk" STANDALONE_ARM64_V8A = "standalone-arm64_v8a_hdpi.apk" -STANDALONE_ARMEABI = "standalone-armeabi_hdpi.apk" STANDALONE_ARMEABI_V7A = "standalone-armeabi_v7a_hdpi.apk" STANDALONE_X86_64 = "standalone-x86_64_hdpi.apk" STANDALONE_X86 = "standalone-x86_hdpi.apk" @@ -48,7 +47,6 @@ buildOutputs(bugsnag: true, minified: false) apk_bugsnag_size = `stat --printf="%s" #{RELEASE_APK}`.to_i arm64_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARM64_V8A}`.to_i -armeabi_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI}`.to_i armeabi_v7a_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI_V7A}`.to_i x86_64_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86_64}`.to_i x86_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86}`.to_i @@ -57,20 +55,17 @@ buildOutputs(bugsnag: true, minified: true) min_apk_bugsnag_size = `stat --printf="%s" #{RELEASE_APK}`.to_i min_arm64_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARM64_V8A}`.to_i -min_armeabi_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI}`.to_i min_armeabi_v7a_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI_V7A}`.to_i min_x86_64_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86_64}`.to_i min_x86_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86}`.to_i calculated_sizes = { :arm64 => arm64_bugsnag_size - aab_size, - :armeabi => armeabi_bugsnag_size - aab_size, :armeabi_v7a => armeabi_v7a_bugsnag_size - aab_size, :x86_64 => x86_64_bugsnag_size - aab_size, :x86 => x86_bugsnag_size - aab_size, :apk => apk_bugsnag_size - apk_size, :min_arm64 => min_arm64_bugsnag_size - min_aab_size, - :min_armeabi => min_armeabi_bugsnag_size - min_aab_size, :min_armeabi_v7a => min_armeabi_v7a_bugsnag_size - min_aab_size, :min_x86_64 => min_x86_64_bugsnag_size - min_aab_size, :min_x86 => min_x86_bugsnag_size - min_aab_size, @@ -88,7 +83,6 @@ markdown(%Q{ |-------------|-----------------------------------------------|---------------------------------------------------| | APK | #{format_kbs(calculated_sizes[:apk])} | #{format_kbs(calculated_sizes[:min_apk])} | | arm64_v8a | #{format_kbs(calculated_sizes[:arm64])} | #{format_kbs(calculated_sizes[:min_arm64])} | - | armeabi | #{format_kbs(calculated_sizes[:armeabi])} | #{format_kbs(calculated_sizes[:min_armeabi])} | | armeabi_v7a | #{format_kbs(calculated_sizes[:armeabi_v7a])} | #{format_kbs(calculated_sizes[:min_armeabi_v7a])} | | x86 | #{format_kbs(calculated_sizes[:x86])} | #{format_kbs(calculated_sizes[:min_x86])} | | x86_64 | #{format_kbs(calculated_sizes[:x86_64])} | #{format_kbs(calculated_sizes[:min_x86_64])} | From 4bbaa30f12fc31b9c0ccc5c537647c3f39c0cb9f Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 12:38:18 +0000 Subject: [PATCH 14/20] build: add make target for running all lints --- Makefile | 6 ++++++ docs/TESTING.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a5c64e2a0e..84a57769ef 100644 --- a/Makefile +++ b/Makefile @@ -52,3 +52,9 @@ endif @sed -i '' "s/var version: String = .*/var version: String = \"$(VERSION)\",/"\ bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @sed -i '' "s/## TBD/## $(VERSION) ($(shell date '+%Y-%m-%d'))/" CHANGELOG.md + +.PHONY: check +check: + @./gradlew lint detekt ktlintCheck checkstyle + @./scripts/run-cpp-check.sh + @./scripts/run-clang-format-ci-check.sh diff --git a/docs/TESTING.md b/docs/TESTING.md index 6e269890d3..ee7aebc2f4 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -20,7 +20,7 @@ Instrumentation tests require an Android emulator or device to run, and they can ## Static analysis -Several static analysis checks are run against bugsnag-android to maintain the quality of the codebase. `./gradlew check` runs them all at once. +Several static analysis and code style checks are run against bugsnag-android to maintain the quality of the codebase. `make check` runs them all at once. ### Android Lint From 73d546449fac6155ae5e3a3cb62a5971599a5d57 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 12:48:33 +0000 Subject: [PATCH 15/20] build: auto-bump the version of bugsnag-android in example deps Update the example app to use the latest version of bugsnag-android during the release process --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a5c64e2a0e..ae96053f5b 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ ifeq ($(VERSION),) @$(error VERSION is not defined. Run with `make VERSION=number bump`) endif @echo Bumping the version number to $(VERSION) + @sed -i '' "s/bugsnag-android:.*\"/bugsnag-android:$(VERSION)\"/" examples/sdk-app-example/app/build.gradle @sed -i '' "s/VERSION_NAME=.*/VERSION_NAME=$(VERSION)/" gradle.properties @sed -i '' "s/var version: String = .*/var version: String = \"$(VERSION)\",/"\ bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt From 7e01a7a52afa85e50c6fe5e111c6a302aeb58edd Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 4 Mar 2022 11:45:30 +0000 Subject: [PATCH 16/20] doc: add changelog entry for unwinder fixes --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf4677bed5..c54d753a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 5.19.3 (TBD) + +### Bug fixes + +* Fix inconsistencies in stack trace quality for C/C++ events. Resolves a few + cases where file and line number information was not resolving to the correct + locations. This change may result in grouping changes to more correctly + highlight the root cause of an event. + [#1605](https://github.com/bugsnag/bugsnag-android/pull/1605) + [#1606](https://github.com/bugsnag/bugsnag-android/pull/1606) + ## 5.19.2 (2022-01-31) ### Bug fixes From 7a373bdf829ca229658e607ca9de11fd365c772d Mon Sep 17 00:00:00 2001 From: Delisa Date: Mon, 14 Mar 2022 10:35:44 +0000 Subject: [PATCH 17/20] ci: remove android 5 builds (#1623) --- .buildkite/pipeline.quick.yml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/.buildkite/pipeline.quick.yml b/.buildkite/pipeline.quick.yml index c3f65c3397..de2e9455b2 100644 --- a/.buildkite/pipeline.quick.yml +++ b/.buildkite/pipeline.quick.yml @@ -1,27 +1,4 @@ steps: - - label: ':android: Android 5 NDK r16 smoke tests' - key: 'android-5-smoke' - depends_on: "fixture-r16" - timeout_in_minutes: 60 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/smoke_tests" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - label: ':android: Android 6 NDK r16 smoke tests' key: 'android-6-smoke' depends_on: "fixture-r16" From cdbc9f6f2ae53f5351002d15e151c6afc183393a Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Mon, 14 Mar 2022 11:20:49 +0100 Subject: [PATCH 18/20] Rework the background task service shutdown procedure to give more headroom against the 5s ANR timeout --- CHANGELOG.md | 3 +++ .../java/com/bugsnag/android/BackgroundTaskService.kt | 9 ++------- .../java/com/bugsnag/android/LaunchCrashDeliveryTest.kt | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa98dac108..ae14b1ecdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Bug fixes +* Fixed an issue where an uncaught exeption on the main thread could in rare cases trigger an ANR. + [#1624](https://github.com/bugsnag/bugsnag-android/pull/1624) + * Fix inconsistencies in stack trace quality for C/C++ events. Resolves a few cases where file and line number information was not resolving to the correct locations. This change may result in grouping changes to more correctly diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt index 4e763633f0..c171c23d50 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt @@ -158,18 +158,13 @@ internal class BackgroundTaskService( internalReportExecutor.shutdownNow() defaultExecutor.shutdownNow() - // shutdown the error/session executors first, waiting for existing tasks to complete. - // If a request fails it may perform IO to persist the payload for delivery next launch, - // which would submit tasks to the IO executor - therefore it's critical to - // shutdown the IO executor last. + // Wait a little while for these ones to shut down errorExecutor.shutdown() sessionExecutor.shutdown() + ioExecutor.shutdown() errorExecutor.awaitTerminationSafe() sessionExecutor.awaitTerminationSafe() - - // shutdown the IO executor last, waiting for any existing tasks to complete - ioExecutor.shutdown() ioExecutor.awaitTerminationSafe() } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt index bafc1ab351..3978dcb8e6 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt @@ -140,7 +140,7 @@ class LaunchCrashDeliveryTest { payload: EventPayload, deliveryParams: DeliveryParams ): DeliveryStatus { - Thread.sleep(3000) + Thread.sleep(2000) count.getAndIncrement() return DeliveryStatus.DELIVERED } From 6a52afcceb3963f3e9f6eb9dca6a1fa672765d12 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 17 Mar 2022 08:48:13 +0000 Subject: [PATCH 19/20] v5.21.0 --- CHANGELOG.md | 4 ++-- .../src/main/java/com/bugsnag/android/Notifier.kt | 2 +- examples/sdk-app-example/app/build.gradle | 2 +- gradle.properties | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae14b1ecdb..cb28ce0d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## TBD +## 5.21.0 (2022-03-17) ### Bug fixes -* Fixed an issue where an uncaught exeption on the main thread could in rare cases trigger an ANR. +* Fixed an issue where an uncaught exception on the main thread could in rare cases trigger an ANR. [#1624](https://github.com/bugsnag/bugsnag-android/pull/1624) * Fix inconsistencies in stack trace quality for C/C++ events. Resolves a few diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt index 7851ffb4c3..bfa3ed5d7f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @@ -7,7 +7,7 @@ import java.io.IOException */ class Notifier @JvmOverloads constructor( var name: String = "Android Bugsnag Notifier", - var version: String = "5.20.0", + var version: String = "5.21.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/examples/sdk-app-example/app/build.gradle b/examples/sdk-app-example/app/build.gradle index 8c09f20689..7ee14b7135 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -38,7 +38,7 @@ android { } dependencies { - implementation "com.bugsnag:bugsnag-android:5.17.0" + implementation "com.bugsnag:bugsnag-android:5.20.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.appcompat:appcompat:1.4.0" implementation "com.google.android.material:material:1.4.0" diff --git a/gradle.properties b/gradle.properties index 13efc4cbdf..1e1329ff98 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx4096m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true -VERSION_NAME=5.20.0 +VERSION_NAME=5.21.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git From 868a91ff56ea319b5963b5ee2b014a4253bd2652 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 17 Mar 2022 10:54:30 +0000 Subject: [PATCH 20/20] chore(changelog): corrected CHANGELOG placing the libunwindstack upgrade as an Enhancement rather than Bug Fix --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb28ce0d8d..703305c4fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,7 @@ ## 5.21.0 (2022-03-17) -### Bug fixes - -* Fixed an issue where an uncaught exception on the main thread could in rare cases trigger an ANR. - [#1624](https://github.com/bugsnag/bugsnag-android/pull/1624) +### Enhancements * Fix inconsistencies in stack trace quality for C/C++ events. Resolves a few cases where file and line number information was not resolving to the correct @@ -14,6 +11,11 @@ [#1605](https://github.com/bugsnag/bugsnag-android/pull/1605) [#1606](https://github.com/bugsnag/bugsnag-android/pull/1606) +### Bug fixes + +* Fixed an issue where an uncaught exception on the main thread could in rare cases trigger an ANR. + [#1624](https://github.com/bugsnag/bugsnag-android/pull/1624) + ## 5.20.0 (2022-03-10) ### Enhancements