process crashes in-situ on Android device

This commit is contained in:
Aaron Culliney 2015-09-26 15:20:54 -07:00
parent 88be6086a2
commit d90b523440
12 changed files with 254 additions and 15 deletions

View File

@ -101,8 +101,6 @@ public class Apple2Activity extends Activity {
public native void nativeEjectDisk(boolean driveA);
public native void nativePerformCrash(int crashType);
@Override
public void onCreate(Bundle savedInstanceState) {
@ -405,6 +403,7 @@ public class Apple2Activity extends Activity {
synchronized (Apple2Activity.this) {
if (mSplashScreen != null) {
mSplashScreen.show();
Apple2CrashHandler.getInstance().checkForCrashes(Apple2Activity.this);
}
}
}

View File

@ -135,6 +135,129 @@ public class Apple2CrashHandler {
return areJavaCrashesPresent(activity) || areNativeCrashesPresent(activity);
}
public void checkForCrashes(final Apple2Activity activity) {
if (!areCrashesPresent(activity)) {
return;
}
boolean previouslyRanCrashCheck = mAlreadyRanCrashCheck.getAndSet(true);
boolean previouslySentReport = mAlreadySentReport.get();
if (previouslySentReport) {
// here we assume that the crash data was previously sent via email ... if not then we lost it =P
Log.d(TAG, "Cleaning up crash data ...");
int idx = 0;
File[] nativeCrashes = _nativeCrashFiles(activity);
for (File crash : nativeCrashes) {
if (!crash.delete()) {
Log.d(TAG, "Could not unlink crash : " + crash);
}
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
if (!processed.delete()) {
Log.d(TAG, "Could not unlink processed : " + processed);
}
}
File javaCrashFile = _javaCrashFile(activity);
if (!javaCrashFile.delete()) {
Log.d(TAG, "Could not unlink java crash : " + javaCrashFile);
}
// remove previous log file
_writeTempLogFile(new StringBuilder());
return;
}
if (previouslyRanCrashCheck) {
// don't keep asking on return from backgrounding
return;
}
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setNegativeButton(R.string.no, null).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
// assuming that the actual native processing works quickly ...
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
final int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(activity);
final int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/false);
final int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/true);
StringBuilder allCrashData = new StringBuilder();
// prepend information about this device
allCrashData.append(Build.BRAND);
allCrashData.append("\n");
allCrashData.append(Build.MODEL);
allCrashData.append("\n");
allCrashData.append(Build.MANUFACTURER);
allCrashData.append("\n");
allCrashData.append(Build.DEVICE);
allCrashData.append("\n");
allCrashData.append("Device sample rate:");
allCrashData.append(sampleRate);
allCrashData.append("\n");
allCrashData.append("Device mono buffer size:");
allCrashData.append(monoBufferSize);
allCrashData.append("\n");
allCrashData.append("Device stereo buffer size:");
allCrashData.append(stereoBufferSize);
allCrashData.append("\n");
File[] nativeCrashes = _nativeCrashFiles(activity);
if (nativeCrashes == null) {
nativeCrashes = new File[0];
}
// iteratively process native crashes
int idx = 0;
for (File crash : nativeCrashes) {
String crashPath = crash.getAbsolutePath();
Log.d(TAG, "Processing crash : " + crashPath);
String processedPath = _dumpPath2ProcessedPath(crashPath);
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
StringBuilder crashData = new StringBuilder();
if (!_readFile(new File(processedPath), crashData)) {
Log.e(TAG, "Error processing crash : " + crashPath);
}
allCrashData.append(">>>>>>> NATIVE CRASH [").append(crashPath).append("]\n");
allCrashData.append(crashData);
}
StringBuilder javaCrashData = new StringBuilder();
File javaCrashFile = _javaCrashFile(activity);
if (javaCrashFile.exists()) {
Log.d(TAG, "Reading java crashes file");
if (!_readFile(javaCrashFile, javaCrashData)) {
Log.e(TAG, "Error processing java crash : " + javaCrashFileName);
}
}
allCrashData.append(">>>>>>> JAVA CRASH DATA\n");
allCrashData.append(javaCrashData);
// send report with all the data
_sendEmailToDeveloperWithCrashData(activity, allCrashData);
}
}, 0);
}
}).create();
activity.registerAndShowDialog(crashDialog);
}
public void performCrash(int crashType) {
if (BuildConfig.DEBUG) {
nativePerformCrash(crashType);
@ -281,4 +404,6 @@ public class Apple2CrashHandler {
private static native void nativeOnUncaughtException(String home, String trace);
private static native void nativeProcessCrash(String crashFilePath, String crashProcessedPath);
}

View File

@ -28,13 +28,18 @@ LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/breakpad.C jnicrash.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) $(BREAKPAD_CFLAGS)
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(EMBEDDED_STACKWALKER),1)
LOCAL_CPPFLAGS += -DEMBEDDED_STACKWALKER=1
else
$(error OOPS, for now you should build with EMBEDDED_STACKWALKER=1)
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive

View File

@ -104,11 +104,12 @@ else
ln -s apple2ix.mk Android.mk
fi
###############################################################################
# build native sources
if test "x$do_release" = "x1" ; then
ndk-build V=1 NDK_MODULE_PATH=. # NDK_TOOLCHAIN_VERSION=clang
ndk-build V=1 NDK_MODULE_PATH=. EMBEDDED_STACKWALKER=1 # NDK_TOOLCHAIN_VERSION=clang
else
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=1 # NDK_TOOLCHAIN_VERSION=clang
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=1 EMBEDDED_STACKWALKER=1 # NDK_TOOLCHAIN_VERSION=clang
fi
ret=$?
if test "x$ret" != "x0" ; then

View File

@ -22,6 +22,19 @@ enum {
#include <jni.h>
// cribbed from AOSP and modified with usleep() and to also ignore EAGAIN (should this be a different errno than EINTR)
#define TEMP_FAILURE_RETRY_FOPEN(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
if (_rc == NULL && (errno == EINTR || errno == EAGAIN) ) { \
usleep(10); \
} else { \
break; \
} \
} while (1); \
_rc; })
static volatile int __attribute__((noinline)) _crash_null_deref(void) {
static volatile uintptr_t *ptr = NULL;
while ((ptr+1)) {
@ -128,3 +141,47 @@ void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativeOnUncaughtException(JNI
TEMP_FAILURE_RETRY(close(fd));
}
void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativeProcessCrash(JNIEnv *env, jclass cls, jstring jCrashPath, jstring jOutputPath) {
if (!(crashHandler && crashHandler->processCrash)) {
return;
}
LOG("...");
const char *crashPath = (*env)->GetStringUTFChars(env, jCrashPath, NULL);
const char *outputPath = (*env)->GetStringUTFChars(env, jOutputPath, NULL);
FILE *outputFILE = NULL;
char *symbolsPath = NULL;
do {
outputFILE = TEMP_FAILURE_RETRY_FOPEN(fopen(outputPath, "w"));
if (!outputFILE) {
ERRLOG("could not open %s", outputPath);
break;
}
if (android_armArchV7A) {
asprintf(&symbolsPath, "%s/symbols/armeabi-v7a", data_dir);
} else /*if (android_armArch)*/ {
asprintf(&symbolsPath, "%s/symbols/armeabi", data_dir);
} /*else { moar archs ... } */
bool success = crashHandler->processCrash(crashPath, symbolsPath, outputFILE);
if (!success) {
RELEASE_LOG("CRASH REPORT PROCESSING FAILED ...");
}
} while (0);
if (outputFILE) {
TEMP_FAILURE_RETRY(fflush(outputFILE));
TEMP_FAILURE_RETRY(fclose(outputFILE));
}
if (symbolsPath) {
FREE(symbolsPath);
}
(*env)->ReleaseStringUTFChars(env, jCrashPath, crashPath);
(*env)->ReleaseStringUTFChars(env, jOutputPath, outputPath);
}

View File

@ -103,8 +103,8 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobje
}
data_dir = strdup(dataDir);
if (initializeCrashHandler) {
initializeCrashHandler(data_dir);
if (crashHandler && crashHandler->init) {
crashHandler->init(data_dir);
}
(*env)->ReleaseStringUTFChars(env, j_dataDir, dataDir);
@ -249,10 +249,13 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnQuit(JNIEnv *env, jobject
cpu_resume();
emulator_shutdown();
if (crashHandler && crashHandler->shutdown) {
crashHandler->shutdown();
}
#endif
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyDown(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
if (UNLIKELY(shuttingDown)) {
return;

View File

@ -17,26 +17,52 @@
#include "common.h"
#include "client/linux/handler/exception_handler.h"
#if EMBEDDED_STACKWALKER
# include "processor/stackwalk_common.h"
#endif
static CrashHandler_s breakpadHandler = { 0 };
static google_breakpad::ExceptionHandler *eh = nullptr;
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
// WARNING : should only do minimal work from within a crashing context ...
//LOG("Dump path: %s\n", descriptor.path());
LOG("Dump path: %s\n", descriptor.path());
return succeeded;
}
extern "C" {
static void initializeBreakpadHandler(const char *dumpDir) {
static void initBreakpadHandler(const char *dumpDir) {
LOG("Initializing Breakpad with dump dir : %s", dumpDir);
google_breakpad::MinidumpDescriptor descriptor(dumpDir);
eh = new google_breakpad::ExceptionHandler(descriptor, NULL, dumpCallback, NULL, true, -1);
}
static void shutdownBreakpadHandler(void) {
delete eh;
eh = nullptr;
}
static bool processCrashWithBreakpad(const char *crash, const char *symbolsPath, const FILE *outputFile) {
#if EMBEDDED_STACKWALKER
LOG("Running breakpad stackwalker on crash ...");
stackwalker_setOutputFile(outputFile);
int err = stackwalker_main(crash, symbolsPath, /*machineReadable:*/true);
stackwalker_setOutputFile(NULL);
return err == 0;
#else
#warning NOT COMPILING WITH EMBEDDED STACKWALKER
return false;
#endif
}
__attribute__((constructor(CTOR_PRIORITY_EARLY)))
static void _breakpad_registration(void) {
LOG("Registering Breakpad as handler");
initializeCrashHandler = &initializeBreakpadHandler;
LOG("Registering Breakpad as native crash handler");
breakpadHandler.init = &initBreakpadHandler;
breakpadHandler.shutdown = &shutdownBreakpadHandler;
breakpadHandler.processCrash = &processCrashWithBreakpad;
crashHandler = &breakpadHandler;
}
}

View File

@ -23,7 +23,7 @@ color_mode_t color_mode = COLOR;
const char *data_dir = NULL;
char **argv = NULL;
int argc = 0;
void (*initializeCrashHandler)(const char *dumpDir) = NULL;
CrashHandler_s *crashHandler = NULL;
__attribute__((constructor(CTOR_PRIORITY_FIRST)))
static void _init_common() {

View File

@ -30,7 +30,30 @@ void emulator_start(void);
// shutdown emulator in preparation for app exit
void emulator_shutdown(void);
// crash handler initialization (if available)
extern void (*initializeCrashHandler)(const char *dumpDir);
//
// Crash handling ...
//
typedef struct CrashHandler_s {
/**
* Initialize crash handler (if available)
*/
void (*init)(const char *dumpDir);
/**
* Shutdown crash handler (if available)
*/
void (*shutdown)(void);
/**
* Processes a crash dump (assuming this is a non-crashing contest).
* Returns success value. On failure, the outputFile may contain the reason processing failed
*/
bool (*processCrash)(const char *crash, const char *symbolsPath, const FILE *outputFile);
} CrashHandler_s;
extern CrashHandler_s *crashHandler;
#endif