Skip to content
This repository was archived by the owner on Jul 3, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
2 changes: 2 additions & 0 deletions jni/Application.mk
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
APP_STL := stlport_static
APP_ABI := armeabi armeabi-v7a
APP_CFLAGS += -std=c++11 -D__STDC_LIMIT_MACROS

177 changes: 177 additions & 0 deletions jni/breakpad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include "breakpad.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <algorithm>

#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<void*>(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<uintptr_t*>(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<uintptr_t>(chunk->ptr) + chunk->length;
if (page_address >= last_address) {
if (IsPageMapped(page_address)) {
if (page_address > last_address) {
--chunk;
chunk->ptr = reinterpret_cast<void*>(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<void*>(page_address);
chunk->length = page_size;
} else {
fault = page_address;
}
}
}
}

void CollectData(uintptr_t stackTop, uintptr_t stackSize) {
memcpy(sortedBuf, reinterpret_cast<void*>(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<HeapContext*>(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);
}
}
6 changes: 6 additions & 0 deletions jni/breakpad.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef __BREAKPAD_H
#define __BREAKPAD_H

void setUpBreakpad(const char* path, bool moreDump);

#endif /*__BREAKPAD_H*/
53 changes: 53 additions & 0 deletions jni/crash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "crash.h"
#include <string.h>
#include <vector>
#include <algorithm>

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<int*> 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);
}
7 changes: 7 additions & 0 deletions jni/crash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef __CRASH_H
#define __CRASH_H

void CrashWithStack();
void CrashWithHeap();

#endif /*__CRASH_H*/
33 changes: 13 additions & 20 deletions jni/native.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
#include <jni.h>
#include <stdio.h>
#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<volatile int*>(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;
}
}
16 changes: 15 additions & 1 deletion res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,21 @@
android:layout_marginBottom="10dip"
android:layout_marginTop="0dp"
android:onClick="onNativeCrashClicked"
android:text="C++ Crash" />
android:text="C++ Crash (stack)" />

<Button
android:id="@+id/ndk_button_heap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ndk_button"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginBottom="10dip"
android:layout_marginTop="0dp"
android:onClick="onNativeCrashClickedWithHeap"
android:text="C++ Crash (heap)" />

<TextView
android:id="@+id/text_view"
Expand Down
12 changes: 8 additions & 4 deletions src/com/hockeyapp/breakapp/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
public class MainActivity extends Activity {
private static final String HOCKEYAPP_ID = "98254247ac79b7cd96dbec27c53b7c9f";

private native void setUpBreakpad(String filepath);
private native void nativeCrash();
private native void setUpBreakpad(String filepath, boolean moreDump);
private native void nativeCrash(boolean withHeap);

static {
System.loadLibrary("native");
Expand All @@ -25,7 +25,7 @@ protected void onCreate(Bundle savedInstanceState) {

setContentView(R.layout.activity_main);
Constants.loadFromContext(this);
setUpBreakpad(Constants.FILES_PATH);
setUpBreakpad(Constants.FILES_PATH, true);
NativeCrashManager.handleDumpFiles(this, HOCKEYAPP_ID);
}

Expand All @@ -44,9 +44,13 @@ public void onSendFeedbackClicked(View v) {
}

public void onNativeCrashClicked(View v) {
nativeCrash();
nativeCrash(false);
}

public void onNativeCrashClickedWithHeap(View v) {
nativeCrash(true);
}

public void onCrashClicked(View v) {
View view = findViewById(0x123);
view.setVisibility(View.INVISIBLE);
Expand Down