diff --git a/jni/Android.mk b/jni/Android.mk index 8b42669..4c27bb8 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -4,7 +4,7 @@ include $(CLEAR_VARS) LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog LOCAL_MODULE := native -LOCAL_SRC_FILES := native.cpp +LOCAL_SRC_FILES := native.cpp breakpad.cpp crash.cpp LOCAL_STATIC_LIBRARIES += breakpad_client include $(BUILD_SHARED_LIBRARY) @@ -14,5 +14,5 @@ include $(BUILD_SHARED_LIBRARY) ifneq ($(NDK_MODULE_PATH),) $(call import-module,google_breakpad) else - include $(LOCAL_PATH)/../../breakpad/android/google_breakpad/Android.mk + include $(LOCAL_PATH)/../../breakpad/src/android/google_breakpad/Android.mk endif diff --git a/jni/Application.mk b/jni/Application.mk index dd83bdc..1ecaefb 100644 --- a/jni/Application.mk +++ b/jni/Application.mk @@ -1,2 +1,4 @@ APP_STL := stlport_static APP_ABI := armeabi armeabi-v7a +APP_CFLAGS += -std=c++11 -D__STDC_LIMIT_MACROS + diff --git a/jni/breakpad.cpp b/jni/breakpad.cpp new file mode 100644 index 0000000..9ab4b31 --- /dev/null +++ b/jni/breakpad.cpp @@ -0,0 +1,177 @@ +#include "breakpad.h" +#include +#include +#include +#include + +#define private public +#include "client/linux/handler/exception_handler.h" +#include "client/linux/handler/minidump_descriptor.h" +#include "client/linux/dump_writer_common/ucontext_reader.h" + +// static exception handler +static google_breakpad::ExceptionHandler* exceptionHandler; + +// these values are compilation time constants +const size_t pt_size = sizeof(void*); +const size_t STACK_PAGES = 2; +const size_t BYTES_PRIOR = 256; + +// these statics are queried at run time but the queries are idempotent (the same results expected each time) +static size_t page_size; +static size_t page_half; +static size_t mem_count; + +// this only needs to be used once +static uintptr_t* sortedBuf = NULL; + +// this one is a dummy marker. whatever equals it may be discarded. +static google_breakpad::AppMemory dummyChunk; + +inline uintptr_t ToPage(uintptr_t address) { + return address & (-page_size); +} + +inline bool IsPageMapped(void* page_address) { + unsigned char vector; + return 0 /*OK*/ == mincore(page_address, page_size, &vector); +} + +inline bool IsPageMapped(uintptr_t page_address) { + return IsPageMapped(reinterpret_cast(page_address)); +} + +inline bool IsAddressMapped(uintptr_t address) { + return IsPageMapped(ToPage(address)); +} + +void EnsureBuf() { + if (sortedBuf == NULL) { + size_t sizeOfBuf = page_size * STACK_PAGES; + // could be malloc(sizeOfBuf) but I preferred a dedicated mapping + sortedBuf = reinterpret_cast(mmap(NULL, sizeOfBuf, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + // assert sortedBuf != null + } +} + +class HeapContext { +private: + google_breakpad::AppMemoryList* mList; + +public: + void Attach(google_breakpad::ExceptionHandler * eh) { + google_breakpad::AppMemoryList& memoryList = eh->app_memory_list_; + // preallocate mem_count dummy chunks + for (int i = 0; i < mem_count; ++i) { + memoryList.push_back(dummyChunk); + } + mList = &memoryList; + } + + void TryIncludePage(google_breakpad::AppMemoryList::reverse_iterator& chunk, bool allowMerge, uintptr_t& fault, uintptr_t page_address) { + if (page_address != fault) { + // try merge + if (allowMerge) { + uintptr_t last_address = reinterpret_cast(chunk->ptr) + chunk->length; + if (page_address >= last_address) { + if (IsPageMapped(page_address)) { + if (page_address > last_address) { + --chunk; + chunk->ptr = reinterpret_cast(page_address); + chunk->length = page_size; + } else { + chunk->length += page_size; + } + } else { + fault = page_address; + } + } + } else { + // map, then create chunk + if (IsPageMapped(page_address)) { + --chunk; + chunk->ptr = reinterpret_cast(page_address); + chunk->length = page_size; + } else { + fault = page_address; + } + } + } + } + + void CollectData(uintptr_t stackTop, uintptr_t stackSize) { + memcpy(sortedBuf, reinterpret_cast(stackTop), stackSize); + uintptr_t ptr_count = stackSize / pt_size; + uintptr_t* sortedPtr = sortedBuf; + uintptr_t* sortedEnd = sortedBuf + ptr_count; + std::sort(sortedPtr, sortedEnd); // qsort + + google_breakpad::AppMemoryList::reverse_iterator start = mList->rend(); + google_breakpad::AppMemoryList::reverse_iterator chunk = start; + uintptr_t fault = 0xffffffff; + while (sortedPtr != sortedEnd) { + uintptr_t stackAddr = *(sortedPtr++); + if (stackAddr >= BYTES_PRIOR) { + stackAddr -= BYTES_PRIOR; + } + uintptr_t stackPage = ToPage(stackAddr); + TryIncludePage(chunk, chunk != start, fault, stackPage); + if (stackAddr > stackPage + page_half) { + TryIncludePage(chunk, chunk != start, fault, stackPage + page_size); + } + } + } +}; + +bool HeapCallback(const void* crash_context, size_t crash_context_size, void* context) { + // << We have a different source of information for the crashing thread.>> -- Google + // * google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc:322 + + google_breakpad::ExceptionHandler::CrashContext* cc = (google_breakpad::ExceptionHandler::CrashContext*) crash_context; + uintptr_t stackTop = ToPage(google_breakpad::UContextReader::GetStackPointer(&cc->context)); + + // http://man7.org/linux/man-pages/man2/mincore.2.html + if (IsPageMapped(stackTop)) { + uintptr_t stackSize = page_size; + + // extra page. keep in sync with STACK_PAGES + if (IsPageMapped(stackTop + page_size)) { + stackSize += page_size; + } + + HeapContext* userContext = reinterpret_cast(context); + userContext->CollectData(stackTop, stackSize); + } + + return false; // we haven't handled - only prepared the handling +} + +bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + printf("Dump path: %s\n", descriptor.path()); + return succeeded; +} + +void setUpBreakpad(const char* path, bool moreDump) { + google_breakpad::MinidumpDescriptor descriptor(path); + // http://man7.org/linux/man-pages/man3/sysconf.3.html + page_size = sysconf(_SC_PAGESIZE); + page_half = page_size >> 1; + mem_count = page_size * STACK_PAGES / pt_size; + + dummyChunk.ptr = &dummyChunk; + dummyChunk.length = 0; + + // signal handler registration is an EH constructor side effect. upon exit, we are registered + if (moreDump) { + HeapContext* userContext = new HeapContext; + exceptionHandler = new google_breakpad::ExceptionHandler(descriptor, NULL, DumpCallback, userContext, true, -1); + exceptionHandler->set_crash_handler(HeapCallback); + userContext->Attach(exceptionHandler); + EnsureBuf(); + } else { + exceptionHandler = new google_breakpad::ExceptionHandler(descriptor, NULL, DumpCallback, NULL, true, -1); + } +} diff --git a/jni/breakpad.h b/jni/breakpad.h new file mode 100644 index 0000000..81218bb --- /dev/null +++ b/jni/breakpad.h @@ -0,0 +1,6 @@ +#ifndef __BREAKPAD_H +#define __BREAKPAD_H + +void setUpBreakpad(const char* path, bool moreDump); + +#endif /*__BREAKPAD_H*/ \ No newline at end of file diff --git a/jni/crash.cpp b/jni/crash.cpp new file mode 100644 index 0000000..4ab1dd8 --- /dev/null +++ b/jni/crash.cpp @@ -0,0 +1,53 @@ +#include "crash.h" +#include +#include +#include + +void swap(int*& left, int*& right) { + int*tmp = left; + left = right; + right = tmp; +} + +void bubble(int** first, int** last) { + if (first < last) { + bubble(&first[1], last); + while (first < last) { + if (*first[0] > *first[1]) { + swap(first[0], first[1]); + } + ++first; + } + } +} + +void CrashWithStack() { + const int count = 10; + const int last = count - 1; + int values[count] = { 12, 34, 5, 7, 218, 923, -1, 0, -1, 5}; + int* refs[count]; + memset(&refs, 0, sizeof(refs)); + // let's introduce an error here and skip the base index (0) + for (int i = 1; i < count; ++i) { + refs[i] = &values[i]; + } + // now let's do a bubble sort + bubble(&refs[0], &refs[last]); +} + +bool compare(int* left, int* right) { + return *left < *right; +} + +void CrashWithHeap() { + const int count = 10; + const int last = count - 1; + int values[count] = { 12, 34, 5, 7, 218, 923, -1, 0, -1, 5}; + std::vector refs(count); + // let's introduce an error here and skip the base index (0) + for (int i = 1; i < count; ++i) { + refs[i] = &values[i]; + } + // now let's do a sort with a custom comparator + std::sort(refs.begin(), refs.end(), compare); +} diff --git a/jni/crash.h b/jni/crash.h new file mode 100644 index 0000000..76c7ce0 --- /dev/null +++ b/jni/crash.h @@ -0,0 +1,7 @@ +#ifndef __CRASH_H +#define __CRASH_H + +void CrashWithStack(); +void CrashWithHeap(); + +#endif /*__CRASH_H*/ \ No newline at end of file diff --git a/jni/native.cpp b/jni/native.cpp index 7141f8f..42b245e 100644 --- a/jni/native.cpp +++ b/jni/native.cpp @@ -1,31 +1,24 @@ #include -#include -#include "client/linux/handler/exception_handler.h" -#include "client/linux/handler/minidump_descriptor.h" -static google_breakpad::ExceptionHandler* exceptionHandler; -bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, - void* context, - bool succeeded) { - printf("Dump path: %s\n", descriptor.path()); - return succeeded; -} - -void Crash() { - volatile int* a = reinterpret_cast(NULL); - *a = 1; -} +#include "breakpad.h" +#include "crash.h" extern "C" { -void Java_com_hockeyapp_breakapp_MainActivity_setUpBreakpad(JNIEnv* env, jobject obj, jstring filepath) { +void Java_com_hockeyapp_breakapp_MainActivity_setUpBreakpad(JNIEnv* env, jobject obj, jstring filepath, jboolean moreDump) { const char *path = env->GetStringUTFChars(filepath, 0); - google_breakpad::MinidumpDescriptor descriptor(path); - exceptionHandler = new google_breakpad::ExceptionHandler(descriptor, NULL, DumpCallback, NULL, true, -1); + setUpBreakpad(path, moreDump); } -void Java_com_hockeyapp_breakapp_MainActivity_nativeCrash(JNIEnv* env, jobject obj) { - Crash(); +void Java_com_hockeyapp_breakapp_MainActivity_nativeCrash(JNIEnv* env, jobject obj, jboolean withHeap) { + if (withHeap) { + CrashWithHeap(); + } else { + CrashWithStack(); + } } +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return JNI_VERSION_1_6; +} } diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml index cd91b79..f0f7ace 100644 --- a/res/layout/activity_main.xml +++ b/res/layout/activity_main.xml @@ -100,7 +100,21 @@ android:layout_marginBottom="10dip" android:layout_marginTop="0dp" android:onClick="onNativeCrashClicked" - android:text="C++ Crash" /> + android:text="C++ Crash (stack)" /> + +