diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java
index 2f64a09e..abb2b3ff 100644
--- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java
+++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java
@@ -106,6 +106,8 @@ public class Apple2Activity extends Activity {
public native void nativeEjectDisk(boolean driveA);
+ public native void nativePerformCrash(int crashType);
+
private void _setCustomExceptionHandler() {
if (mSetUncaughtExceptionHandler) {
diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java
index fa48b0f8..a4f2f932 100644
--- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java
+++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java
@@ -15,11 +15,13 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
+import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
+import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
public class Apple2SettingsMenu extends Apple2AbstractMenu {
@@ -290,6 +292,67 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
AlertDialog dialog = builder.create();
activity.registerAndShowDialog(dialog);
}
+ },
+ CRASH {
+ @Override
+ public final String getTitle(Apple2Activity activity) {
+ if (BuildConfig.DEBUG) {
+ // in debug mode we actually exercise the crash reporter ...
+ return activity.getResources().getString(R.string.crasher_title);
+ } else {
+ return activity.getResources().getString(R.string.crasher_send_title);
+ }
+ }
+
+ @Override
+ public final String getSummary(Apple2Activity activity) {
+ if (BuildConfig.DEBUG) {
+ return activity.getResources().getString(R.string.crasher_summary);
+ } else {
+ return activity.getResources().getString(R.string.crasher_send_summary);
+ }
+ }
+
+ @Override
+ public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
+
+ if (BuildConfig.DEBUG) {
+ _alertDialogHandleSelection(activity, R.string.crasher, new String[]{
+ activity.getResources().getString(R.string.crash_java_npe),
+ activity.getResources().getString(R.string.crash_null),
+ activity.getResources().getString(R.string.crash_stackcall_overflow),
+ activity.getResources().getString(R.string.crash_stackbuf_overflow),
+ }, new IPreferenceLoadSave() {
+ @Override
+ public int intValue() {
+ return -1;
+ }
+
+ @Override
+ public void saveInt(int value) {
+ switch (value) {
+ case 0: {
+ final String[] str = new String[1];
+ str[0] = null;
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "About to NPE : " + str[0].length());
+ }
+ });
+ }
+ break;
+
+ default:
+ activity.nativePerformCrash(value);
+ break;
+ }
+ }
+ });
+ } else {
+ // TODO FIXME : run local crash analysis and open Email Intent to send
+ }
+ }
};
public static final int size = SETTINGS.values().length;
diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml
index bb440c69..7f18feee 100644
--- a/Android/app/src/main/res/values/strings.xml
+++ b/Android/app/src/main/res/values/strings.xml
@@ -18,6 +18,16 @@
Black/white
Color
Interpolated color
+ Crasher
+ Crash emulator
+ Test crash generation
+ Send crash reports
+ Send crash reports
+ Will email crash reports to developer (if any)
+ NULL-deref
+ Java NPE
+ stack call overflow
+ stack buffer overflow
Drive 1
Drive 2
Eject
diff --git a/Android/jni/apple2ix.mk b/Android/jni/apple2ix.mk
index 58247c21..ecbcd31d 100644
--- a/Android/jni/apple2ix.mk
+++ b/Android/jni/apple2ix.mk
@@ -23,7 +23,7 @@ LOCAL_CPP_EXTENSION := .C
LOCAL_CPPFLAGS := -std=gnu++11
LOCAL_MODULE := libapple2ix
-LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/breakpad.C
+LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/breakpad.C jnicrash.c
#LOCAL_ARM_MODE := arm
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) $(BREAKPAD_CFLAGS)
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
diff --git a/Android/jni/jnicrash.c b/Android/jni/jnicrash.c
new file mode 100644
index 00000000..027dd3d7
--- /dev/null
+++ b/Android/jni/jnicrash.c
@@ -0,0 +1,93 @@
+/*
+ * Apple // emulator for *nix
+ *
+ * This software package is subject to the GNU General Public License
+ * version 2 or later (your choice) as published by the Free Software
+ * Foundation.
+ *
+ * THERE ARE NO WARRANTIES WHATSOEVER.
+ *
+ */
+
+#include "common.h"
+
+// Keep these in sync with the Java side
+enum {
+ CRASH_JAVA=0,
+ CRASH_NULL_DEREF,
+ CRASH_STACKCALL_OVERFLOW,
+ CRASH_STACKBUF_OVERFLOW,
+ // MOAR!
+};
+
+#include
+
+static volatile int __attribute__((noinline)) _crash_null_deref(void) {
+ static volatile uintptr_t *ptr = NULL;
+ while ((ptr+1)) {
+ *ptr++ = 0xAA;
+ }
+ return (int)ptr[0];
+}
+
+static volatile int (*funPtr0)(void) = NULL;
+static volatile int __attribute__((noinline)) _crash_stackcall_overflow(void) {
+ if (funPtr0) {
+ funPtr0();
+ funPtr0 = NULL;
+ } else {
+ funPtr0 = &_crash_stackcall_overflow;
+ funPtr0();
+ }
+ return getpid();
+}
+
+static volatile int (*funPtr1)(unsigned int) = NULL;
+static volatile int __attribute__((noinline)) _crash_stackbuf_overflow0(unsigned int smashSize) {
+ volatile char buf[32];
+ memset((char *)buf, 0x55, smashSize);
+ return (int)&buf[0];
+}
+
+static volatile int __attribute__((noinline)) _crash_stackbuf_overflow(void) {
+ static volatile unsigned int smashSize = 0;
+ while (1) {
+ if (funPtr1) {
+ funPtr1(smashSize);
+ funPtr1 = NULL;
+ } else {
+ funPtr1 = &_crash_stackbuf_overflow0;
+ funPtr1(smashSize);
+ }
+
+ smashSize += 32;
+ if (!smashSize) {
+ break;
+ }
+ }
+ return getpid();
+}
+
+void Java_org_deadc0de_apple2ix_Apple2Activity_nativePerformCrash(JNIEnv *env, jobject obj, jint crashType) {
+ LOG("... performing crash of type : %d", crashType);
+
+ switch (crashType) {
+ case CRASH_NULL_DEREF:
+ _crash_null_deref();
+ break;
+
+ case CRASH_STACKCALL_OVERFLOW:
+ _crash_stackcall_overflow();
+ break;
+
+ case CRASH_STACKBUF_OVERFLOW:
+ _crash_stackbuf_overflow();
+ break;
+
+ default:
+ // unknown crasher, just abort ...
+ abort();
+ break;
+ }
+}
+