This commit is contained in:
Hajime Hoshi 2019-08-21 01:43:43 +09:00
parent 2d2da09b85
commit 708bd50405
18 changed files with 2324 additions and 35 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,36 @@
###############################################################################
#
# "FastLZ" compression library
#
###############################################################################
CFILES = coffeecatch.c
all: gcc test
clean:
rm -f *.o *.obj *.so* *.dll *.exe *.pdb *.exp *.lib sample
tar:
rm -f coffeecatch.tgz
tar cvfz coffeecatch.tgz coffeecatch.txt coffeecatch.c coffeecatch.h Makefile LICENSE README.md
gcc:
gcc -c -fPIC -O3 -g3 -pthread \
-W -Wall -Wextra -Werror -Wno-unused-function \
-D_REENTRANT -D_GNU_SOURCE \
$(CFILES)
gcc -shared -fPIC -O3 -Wl,-O1 -Wl,--no-undefined \
-rdynamic -shared -Wl,-soname=libcoffeecatch.so \
coffeecatch.o -o libcoffeecatch.so \
-ldl -lpthread
test:
gcc -c -fPIC -O3 -g3 \
-W -Wall -Wextra -Werror -Wno-unused-function \
-D_REENTRANT \
sample.c -o sample.o
gcc -fPIC -O3 -Wl,-O1 \
-lcoffeecatch -L. \
sample.o -o sample

View File

@ -0,0 +1,122 @@
coffeecatch
===========
**CoffeeCatch**, a *tiny* native POSIX signal catcher (especially useful for JNI code on **Android**/Dalvik, but it can be used in non-Java projects)
It allows to "gracefully" recover from a **signal** (`SIGSEGV`, `SIGBUS`...) as if it was an **exception**. It will not gracefully recover from allocator/mutexes corruption etc., however, but at least "most" gentle crashes (null pointer dereferencing, integer division, stack overflow etc.) should be handled without too much troubles.
```c
/** Enter protected section. **/
COFFEE_TRY() {
/** Try to call 'call_some_native_function'. **/
call_some_protected_function();
} COFFEE_CATCH() {
/** Caught a signal: throw Java exception. **/
/** In pure C projects, you may print an error message (coffeecatch_get_message()). **/
coffeecatch_throw_exception(env);
} COFFEE_END();
```
You may read the corresponding [discussion](http://blog.httrack.com/blog/2013/08/23/catching-posix-signals-on-android/) about this project.
The handler is thread-safe, but client must have exclusive control on the signal handlers (ie. the library is installing its own signal handlers on top of the existing ones).
**Libraries**
If you want to get useful stack traces, you should build all your libraries with `-funwind-tables` (this adds unwinding information). On ARM, you may also use the `--no-merge-exidx-entries` linker switch, to solve certain issues with unwinding (the switch is possibly not needed anymore). On Android, this can be achieved by using this line in the `Android.mk` file in each library block:
```
LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
```
**Example**
* Inside JNI (typically, Android)
*First, build the library, or just add the two files in the list of local files to be built:*
```
LOCAL_SRC_FILES += coffeecatch.c coffeejni.c
```
*then, use the COFFEE_TRY_JNI() macro to protect your call(s):*
```c
/** The potentially dangerous function. **/
jint call_dangerous_function(JNIEnv* env, jobject object) {
// ... do dangerous things!
return 42;
}
/** Protected function stub. **/
void foo_protected(JNIEnv* env, jobject object, jint *retcode) {
/* Try to call 'call_dangerous_function', and raise proper Java Error upon
* fatal error (SEGV, etc.). **/
COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
}
/** Regular JNI entry point. **/
jint Java_com_example_android_MyNative_foo(JNIEnv* env, jobject object) {
jint retcode = 0;
foo_protected(env, object, &retcode);
return retcode;
}
```
*and, in case of crash, get something like this (note: the last Exception with native backtrace is produced on Android >= 4.1.1)*:
```
FATAL EXCEPTION: AsyncTask #5
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:299)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
at java.util.concurrent.FutureTask.run(FutureTask.java:239)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libexample.so:0xa024]
at com.example.jni.ExampleLib.main(Native Method)
at com.example.ExampleActivity$Runner.runInternal(ExampleActivity.java:998)
at com.example.ExampleActivity$Runner.doInBackground(ExampleActivity.java:919)
at com.example.ExampleActivity$Runner.doInBackground(ExampleActivity.java:1)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
... 4 more
Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libexample.so:0xa024]
at data.app_lib.com_example.libexample_so.0xa024(Native Method)
at data.app_lib.com_example.libexample_so.0x705fc(hts_main2:0x8f74:0)
at data.app_lib.com_example.libexamplejni_so.0x4cc8(ExampleLib_main:0xf8:0)
at data.app_lib.com_example.libexamplejni_so.0x52d8(Java_com_example_jni_ExampleLib_main:0x64:0)
at system.lib.libdvm_so.0x1dc4c(dvmPlatformInvoke:0x70:0)
at system.lib.libdvm_so.0x4dcab(dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x18a:0)
at system.lib.libdvm_so.0x385e1(dvmCheckCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x8:0)
at system.lib.libdvm_so.0x4f699(dvmResolveNativeMethod(unsigned int const*, JValue*, Method const*, Thread*):0xb8:0)
at system.lib.libdvm_so.0x27060(Native Method)
at system.lib.libdvm_so.0x2b580(dvmInterpret(Thread*, Method const*, JValue*):0xb8:0)
at system.lib.libdvm_so.0x5fcbd(dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list):0x124:0)
at system.lib.libdvm_so.0x5fce7(dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...):0x14:0)
at system.lib.libdvm_so.0x54a6f(Native Method)
at system.lib.libc_so.0xca58(__thread_entry:0x48:0)
at system.lib.libc_so.0xcbd4(pthread_create:0xd0:0)
```
* Outside JNI code
The COFFEE_TRY()/COFFEE_CATCH()/COFFEE_END() syntax can be used:
```c
void my_function() {
COFFEE_TRY() {
/** Try to call 'call_some_native_function'. **/
call_some_native_function();
} COFFEE_CATCH() {
/** Caught a signal. **/
const char*const message = coffeecatch_get_message();
fprintf(stderr, "**FATAL ERROR: %s\n", message);
} COFFEE_END();
}
```
* Hints
If you wish to catch signals and continue running your program rather than ending it (this may be dangerous, especially if a crash was spotted within a C library function, such as `malloc()`), use the `coffeecatch_cancel_pending_alarm()` function to cancel the default pending alarm triggered to avoid deadlocks.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,228 @@
/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
* (especially for Android/Dalvik)
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
* See the "License" section below for the licensing terms.
*
* Description:
*
* Allows to "gracefully" recover from a signal (segv, sibus...) as if it was
* a Java exception. It will not gracefully recover from allocator/mutexes
* corruption etc., however, but at least "most" gentle crashes (null pointer
* dereferencing, integer division, stack overflow etc.) should be handled
* without too much troubles.
*
* The handler is thread-safe, but client must have exclusive control on the
* signal handlers (ie. the library is installing its own signal handlers on
* top of the existing ones).
*
* You must build all your libraries with `-funwind-tables', to get proper
* unwinding information on all binaries. On ARM, you may also use the
* `--no-merge-exidx-entries` linker switch, to solve certain issues with
* unwinding (the switch is possibly not needed anymore).
* On Android, this can be achieved by using this line in the Android.mk file
* in each library block:
* LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
*
* Example:
*
* COFFEE_TRY() {
* call_some_native_function()
* } COFFEE_CATCH() {
* const char*const message = coffeecatch_get_message();
* jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
* (*env)->ThrowNew(env, cls, strdup(message));
* } COFFEE_END();
*
* Implementation notes:
*
* Currently the library is installing both alternate stack and signal
* handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,
* SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to
* "userland" (compared to signal handler context). As a security, an alarm
* is started as soon as a fatal signal is detected (ie. not something the
* JVM will handle) to kill the process after a grace period. Be sure your
* program will exit quickly after the error is caught, or call alarm(0)
* to cancel the pending time-bomb.
* The signal handlers had to be written with caution, because the virtual
* machine might be using signals (including SEGV) to handle JIT compiler,
* and some clever optimizations (such as NullPointerException handling)
* We are using several signal-unsafe functions, namely:
* - siglongjmp() to return to userland
* - pthread_getspecific() to get thread-specific setup
*
* License:
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COFFEECATCH_H
#define COFFEECATCH_H
#include <stdint.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Setup crash handler to enter in a protected section. If a recognized signal
* is received in this section, the execution will be diverted to the
* COFFEE_CATCH() block.
*
* Note: you MUST use the following pattern when using this macro:
* COFFEE_TRY() {
* .. protected section without exit point
* } COFFEE_CATCH() {
* .. handler section without exit point
* } COFFEE_END();
*
* You can not exit the protected section block, or the handler section block,
* using statements such as "return", because the cleanup code would not be
* executed.
*
* It is advised to enclose this complete try/catch/end block in a dedicated
* function declared extern or __attribute__ ((noinline)).
*
* Example:
*
* void my_native_function(JNIEnv* env, jobject object, jint *retcode) {
* COFFEE_TRY() {
* *retcode = call_dangerous_function(env, object);
* } COFFEE_CATCH() {
* const char*const message = coffeecatch_get_message();
* jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
* (*env)->ThrowNew(env, cls, strdup(message));
* *retcode = -1;
* } COFFEE_END();
* }
*
* In addition, the following restrictions MUST be followed:
* - the function must be declared extern, or with the special attribute
* __attribute__ ((noinline)).
* - you must not use local variables before the complete try/catch/end block,
* or define them as "volatile".
* - your function should not ignore the crash silently, as the library will
* ensure the process is killed after a grace period (typically 30s) to
* prevent any deadlock that may occur if the crash was caught inside a
* non-signal-safe function, for example (such as malloc()).
*
COFFEE_TRY()
**/
/**
* Declare the signal handler block. This block will be executed if a signal
* was received, and recognized, in the previous COFFEE_TRY() {} section.
* You may call audit functions in this block, such as coffeecatch_get_signal()
* or coffeecatch_get_message().
*
COFFEE_CATCH()
**/
/**
* Declare the end of the COFFEE_TRY()/COFFEE_CATCH() section.
* Diagnostic functions must not be called beyond this point.
*
COFFEE_END()
**/
/**
* Get the signal associated with the crash.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern int coffeecatch_get_signal(void);
/**
* Get the full error message associated with the crash.
* This function can only be called inside a COFFEE_CATCH() block, and the
* returned pointer is only valid within this block. (you may want to copy
* the string in a static buffer, or use strdup())
*/
const char* coffeecatch_get_message(void);
/**
* Raise an abort() signal in the current thread. If the current code section
* is protected, the 'exp', 'file' and 'line' information are stored for
* further audit.
*/
extern void coffeecatch_abort(const char* exp, const char* file, int line);
/**
* Assertion check. If the expression is false, an abort() signal is raised
* using coffeecatch_abort().
*/
#define coffeecatch_assert(EXP) (void)( (EXP) || (coffeecatch_abort(#EXP, __FILE__, __LINE__), 0) )
/**
* Get the backtrace size, or 0 upon error.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern size_t coffeecatch_get_backtrace_size(void);
/**
* Get the backtrace pointer, or 0 upon error.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern uintptr_t coffeecatch_get_backtrace(ssize_t index);
/**
* Enumerate the backtrace with information.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern void coffeecatch_get_backtrace_info(void (*fun)(void *arg,
const char *module,
uintptr_t addr,
const char *function,
uintptr_t offset), void *arg);
/**
* Cancel any pending alarm() triggered after a signal was caught.
* Calling this function is dangerous, because it exposes the process to
* a possible deadlock if the signal was caught due to internal low-level
* library error (mutex being in a locked state, for example).
*/
extern int coffeecatch_cancel_pending_alarm(void);
/** Internal functions & definitions, not to be used directly. **/
#include <setjmp.h>
extern int coffeecatch_inside(void);
extern int coffeecatch_setup(void);
extern sigjmp_buf* coffeecatch_get_ctx(void);
extern void coffeecatch_cleanup(void);
#define COFFEE_TRY() \
if (coffeecatch_inside() || \
(coffeecatch_setup() == 0 \
&& sigsetjmp(*coffeecatch_get_ctx(), 1) == 0))
#define COFFEE_CATCH() else
#define COFFEE_END() coffeecatch_cleanup()
/** End of internal functions & definitions. **/
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,185 @@
/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
* (especially for Android/Dalvik)
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COFFEECATCH_JNI_H
#define COFFEECATCH_JNI_H
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <jni.h>
#include <assert.h>
#include "coffeecatch.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct t_bt_fun {
JNIEnv* env;
jclass cls;
jclass cls_ste;
jmethodID cons_ste;
jobjectArray elements;
size_t size;
size_t index;
} t_bt_fun;
static char* bt_print(const char *function, uintptr_t offset) {
if (function != NULL) {
char buffer[256];
snprintf(buffer, sizeof(buffer), "%s:%p", function, (void*) offset);
return strdup(buffer);
} else {
return "<unknown>";
}
}
static char* bt_addr(uintptr_t addr) {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%p", (void*) addr);
return strdup(buffer);
}
#define IS_VALID_CLASS_CHAR(C) ( \
((C) >= 'a' && (C) <= 'z') \
|| ((C) >= 'A' && (C) <= 'Z') \
|| ((C) >= '0' && (C) <= '9') \
|| (C) == '_' \
)
static char* bt_module(const char *module) {
if (module != NULL) {
size_t i;
char *copy;
if (*module == '/') {
module++;
}
copy = strdup(module);
/* Pseudo-java-class. */
for(i = 0; copy[i] != '\0'; i++) {
if (copy[i] == '/') {
copy[i] = '.';
} else if (!IS_VALID_CLASS_CHAR(copy[i])) {
copy[i] = '_';
}
}
return copy;
} else {
return "<unknown>";
}
}
static void bt_fun(void *arg, const char *module, uintptr_t addr,
const char *function, uintptr_t offset) {
t_bt_fun *const t = (t_bt_fun*) arg;
JNIEnv*const env = t->env;
jstring declaringClass = (*env)->NewStringUTF(env, bt_module(module));
jstring methodName = (*env)->NewStringUTF(env, bt_addr(addr));
jstring fileName = (*env)->NewStringUTF(env, bt_print(function, offset));
const int lineNumber = function != NULL ? 0 : -2; /* "-2" is "inside JNI code" */
jobject trace = (*env)->NewObject(env, t->cls_ste, t->cons_ste,
declaringClass, methodName, fileName,
lineNumber);
if (t->index < t->size) {
(*t->env)->SetObjectArrayElement(t->env, t->elements, t->index++, trace);
}
}
void coffeecatch_throw_exception(JNIEnv* env) {
jclass cls = (*env)->FindClass(env, "java/lang/Error");
jclass cls_ste = (*env)->FindClass(env, "java/lang/StackTraceElement");
jmethodID cons = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
jmethodID cons_cause = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
jmethodID cons_ste = (*env)->GetMethodID(env, cls_ste, "<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
jmethodID meth_sste = (*env)->GetMethodID(env, cls, "setStackTrace",
"([Ljava/lang/StackTraceElement;)V");
/* Exception message. */
const char*const message = coffeecatch_get_message();
jstring str = (*env)->NewStringUTF(env, strdup(message));
/* Final exception. */
jthrowable exception;
/* Add pseudo-stack trace. */
const ssize_t bt_size = coffeecatch_get_backtrace_size();
assert(cls != NULL);
assert(cls_ste != NULL);
assert(cons != NULL);
assert(cons_cause != NULL);
assert(cons_ste != NULL);
assert(meth_sste != NULL);
assert(message != NULL);
assert(str != NULL);
/* Can we produce a stack trace ? */
if (bt_size > 0) {
/* Create secondary exception. */
jthrowable cause = (jthrowable) (*env)->NewObject(env, cls, cons, str);
/* Stack trace. */
jobjectArray elements =
(*env)->NewObjectArray(env, bt_size, cls_ste, NULL);
if (elements != NULL) {
t_bt_fun t;
t.env = env;
t.cls = cls;
t.cls_ste = cls_ste;
t.cons_ste = cons_ste;
t.elements = elements;
t.index = 0;
t.size = bt_size;
coffeecatch_get_backtrace_info(bt_fun, &t);
(*env)->CallVoidMethod(env, cause, meth_sste, elements);
}
/* Primary exception */
exception = (jthrowable) (*env)->NewObject(env, cls, cons_cause, str, cause);
} else {
/* Simple exception */
exception = (jthrowable) (*env)->NewObject(env, cls, cons, str);
}
/* Throw exception. */
if (exception != NULL) {
(*env)->Throw(env, exception);
} else {
(*env)->ThrowNew(env, cls, strdup(message));
}
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,131 @@
/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
* (especially for Android/Dalvik)
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
* See the "License" section below for the licensing terms.
*
* Description:
*
* Allows to "gracefully" recover from a signal (segv, sibus...) as if it was
* a Java exception. It will not gracefully recover from allocator/mutexes
* corruption etc., however, but at least "most" gentle crashes (null pointer
* dereferencing, integer division, stack overflow etc.) should be handled
* without too much troubles.
*
* The handler is thread-safe, but client must have exclusive control on the
* signal handlers (ie. the library is installing its own signal handlers on
* top of the existing ones).
*
* You must build all your libraries with `-funwind-tables', to get proper
* unwinding information on all binaries. On ARM, you may also use the
* `--no-merge-exidx-entries` linker switch, to solve certain issues with
* unwinding (the switch is possibly not needed anymore).
* On Android, this can be achieved by using this line in the Android.mk file
* in each library block:
* LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
*
* Example:
* COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
*
* Implementation notes:
*
* Currently the library is installing both alternate stack and signal
* handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,
* SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to
* "userland" (compared to signal handler context). As a security, an alarm
* is started as soon as a fatal signal is detected (ie. not something the
* JVM will handle) to kill the process after a grace period. Be sure your
* program will exit quickly after the error is caught, or call alarm(0)
* to cancel the pending time-bomb.
* The signal handlers had to be written with caution, because the virtual
* machine might be using signals (including SEGV) to handle JIT compiler,
* and some clever optimizations (such as NullPointerException handling)
* We are using several signal-unsafe functions, namely:
* - siglongjmp() to return to userland
* - pthread_getspecific() to get thread-specific setup
*
* License:
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COFFEECATCH_JNI_H
#define COFFEECATCH_JNI_H
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Setup crash handler to enter in a protected section. If a recognized signal
* is received in this section, an appropriate native Java Error will be
* raised.
*
* You can not exit the protected section block CODE_TO_BE_EXECUTED, using
* statements such as "return", because the cleanup code would not be
* executed.
*
* It is advised to enclose the complete CODE_TO_BE_EXECUTED block in a
* dedicated function declared extern or __attribute__ ((noinline)).
*
* You must build all your libraries with `-funwind-tables', to get proper
* unwinding information on all binaries. On Android, this can be achieved
* by using this line in the Android.mk file in each library block:
* LOCAL_CFLAGS := -funwind-tables
*
* Example:
*
* void my_native_function(JNIEnv* env, jobject object, jint *retcode) {
* COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
* }
*
* In addition, the following restrictions MUST be followed:
* - the function must be declared extern, or with the special attribute
* __attribute__ ((noinline)).
* - you must not use local variables before the COFFEE_TRY_JNI block,
* or define them as "volatile".
*
COFFEE_TRY_JNI(JNIEnv* env, CODE_TO_BE_EXECUTED)
*/
/** Internal functions & definitions, not to be used directly. **/
extern void coffeecatch_throw_exception(JNIEnv* env);
#define COFFEE_TRY_JNI(ENV, CODE) \
do { \
COFFEE_TRY() { \
CODE; \
} COFFEE_CATCH() { \
coffeecatch_throw_exception(ENV); \
} COFFEE_END(); \
} while(0)
/** End of internal functions & definitions. **/
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include "coffeecatch.h"
int recurse_madness(int level) {
static int var[] = { 1, 2 };
if (level > 2000) {
return 1 + level;
} else {
return recurse_madness(level + 1)*var[level];
}
}
static char string_buffer[256];
static __attribute__ ((noinline)) void demo(int *fault) {
COFFEE_TRY() {
recurse_madness(42);
*fault = 0;
} COFFEE_CATCH() {
const char*const message = coffeecatch_get_message();
snprintf(string_buffer, sizeof(string_buffer), "%s", message);
*fault = 1;
} COFFEE_END();
}
int main(int argc, char **argv) {
int fault;
(void) argc;
(void) argv;
printf("running demo...\n");
demo(&fault);
if (fault != 0) {
fprintf(stderr, "** crash detected: %s\n", string_buffer);
exit(EXIT_FAILURE);
}
printf("success!\n");
return EXIT_SUCCESS;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:generate file2byteslice -package=main -input=gobind.go -output=gobind.src.go -var gobindsrc //go:generate file2byteslice -package=main -input=gobind.go -output=gobind.go.go -var gobind_go
//go:generate file2byteslice -package=main -input=coffeecatch/coffeecatch.c -output=coffeecatch.c.go -var coffeecatch_c -buildtags=ebitenmobilegobind
//go:generate file2byteslice -package=main -input=coffeecatch.c.go -output=coffeecatch.c.go.go -var coffeecatch_c_go
//go:generate rm coffeecatch.c.go
//go:generate file2byteslice -package=main -input=coffeecatch/coffeecatch.h -output=coffeecatch.h.go -var coffeecatch_h -buildtags=ebitenmobilegobind
//go:generate file2byteslice -package=main -input=coffeecatch.h.go -output=coffeecatch.h.go.go -var coffeecatch_h_go
//go:generate rm coffeecatch.h.go
//go:generate file2byteslice -package=main -input=coffeecatch/coffeejni.c -output=coffeejni.c.go -var coffeejni_c -buildtags=ebitenmobilegobind
//go:generate file2byteslice -package=main -input=coffeejni.c.go -output=coffeejni.c.go.go -var coffeejni_c_go
//go:generate rm coffeejni.c.go
//go:generate file2byteslice -package=main -input=coffeecatch/coffeejni.h -output=coffeejni.h.go -var coffeejni_h -buildtags=ebitenmobilegobind
//go:generate file2byteslice -package=main -input=coffeejni.h.go -output=coffeejni.h.go.go -var coffeejni_h_go
//go:generate rm coffeejni.h.go
package main package main

View File

@ -80,8 +80,16 @@ func invokeOriginalGobind(lang string) (pkgName string, err error) {
} }
func run() error { func run() error {
writeFile := func(filename string, content string) error { readFile := func(filename string) ([]byte, error) {
if err := ioutil.WriteFile(filepath.Join(*outdir, filename), []byte(content), 0644); err != nil { content, err := ioutil.ReadFile(filepath.Join(*outdir, filename))
if err != nil {
return nil, err
}
return content, nil
}
writeFile := func(filename string, content []byte) error {
if err := ioutil.WriteFile(filepath.Join(*outdir, filename), content, 0644); err != nil {
return err return err
} }
return nil return nil
@ -105,22 +113,44 @@ func run() error {
switch lang { switch lang {
case "objc": case "objc":
// iOS if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), []byte(replacePrefixes(objcM))); err != nil {
if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), replacePrefixes(objcM)); err != nil {
return err return err
} }
case "java": case "java":
// Android
dir := filepath.Join(strings.Split(*javaPkg, ".")...) dir := filepath.Join(strings.Split(*javaPkg, ".")...)
dir = filepath.Join(dir, prefixLower) dir = filepath.Join(dir, prefixLower)
if err := writeFile(filepath.Join("java", dir, "EbitenView.java"), replacePrefixes(viewJava)); err != nil { if err := writeFile(filepath.Join("java", dir, "EbitenView.java"), []byte(replacePrefixes(viewJava))); err != nil {
return err return err
} }
if err := writeFile(filepath.Join("java", dir, "EbitenSurfaceView.java"), replacePrefixes(surfaceViewJava)); err != nil { if err := writeFile(filepath.Join("java", dir, "EbitenSurfaceView.java"), []byte(replacePrefixes(surfaceViewJava))); err != nil {
return err
}
// Use CoffeeCatch (https://github.com/xroche/coffeecatch)
binding, err := readFile(filepath.Join("src", "gobind", "ebitenmobileview_android.c"))
if err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", "ebitenmobileview_android.c"), []byte(addCoffeeCatch(string(binding)))); err != nil {
return err return err
} }
case "go": case "go":
// Do nothing. if err := writeFile(filepath.Join("src", "gobind", "coffeecatch.c"), coffeecatch_c); err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", "coffeecatch.h"), coffeecatch_h); err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", "coffeejni.c"), coffeejni_c); err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", "coffeejni.h"), coffeejni_h); err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", "cgoflags.go"), []byte(cgoFlagsGo)); err != nil {
return err
}
default: default:
panic(fmt.Sprintf("unsupported language: %s", lang)) panic(fmt.Sprintf("unsupported language: %s", lang))
} }
@ -129,6 +159,42 @@ func run() error {
return nil return nil
} }
func addInclude(src string, filename string) string {
idx0 := strings.LastIndex(src, "#include ")
idx1 := strings.Index(src[idx0:], "\n") + idx0
return src[:idx1] + "\n#include \"" + filename + "\"" + src[idx1:]
}
func addCoffeeCatch(src string) string {
src = addInclude(src, "coffeecatch.h")
src = addInclude(src, "coffeejni.h")
const sig = "ebitenmobileview_Ebitenmobileview_update(JNIEnv* env, jclass _clazz) {"
idx0 := strings.Index(src, sig) + len(sig)
idx1 := strings.Index(src[idx0:], "}") + idx0
p0 := src[:idx0]
p1 := src[idx0:idx1]
p2 := src[idx1:]
const coffeeCatchPre = `
COFFEE_TRY() {
`
const coffeeCatchPost = `
} COFFEE_CATCH() {
coffeecatch_throw_exception(env);
} COFFEE_END();
`
return p0 + coffeeCatchPre + p1 + coffeeCatchPost + p2
}
const cgoFlagsGo = `// Code generated by ebitenmobile. DO NOT EDIT.
package main
// #cgo CFLAGS: -D_REENTRANT
import "C"
`
const objcM = `// Code generated by ebitenmobile. DO NOT EDIT. const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
// +build ios // +build ios
@ -196,7 +262,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
// TODO: Notify this to Go world? // TODO: Notify this to Go world?
} }
- (void)drawFrame{ - (void)drawFrame {
@synchronized(self) { @synchronized(self) {
if (!active_) { if (!active_) {
return; return;
@ -252,8 +318,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
[self updateTouches:touches]; [self updateTouches:touches];
} }
- (void)suspendGame - (void)suspendGame {
{
NSAssert(started_, @"suspendGame msut not be called before viewDidLoad is called"); NSAssert(started_, @"suspendGame msut not be called before viewDidLoad is called");
@synchronized(self) { @synchronized(self) {
@ -261,8 +326,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
} }
} }
- (void)resumeGame - (void)resumeGame {
{
NSAssert(started_, @"resumeGame msut not be called before viewDidLoad is called"); NSAssert(started_, @"resumeGame msut not be called before viewDidLoad is called");
@synchronized(self) { @synchronized(self) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -108,11 +108,22 @@ func prepareGomobileCommands() error {
if err := os.Mkdir("src", 0755); err != nil { if err := os.Mkdir("src", 0755); err != nil {
return err return err
} }
if err := ioutil.WriteFile(filepath.Join("src", "gobind.go"), gobindsrc, 0644); err != nil { if err := ioutil.WriteFile(filepath.Join("src", "gobind.go"), gobind_go, 0644); err != nil {
return err return err
} }
if err := ioutil.WriteFile(filepath.Join("src", "coffeecatch.c.go"), coffeecatch_c_go, 0644); err != nil {
if err := runGo("build", "-o", filepath.Join("bin", "gobind"), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil { return err
}
if err := ioutil.WriteFile(filepath.Join("src", "coffeecatch.h.go"), coffeecatch_h_go, 0644); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join("src", "coffeejni.c.go"), coffeejni_c_go, 0644); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join("src", "coffeejni.h.go"), coffeejni_h_go, 0644); err != nil {
return err
}
if err := runGo("build", "-o", filepath.Join("bin", "gobind"), "-tags", "ebitenmobilegobind", "./src"); err != nil {
return err return err
} }

View File

@ -18,9 +18,7 @@ package mobile
import ( import (
"context" "context"
"fmt"
"image" "image"
"runtime/debug"
"sync" "sync"
"time" "time"
@ -184,16 +182,6 @@ func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, tit
} }
func (u *UserInterface) run(width, height int, scale float64, title string, context driver.UIContext, graphics driver.Graphics, mainloop bool) (err error) { func (u *UserInterface) run(width, height int, scale float64, title string, context driver.UIContext, graphics driver.Graphics, mainloop bool) (err error) {
// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
// unfortunately.
// TODO: Panic on other goroutines cannot be handled here.
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v\n%q", r, string(debug.Stack()))
}
}()
u.m.Lock() u.m.Lock()
u.width = width u.width = width
u.height = height u.height = height