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; + } +} +