From 708bd50405305bf527eba7396fd94f34b65e8565 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Wed, 21 Aug 2019 01:43:43 +0900 Subject: [PATCH] tmp --- cmd/ebitenmobile/coffeecatch.c.go.go | 6 + cmd/ebitenmobile/coffeecatch.h.go.go | 6 + cmd/ebitenmobile/coffeecatch/LICENSE | 23 + cmd/ebitenmobile/coffeecatch/Makefile | 36 + cmd/ebitenmobile/coffeecatch/README.md | 122 ++ cmd/ebitenmobile/coffeecatch/coffeecatch.c | 1418 ++++++++++++++++++++ cmd/ebitenmobile/coffeecatch/coffeecatch.h | 228 ++++ cmd/ebitenmobile/coffeecatch/coffeejni.c | 185 +++ cmd/ebitenmobile/coffeecatch/coffeejni.h | 131 ++ cmd/ebitenmobile/coffeecatch/sample.c | 43 + cmd/ebitenmobile/coffeejni.c.go.go | 6 + cmd/ebitenmobile/coffeejni.h.go.go | 6 + cmd/ebitenmobile/generate.go | 18 +- cmd/ebitenmobile/gobind.go | 90 +- cmd/ebitenmobile/gobind.go.go | 6 + cmd/ebitenmobile/gobind.src.go | 6 - cmd/ebitenmobile/gomobile.go | 17 +- internal/uidriver/mobile/ui.go | 12 - 18 files changed, 2324 insertions(+), 35 deletions(-) create mode 100644 cmd/ebitenmobile/coffeecatch.c.go.go create mode 100644 cmd/ebitenmobile/coffeecatch.h.go.go create mode 100644 cmd/ebitenmobile/coffeecatch/LICENSE create mode 100644 cmd/ebitenmobile/coffeecatch/Makefile create mode 100644 cmd/ebitenmobile/coffeecatch/README.md create mode 100644 cmd/ebitenmobile/coffeecatch/coffeecatch.c create mode 100644 cmd/ebitenmobile/coffeecatch/coffeecatch.h create mode 100644 cmd/ebitenmobile/coffeecatch/coffeejni.c create mode 100644 cmd/ebitenmobile/coffeecatch/coffeejni.h create mode 100644 cmd/ebitenmobile/coffeecatch/sample.c create mode 100644 cmd/ebitenmobile/coffeejni.c.go.go create mode 100644 cmd/ebitenmobile/coffeejni.h.go.go create mode 100644 cmd/ebitenmobile/gobind.go.go delete mode 100644 cmd/ebitenmobile/gobind.src.go diff --git a/cmd/ebitenmobile/coffeecatch.c.go.go b/cmd/ebitenmobile/coffeecatch.c.go.go new file mode 100644 index 000000000..7a6b13f83 --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch.c.go.go @@ -0,0 +1,6 @@ +// Code generated by file2byteslice. DO NOT EDIT. +// (gofmt is fine after generating) + +package main + +var coffeecatch_c_go = []byte("// Code generated by file2byteslice. DO NOT EDIT.\n// (gofmt is fine after generating)\n\n// +build ebitenmobilegobind\n\npackage main\n\nvar coffeecatch_c = []byte(\"/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.\\n * (especially for Android/Dalvik)\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n *\\n * Redistribution and use in source and binary forms, with or without\\n * modification, are permitted provided that the following conditions are met:\\n *\\n * 1. Redistributions of source code must retain the above copyright notice, this\\n * list of conditions and the following disclaimer.\\n * 2. Redistributions in binary form must reproduce the above copyright notice,\\n * this list of conditions and the following disclaimer in the documentation\\n * and/or other materials provided with the distribution.\\n *\\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND\\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\n */\\n\\n#ifdef __ANDROID__\\n#define USE_UNWIND\\n#define USE_CORKSCREW\\n#define USE_LIBUNWIND\\n#endif\\n\\n/* #undef NO_USE_SIGALTSTACK */\\n/* #undef USE_SILENT_SIGALTSTACK */\\n\\n#include \\n#include \\n#include \\n#include \\n#include \\n#include \\n#include \\n#include \\n#include \\n#include \\n#if defined(__ANDROID__) && !defined(__BIONIC_HAVE_UCONTEXT_T) && \\\\\\n defined(__arm__) && !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT)\\n#include \\n#endif \\n#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))\\n#include \\n#endif\\n#include \\n#include \\n#include \\\"coffeecatch.h\\\"\\n\\n/*#define NDK_DEBUG 1*/\\n#if ( defined(NDK_DEBUG) && ( NDK_DEBUG == 1 ) )\\n#define DEBUG(A) do { A; } while(0)\\n#define FD_ERRNO 2\\nstatic void print(const char *const s) {\\n size_t count;\\n for(count = 0; s[count] != '\\\\0'; count++) ;\\n /* write() is async-signal-safe. */\\n (void) write(FD_ERRNO, s, count);\\n}\\n#else\\n#define DEBUG(A)\\n#endif\\n\\n/* Alternative stack size. */\\n#define SIG_STACK_BUFFER_SIZE SIGSTKSZ\\n\\n#ifdef USE_UNWIND\\n/* Number of backtraces to get. */\\n#define BACKTRACE_FRAMES_MAX 32\\n#endif\\n\\n/* Signals to be caught. */\\n#define SIG_CATCH_COUNT 7\\nstatic const int native_sig_catch[SIG_CATCH_COUNT + 1]\\n = { SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV\\n#ifdef SIGSTKFLT\\n , SIGSTKFLT\\n#endif\\n , 0 };\\n\\n/* Maximum value of a caught signal. */\\n#define SIG_NUMBER_MAX 32\\n\\n#if defined(__ANDROID__)\\n#ifndef ucontext_h_seen\\n#define ucontext_h_seen\\n\\n/* stack_t definition */\\n#include \\n\\n#if defined(__arm__)\\n\\n/* Taken from richard.quirk's header file. (Android does not have it) */\\n\\n#if !defined(__BIONIC_HAVE_UCONTEXT_T)\\ntypedef struct ucontext {\\n unsigned long uc_flags;\\n struct ucontext *uc_link;\\n stack_t uc_stack;\\n struct sigcontext uc_mcontext;\\n unsigned long uc_sigmask;\\n} ucontext_t;\\n#endif\\n\\n#elif defined(__aarch64__)\\n\\n#elif defined(__i386__)\\n\\n/* Taken from Google Breakpad. */\\n\\n/* 80-bit floating-point register */\\n/*struct _libc_fpreg {\\n unsigned short significand[4];\\n unsigned short exponent;\\n};*/\\n\\n/* Simple floating-point state, see FNSTENV instruction */\\n/*struct _libc_fpstate {\\n unsigned long cw;\\n unsigned long sw;\\n unsigned long tag;\\n unsigned long ipoff;\\n unsigned long cssel;\\n unsigned long dataoff;\\n unsigned long datasel;\\n struct _libc_fpreg _st[8];\\n unsigned long status;\\n};*/\\n\\n//typedef uint32_t greg_t;\\n\\n/*typedef struct {\\n uint32_t gregs[19];\\n struct _libc_fpstate* fpregs;\\n uint32_t oldmask;\\n uint32_t cr2;\\n} mcontext_t;*/\\n\\n/*enum {\\n REG_GS = 0,\\n REG_FS,\\n REG_ES,\\n REG_DS,\\n REG_EDI,\\n REG_ESI,\\n REG_EBP,\\n REG_ESP,\\n REG_EBX,\\n REG_EDX,\\n REG_ECX,\\n REG_EAX,\\n REG_TRAPNO,\\n REG_ERR,\\n REG_EIP,\\n REG_CS,\\n REG_EFL,\\n REG_UESP,\\n REG_SS,\\n};*/\\n\\n#if !defined(__BIONIC_HAVE_UCONTEXT_T)\\ntypedef struct ucontext {\\n uint32_t uc_flags;\\n struct ucontext* uc_link;\\n stack_t uc_stack;\\n mcontext_t uc_mcontext;\\n} ucontext_t;\\n#endif\\n\\n#elif defined(__mips__)\\n\\n/* Taken from Google Breakpad. */\\n\\ntypedef struct {\\n uint32_t regmask;\\n uint32_t status;\\n uint64_t pc;\\n uint64_t gregs[32];\\n uint64_t fpregs[32];\\n uint32_t acx;\\n uint32_t fpc_csr;\\n uint32_t fpc_eir;\\n uint32_t used_math;\\n uint32_t dsp;\\n uint64_t mdhi;\\n uint64_t mdlo;\\n uint32_t hi1;\\n uint32_t lo1;\\n uint32_t hi2;\\n uint32_t lo2;\\n uint32_t hi3;\\n uint32_t lo3;\\n} mcontext_t;\\n\\n#if !defined(__BIONIC_HAVE_UCONTEXT_T)\\ntypedef struct ucontext {\\n uint32_t uc_flags;\\n struct ucontext* uc_link;\\n stack_t uc_stack;\\n mcontext_t uc_mcontext;\\n} ucontext_t;\\n#endif\\n\\n#else\\n#error \\\"Architecture is not supported (unknown ucontext layout)\\\"\\n#endif\\n\\n#endif\\n\\n#ifdef USE_CORKSCREW\\ntypedef struct map_info_t map_info_t;\\n/* Extracted from Android's include/corkscrew/backtrace.h */\\ntypedef struct {\\n uintptr_t absolute_pc;\\n uintptr_t stack_top;\\n size_t stack_size;\\n} backtrace_frame_t;\\ntypedef struct {\\n uintptr_t relative_pc;\\n uintptr_t relative_symbol_addr;\\n char* map_name;\\n char* symbol_name;\\n char* demangled_name;\\n} backtrace_symbol_t;\\n/* Extracted from Android's libcorkscrew/arch-arm/backtrace-arm.c */\\ntypedef ssize_t (*t_unwind_backtrace_signal_arch)\\n(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt,\\nsize_t ignore_depth, size_t max_depth);\\ntypedef map_info_t* (*t_acquire_my_map_info_list)();\\ntypedef void (*t_release_my_map_info_list)(map_info_t* milist);\\ntypedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace,\\n size_t frames,\\n backtrace_symbol_t* symbols);\\ntypedef void (*t_free_backtrace_symbols)(backtrace_symbol_t* symbols,\\n size_t frames);\\n#endif\\n\\n#endif\\n\\n/* Process-wide crash handler structure. */\\ntypedef struct native_code_global_struct {\\n /* Initialized. */\\n int initialized;\\n\\n /* Lock. */\\n pthread_mutex_t mutex;\\n\\n /* Backup of sigaction. */\\n struct sigaction *sa_old;\\n} native_code_global_struct;\\n#define NATIVE_CODE_GLOBAL_INITIALIZER { 0, PTHREAD_MUTEX_INITIALIZER, NULL }\\n\\n/* Thread-specific crash handler structure. */\\ntypedef struct native_code_handler_struct {\\n /* Restore point context. */\\n sigjmp_buf ctx;\\n int ctx_is_set;\\n int reenter;\\n\\n /* Alternate stack. */\\n char *stack_buffer;\\n size_t stack_buffer_size;\\n stack_t stack_old;\\n\\n /* Signal code and info. */\\n int code;\\n siginfo_t si;\\n ucontext_t uc;\\n\\n /* Uwind context. */\\n#if (defined(USE_CORKSCREW))\\n backtrace_frame_t frames[BACKTRACE_FRAMES_MAX];\\n#elif (defined(USE_UNWIND))\\n uintptr_t frames[BACKTRACE_FRAMES_MAX];\\n#endif\\n#ifdef USE_LIBUNWIND\\n void* uframes[BACKTRACE_FRAMES_MAX];\\n#endif\\n size_t frames_size;\\n size_t frames_skip;\\n\\n /* Custom assertion failures. */\\n const char *expression;\\n const char *file;\\n int line;\\n\\n /* Alarm was fired. */\\n int alarm;\\n} native_code_handler_struct;\\n\\n/* Global crash handler structure. */\\nstatic native_code_global_struct native_code_g =\\n NATIVE_CODE_GLOBAL_INITIALIZER;\\n\\n/* Thread variable holding context. */\\npthread_key_t native_code_thread;\\n\\n#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))\\n/* Unwind callback */\\nstatic _Unwind_Reason_Code\\ncoffeecatch_unwind_callback(struct _Unwind_Context* context, void* arg) {\\n native_code_handler_struct *const s = (native_code_handler_struct*) arg;\\n\\n const uintptr_t ip = _Unwind_GetIP(context);\\n\\n DEBUG(print(\\\"called unwind callback\\\\n\\\"));\\n\\n if (ip != 0x0) {\\n if (s->frames_skip == 0) {\\n s->frames[s->frames_size] = ip;\\n s->frames_size++;\\n } else {\\n s->frames_skip--;\\n }\\n }\\n\\n if (s->frames_size == BACKTRACE_FRAMES_MAX) {\\n return _URC_END_OF_STACK;\\n } else {\\n DEBUG(print(\\\"returned _URC_OK\\\\n\\\"));\\n return _URC_OK;\\n }\\n}\\n#endif\\n\\n/* Use libcorkscrew to get a backtrace inside a signal handler.\\n Will only return a non-zero code on Android >= 4 (with libcorkscrew.so\\n being shipped) */\\n#ifdef USE_CORKSCREW\\nstatic size_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc, \\n backtrace_frame_t* frames,\\n size_t ignore_depth,\\n size_t max_depth) {\\n void *const libcorkscrew = dlopen(\\\"libcorkscrew.so\\\", RTLD_LAZY | RTLD_LOCAL);\\n if (libcorkscrew != NULL) {\\n t_unwind_backtrace_signal_arch unwind_backtrace_signal_arch \\n = (t_unwind_backtrace_signal_arch)\\n dlsym(libcorkscrew, \\\"unwind_backtrace_signal_arch\\\");\\n t_acquire_my_map_info_list acquire_my_map_info_list \\n = (t_acquire_my_map_info_list)\\n dlsym(libcorkscrew, \\\"acquire_my_map_info_list\\\");\\n t_release_my_map_info_list release_my_map_info_list \\n = (t_release_my_map_info_list)\\n dlsym(libcorkscrew, \\\"release_my_map_info_list\\\");\\n if (unwind_backtrace_signal_arch != NULL\\n && acquire_my_map_info_list != NULL\\n && release_my_map_info_list != NULL) {\\n map_info_t*const info = acquire_my_map_info_list();\\n const ssize_t size = \\n unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth,\\n max_depth);\\n release_my_map_info_list(info);\\n return size >= 0 ? size : 0;\\n } else {\\n DEBUG(print(\\\"symbols not found in libcorkscrew.so\\\\n\\\"));\\n }\\n dlclose(libcorkscrew);\\n } else {\\n DEBUG(print(\\\"libcorkscrew.so could not be loaded\\\\n\\\"));\\n }\\n return 0;\\n}\\n\\nstatic int coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace,\\n size_t frames,\\n void (*fun)(void *arg,\\n const backtrace_symbol_t *sym),\\n void *arg) {\\n int success = 0;\\n void *const libcorkscrew = dlopen(\\\"libcorkscrew.so\\\", RTLD_LAZY | RTLD_LOCAL);\\n if (libcorkscrew != NULL) {\\n t_get_backtrace_symbols get_backtrace_symbols \\n = (t_get_backtrace_symbols)\\n dlsym(libcorkscrew, \\\"get_backtrace_symbols\\\");\\n t_free_backtrace_symbols free_backtrace_symbols \\n = (t_free_backtrace_symbols)\\n dlsym(libcorkscrew, \\\"free_backtrace_symbols\\\");\\n if (get_backtrace_symbols != NULL\\n && free_backtrace_symbols != NULL) {\\n backtrace_symbol_t symbols[BACKTRACE_FRAMES_MAX];\\n size_t i;\\n if (frames > BACKTRACE_FRAMES_MAX) {\\n frames = BACKTRACE_FRAMES_MAX;\\n }\\n get_backtrace_symbols(backtrace, frames, symbols);\\n for(i = 0; i < frames; i++) {\\n fun(arg, &symbols[i]);\\n }\\n free_backtrace_symbols(symbols, frames);\\n success = 1;\\n } else {\\n DEBUG(print(\\\"symbols not found in libcorkscrew.so\\\\n\\\"));\\n }\\n dlclose(libcorkscrew);\\n } else {\\n DEBUG(print(\\\"libcorkscrew.so could not be loaded\\\\n\\\"));\\n }\\n return success;\\n}\\n#endif\\n\\n/* Use libunwind to get a backtrace inside a signal handler.\\n Will only return a non-zero code on Android >= 5 (with libunwind.so\\n being shipped) */\\n#ifdef USE_LIBUNWIND\\nstatic ssize_t coffeecatch_unwind_signal(siginfo_t* si, void* sc, \\n void** frames,\\n size_t ignore_depth,\\n size_t max_depth) {\\n void *libunwind = dlopen(\\\"libunwind.so\\\", RTLD_LAZY | RTLD_LOCAL);\\n if (libunwind != NULL) {\\n int (*backtrace)(void **buffer, int size) =\\n dlsym(libunwind, \\\"unw_backtrace\\\");\\n if (backtrace != NULL) {\\n int nb = backtrace(frames, max_depth);\\n if (nb > 0) {\\n }\\n return nb;\\n } else {\\n DEBUG(print(\\\"symbols not found in libunwind.so\\\\n\\\"));\\n }\\n dlclose(libunwind);\\n } else {\\n DEBUG(print(\\\"libunwind.so could not be loaded\\\\n\\\"));\\n }\\n return -1;\\n}\\n#endif\\n\\n/* Call the old handler. */\\nstatic void coffeecatch_call_old_signal_handler(const int code, siginfo_t *const si,\\n void * const sc) {\\n /* Call the \\\"real\\\" Java handler for JIT and internals. */\\n if (code >= 0 && code < SIG_NUMBER_MAX) {\\n if (native_code_g.sa_old[code].sa_sigaction != NULL) {\\n native_code_g.sa_old[code].sa_sigaction(code, si, sc);\\n } else if (native_code_g.sa_old[code].sa_handler != NULL) {\\n native_code_g.sa_old[code].sa_handler(code);\\n }\\n }\\n}\\n\\n/* Unflag \\\"on stack\\\" */\\nstatic void coffeecatch_revert_alternate_stack(void) {\\n#ifndef NO_USE_SIGALTSTACK\\n stack_t ss;\\n if (sigaltstack(NULL, &ss) == 0) {\\n ss.ss_flags &= ~SS_ONSTACK;\\n sigaltstack (&ss, NULL);\\n }\\n#endif\\n}\\n\\n/* Try to jump to userland. */\\nstatic void coffeecatch_try_jump_userland(native_code_handler_struct*\\n const t,\\n const int code,\\n siginfo_t *const si,\\n void * const sc) {\\n (void) si; /* UNUSED */\\n (void) sc; /* UNUSED */\\n\\n /* Valid context ? */\\n if (t != NULL && t->ctx_is_set) {\\n DEBUG(print(\\\"calling siglongjmp()\\\\n\\\"));\\n\\n /* Invalidate the context */\\n t->ctx_is_set = 0;\\n\\n /* We need to revert the alternate stack before jumping. */\\n coffeecatch_revert_alternate_stack();\\n\\n /*\\n * Note on async-signal-safety of siglongjmp() [POSIX] :\\n * \\\"Note that longjmp() and siglongjmp() are not in the list of\\n * async-signal-safe functions. This is because the code executing after\\n * longjmp() and siglongjmp() can call any unsafe functions with the same\\n * danger as calling those unsafe functions directly from the signal\\n * handler. Applications that use longjmp() and siglongjmp() from within\\n * signal handlers require rigorous protection in order to be portable.\\n * Many of the other functions that are excluded from the list are\\n * traditionally implemented using either malloc() or free() functions or\\n * the standard I/O library, both of which traditionally use data\\n * structures in a non-async-signal-safe manner. Since any combination of\\n * different functions using a common data structure can cause\\n * async-signal-safety problems, this volume of POSIX.1-2008 does not\\n * define the behavior when any unsafe function is called in a signal\\n * handler that interrupts an unsafe function.\\\"\\n */\\n siglongjmp(t->ctx, code);\\n }\\n}\\n\\nstatic void coffeecatch_start_alarm(void) {\\n /* Ensure we do not deadlock. Default of ALRM is to die.\\n * (signal() and alarm() are signal-safe) */\\n (void) alarm(30);\\n}\\n\\nstatic void coffeecatch_mark_alarm(native_code_handler_struct *const t) {\\n t->alarm = 1;\\n}\\n\\n/* Copy context infos (signal code, etc.) */\\nstatic void coffeecatch_copy_context(native_code_handler_struct *const t,\\n const int code, siginfo_t *const si,\\n void *const sc) {\\n t->code = code;\\n t->si = *si;\\n if (sc != NULL) {\\n ucontext_t *const uc = (ucontext_t*) sc;\\n t->uc = *uc;\\n } else {\\n memset(&t->uc, 0, sizeof(t->uc));\\n }\\n\\n#ifdef USE_UNWIND\\n /* Frame buffer initial position. */\\n t->frames_size = 0;\\n\\n /* Skip us and the caller. */\\n t->frames_skip = 2;\\n\\n /* Use the corkscrew library to extract the backtrace. */\\n#ifdef USE_CORKSCREW\\n t->frames_size = coffeecatch_backtrace_signal(si, sc, t->frames, 0,\\n BACKTRACE_FRAMES_MAX);\\n#else\\n /* Unwind frames (equivalent to backtrace()) */\\n _Unwind_Backtrace(coffeecatch_unwind_callback, t);\\n#endif\\n\\n#ifdef USE_LIBUNWIND\\n if (t->frames_size == 0) {\\n size_t i;\\n t->frames_size = coffeecatch_unwind_signal(si, sc, t->uframes, 0,\\n BACKTRACE_FRAMES_MAX);\\n for(i = 0 ; i < t->frames_size ; i++) {\\n t->frames[i].absolute_pc = (uintptr_t) t->uframes[i];\\n t->frames[i].stack_top = 0;\\n t->frames[i].stack_size = 0;\\n }\\n }\\n#endif\\n\\n if (t->frames_size != 0) {\\n DEBUG(print(\\\"called _Unwind_Backtrace()\\\\n\\\"));\\n } else {\\n DEBUG(print(\\\"called _Unwind_Backtrace(), but no traces\\\\n\\\"));\\n }\\n#endif\\n}\\n\\n/* Return the thread-specific native_code_handler_struct structure, or\\n * @c null if no such structure is available. */\\nstatic native_code_handler_struct* coffeecatch_get() {\\n return (native_code_handler_struct*)\\n pthread_getspecific(native_code_thread);\\n}\\n\\nint coffeecatch_cancel_pending_alarm() {\\n native_code_handler_struct *const t = coffeecatch_get();\\n if (t != NULL && t->alarm) {\\n t->alarm = 0;\\n /* \\\"If seconds is 0, a pending alarm request, if any, is canceled.\\\" */\\n alarm(0);\\n return 0;\\n }\\n return -1;\\n}\\n\\n/* Internal signal pass-through. Allows to peek the \\\"real\\\" crash before\\n * calling the Java handler. Remember than Java needs many of the signals\\n * (for the JIT, for test-free NullPointerException handling, etc.)\\n * We record the siginfo_t context in this function each time it is being\\n * called, to be able to know what error caused an issue.\\n */\\nstatic void coffeecatch_signal_pass(const int code, siginfo_t *const si,\\n void *const sc) {\\n native_code_handler_struct *t;\\n\\n DEBUG(print(\\\"caught signal\\\\n\\\"));\\n\\n /* Call the \\\"real\\\" Java handler for JIT and internals. */\\n coffeecatch_call_old_signal_handler(code, si, sc);\\n\\n /* Still here ?\\n * FIXME TODO: This is the Dalvik behavior - but is it the SunJVM one ? */\\n\\n /* Ensure we do not deadlock. Default of ALRM is to die.\\n * (signal() and alarm() are signal-safe) */\\n signal(code, SIG_DFL);\\n coffeecatch_start_alarm();\\n\\n /* Available context ? */\\n t = coffeecatch_get();\\n if (t != NULL) {\\n /* An alarm() call was triggered. */\\n coffeecatch_mark_alarm(t);\\n\\n /* Take note of the signal. */\\n coffeecatch_copy_context(t, code, si, sc);\\n\\n /* Back to the future. */\\n coffeecatch_try_jump_userland(t, code, si, sc);\\n }\\n\\n /* Nope. (abort() is signal-safe) */\\n DEBUG(print(\\\"calling abort()\\\\n\\\"));\\n signal(SIGABRT, SIG_DFL);\\n abort();\\n}\\n\\n/* Internal crash handler for abort(). Java calls abort() if its signal handler\\n * could not resolve the signal ; thus calling us through this handler. */\\nstatic void coffeecatch_signal_abort(const int code, siginfo_t *const si,\\n void *const sc) {\\n native_code_handler_struct *t;\\n\\n (void) sc; /* UNUSED */\\n\\n DEBUG(print(\\\"caught abort\\\\n\\\"));\\n\\n /* Ensure we do not deadlock. Default of ALRM is to die.\\n * (signal() and alarm() are signal-safe) */\\n signal(code, SIG_DFL);\\n coffeecatch_start_alarm();\\n\\n /* Available context ? */\\n t = coffeecatch_get();\\n if (t != NULL) {\\n /* An alarm() call was triggered. */\\n coffeecatch_mark_alarm(t);\\n\\n /* Take note (real \\\"abort()\\\") */\\n coffeecatch_copy_context(t, code, si, sc);\\n\\n /* Back to the future. */\\n coffeecatch_try_jump_userland(t, code, si, sc);\\n }\\n\\n /* No such restore point, call old signal handler then. */\\n DEBUG(print(\\\"calling old signal handler\\\\n\\\"));\\n coffeecatch_call_old_signal_handler(code, si, sc);\\n\\n /* Nope. (abort() is signal-safe) */\\n DEBUG(print(\\\"calling abort()\\\\n\\\"));\\n abort();\\n}\\n\\n/* Internal globals initialization. */\\nstatic int coffeecatch_handler_setup_global(void) {\\n if (native_code_g.initialized++ == 0) {\\n size_t i;\\n struct sigaction sa_abort;\\n struct sigaction sa_pass;\\n\\n DEBUG(print(\\\"installing global signal handlers\\\\n\\\"));\\n\\n /* Setup handler structure. */\\n memset(&sa_abort, 0, sizeof(sa_abort));\\n sigemptyset(&sa_abort.sa_mask);\\n sa_abort.sa_sigaction = coffeecatch_signal_abort;\\n sa_abort.sa_flags = SA_SIGINFO | SA_ONSTACK;\\n\\n memset(&sa_pass, 0, sizeof(sa_pass));\\n sigemptyset(&sa_pass.sa_mask);\\n sa_pass.sa_sigaction = coffeecatch_signal_pass;\\n sa_pass.sa_flags = SA_SIGINFO | SA_ONSTACK;\\n\\n /* Allocate */\\n native_code_g.sa_old = calloc(sizeof(struct sigaction), SIG_NUMBER_MAX);\\n if (native_code_g.sa_old == NULL) {\\n return -1;\\n }\\n\\n /* Setup signal handlers for SIGABRT (Java calls abort()) and others. **/\\n for (i = 0; native_sig_catch[i] != 0; i++) {\\n const int sig = native_sig_catch[i];\\n const struct sigaction * const action =\\n sig == SIGABRT ? &sa_abort : &sa_pass;\\n assert(sig < SIG_NUMBER_MAX);\\n if (sigaction(sig, action, &native_code_g.sa_old[sig]) != 0) {\\n return -1;\\n }\\n }\\n\\n /* Initialize thread var. */\\n if (pthread_key_create(&native_code_thread, NULL) != 0) {\\n return -1;\\n }\\n\\n DEBUG(print(\\\"installed global signal handlers\\\\n\\\"));\\n }\\n\\n /* OK. */\\n return 0;\\n}\\n\\n/**\\n * Free a native_code_handler_struct structure.\\n **/\\nstatic int coffeecatch_native_code_handler_struct_free(native_code_handler_struct *const t) {\\n int code = 0;\\n\\n if (t == NULL) {\\n return -1;\\n }\\n\\n#ifndef NO_USE_SIGALTSTACK\\n /* Restore previous alternative stack. */\\n if (t->stack_old.ss_sp != NULL && sigaltstack(&t->stack_old, NULL) != 0) {\\n#ifndef USE_SILENT_SIGALTSTACK\\n code = -1;\\n#endif\\n }\\n#endif\\n\\n /* Free alternative stack */\\n if (t->stack_buffer != NULL) {\\n free(t->stack_buffer);\\n t->stack_buffer = NULL;\\n t->stack_buffer_size = 0;\\n }\\n\\n /* Free structure. */\\n free(t);\\n\\n return code;\\n}\\n\\n/**\\n * Create a native_code_handler_struct structure.\\n **/\\nstatic native_code_handler_struct* coffeecatch_native_code_handler_struct_init(void) {\\n stack_t stack;\\n native_code_handler_struct *const t =\\n calloc(sizeof(native_code_handler_struct), 1);\\n\\n if (t == NULL) {\\n return NULL;\\n }\\n\\n DEBUG(print(\\\"installing thread alternative stack\\\\n\\\"));\\n\\n /* Initialize structure */\\n t->stack_buffer_size = SIG_STACK_BUFFER_SIZE;\\n t->stack_buffer = malloc(t->stack_buffer_size);\\n if (t->stack_buffer == NULL) {\\n coffeecatch_native_code_handler_struct_free(t);\\n return NULL;\\n }\\n\\n /* Setup alternative stack. */\\n memset(&stack, 0, sizeof(stack));\\n stack.ss_sp = t->stack_buffer;\\n stack.ss_size = t->stack_buffer_size;\\n stack.ss_flags = 0;\\n\\n#ifndef NO_USE_SIGALTSTACK\\n /* Install alternative stack. This is thread-safe */\\n if (sigaltstack(&stack, &t->stack_old) != 0) {\\n#ifndef USE_SILENT_SIGALTSTACK\\n coffeecatch_native_code_handler_struct_free(t);\\n return NULL;\\n#endif\\n }\\n#endif\\n\\n return t;\\n}\\n\\n/**\\n * Acquire the crash handler for the current thread.\\n * The coffeecatch_handler_cleanup() must be called to release allocated\\n * resources.\\n **/\\nstatic int coffeecatch_handler_setup(int setup_thread) {\\n int code;\\n\\n DEBUG(print(\\\"setup for a new handler\\\\n\\\"));\\n\\n /* Initialize globals. */\\n if (pthread_mutex_lock(&native_code_g.mutex) != 0) {\\n return -1;\\n }\\n code = coffeecatch_handler_setup_global();\\n if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {\\n return -1;\\n }\\n\\n /* Global initialization failed. */\\n if (code != 0) {\\n return -1;\\n }\\n\\n /* Initialize locals. */\\n if (setup_thread && coffeecatch_get() == NULL) {\\n native_code_handler_struct *const t =\\n coffeecatch_native_code_handler_struct_init();\\n\\n if (t == NULL) {\\n return -1;\\n }\\n\\n DEBUG(print(\\\"installing thread alternative stack\\\\n\\\"));\\n\\n /* Set thread-specific value. */\\n if (pthread_setspecific(native_code_thread, t) != 0) {\\n coffeecatch_native_code_handler_struct_free(t);\\n return -1;\\n }\\n\\n DEBUG(print(\\\"installed thread alternative stack\\\\n\\\"));\\n }\\n\\n /* OK. */\\n return 0;\\n}\\n\\n/**\\n * Release the resources allocated by a previous call to\\n * coffeecatch_handler_setup().\\n * This function must be called as many times as\\n * coffeecatch_handler_setup() was called to fully release allocated\\n * resources.\\n **/\\nstatic int coffeecatch_handler_cleanup() {\\n /* Cleanup locals. */\\n native_code_handler_struct *const t = coffeecatch_get();\\n if (t != NULL) {\\n DEBUG(print(\\\"removing thread alternative stack\\\\n\\\"));\\n\\n /* Erase thread-specific value now (detach). */\\n if (pthread_setspecific(native_code_thread, NULL) != 0) {\\n assert(! \\\"pthread_setspecific() failed\\\");\\n }\\n\\n /* Free handler and reset slternate stack */\\n if (coffeecatch_native_code_handler_struct_free(t) != 0) {\\n return -1;\\n }\\n\\n DEBUG(print(\\\"removed thread alternative stack\\\\n\\\"));\\n }\\n\\n /* Cleanup globals. */\\n if (pthread_mutex_lock(&native_code_g.mutex) != 0) {\\n assert(! \\\"pthread_mutex_lock() failed\\\");\\n }\\n assert(native_code_g.initialized != 0);\\n if (--native_code_g.initialized == 0) {\\n size_t i;\\n\\n DEBUG(print(\\\"removing global signal handlers\\\\n\\\"));\\n\\n /* Restore signal handler. */\\n for(i = 0; native_sig_catch[i] != 0; i++) {\\n const int sig = native_sig_catch[i];\\n assert(sig < SIG_NUMBER_MAX);\\n if (sigaction(sig, &native_code_g.sa_old[sig], NULL) != 0) {\\n return -1;\\n }\\n }\\n\\n /* Free old structure. */\\n free(native_code_g.sa_old);\\n native_code_g.sa_old = NULL;\\n\\n /* Delete thread var. */\\n if (pthread_key_delete(native_code_thread) != 0) {\\n assert(! \\\"pthread_key_delete() failed\\\");\\n }\\n\\n DEBUG(print(\\\"removed global signal handlers\\\\n\\\"));\\n }\\n if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {\\n assert(! \\\"pthread_mutex_unlock() failed\\\");\\n }\\n\\n return 0;\\n}\\n\\n/**\\n * Get the signal associated with the crash.\\n */\\nint coffeecatch_get_signal() {\\n const native_code_handler_struct* const t = coffeecatch_get();\\n if (t != NULL) {\\n return t->code;\\n } else {\\n return -1;\\n }\\n}\\n\\n/* Signal descriptions.\\n See \\n*/\\nstatic const char* coffeecatch_desc_sig(int sig, int code) {\\n switch(sig) {\\n case SIGILL:\\n switch(code) {\\n case ILL_ILLOPC:\\n return \\\"Illegal opcode\\\";\\n case ILL_ILLOPN:\\n return \\\"Illegal operand\\\";\\n case ILL_ILLADR:\\n return \\\"Illegal addressing mode\\\";\\n case ILL_ILLTRP:\\n return \\\"Illegal trap\\\";\\n case ILL_PRVOPC:\\n return \\\"Privileged opcode\\\";\\n case ILL_PRVREG:\\n return \\\"Privileged register\\\";\\n case ILL_COPROC:\\n return \\\"Coprocessor error\\\";\\n case ILL_BADSTK:\\n return \\\"Internal stack error\\\";\\n default:\\n return \\\"Illegal operation\\\";\\n }\\n break;\\n case SIGFPE:\\n switch(code) {\\n case FPE_INTDIV:\\n return \\\"Integer divide by zero\\\";\\n case FPE_INTOVF:\\n return \\\"Integer overflow\\\";\\n case FPE_FLTDIV:\\n return \\\"Floating-point divide by zero\\\";\\n case FPE_FLTOVF:\\n return \\\"Floating-point overflow\\\";\\n case FPE_FLTUND:\\n return \\\"Floating-point underflow\\\";\\n case FPE_FLTRES:\\n return \\\"Floating-point inexact result\\\";\\n case FPE_FLTINV:\\n return \\\"Invalid floating-point operation\\\";\\n case FPE_FLTSUB:\\n return \\\"Subscript out of range\\\";\\n default:\\n return \\\"Floating-point\\\";\\n }\\n break;\\n case SIGSEGV:\\n switch(code) {\\n case SEGV_MAPERR:\\n return \\\"Address not mapped to object\\\";\\n case SEGV_ACCERR:\\n return \\\"Invalid permissions for mapped object\\\";\\n default:\\n return \\\"Segmentation violation\\\";\\n }\\n break;\\n case SIGBUS:\\n switch(code) {\\n case BUS_ADRALN:\\n return \\\"Invalid address alignment\\\";\\n case BUS_ADRERR:\\n return \\\"Nonexistent physical address\\\";\\n case BUS_OBJERR:\\n return \\\"Object-specific hardware error\\\";\\n default:\\n return \\\"Bus error\\\";\\n }\\n break;\\n case SIGTRAP:\\n switch(code) {\\n case TRAP_BRKPT:\\n return \\\"Process breakpoint\\\";\\n case TRAP_TRACE:\\n return \\\"Process trace trap\\\";\\n default:\\n return \\\"Trap\\\";\\n }\\n break;\\n case SIGCHLD:\\n switch(code) {\\n case CLD_EXITED:\\n return \\\"Child has exited\\\";\\n case CLD_KILLED:\\n return \\\"Child has terminated abnormally and did not create a core file\\\";\\n case CLD_DUMPED:\\n return \\\"Child has terminated abnormally and created a core file\\\";\\n case CLD_TRAPPED:\\n return \\\"Traced child has trapped\\\";\\n case CLD_STOPPED:\\n return \\\"Child has stopped\\\";\\n case CLD_CONTINUED:\\n return \\\"Stopped child has continued\\\";\\n default:\\n return \\\"Child\\\";\\n }\\n break;\\n case SIGPOLL:\\n switch(code) {\\n case POLL_IN:\\n return \\\"Data input available\\\";\\n case POLL_OUT:\\n return \\\"Output buffers available\\\";\\n case POLL_MSG:\\n return \\\"Input message available\\\";\\n case POLL_ERR:\\n return \\\"I/O error\\\";\\n case POLL_PRI:\\n return \\\"High priority input available\\\";\\n case POLL_HUP:\\n return \\\"Device disconnected\\\";\\n default:\\n return \\\"Pool\\\";\\n }\\n break;\\n case SIGABRT:\\n return \\\"Process abort signal\\\";\\n case SIGALRM:\\n return \\\"Alarm clock\\\";\\n case SIGCONT:\\n return \\\"Continue executing, if stopped\\\";\\n case SIGHUP:\\n return \\\"Hangup\\\";\\n case SIGINT:\\n return \\\"Terminal interrupt signal\\\";\\n case SIGKILL:\\n return \\\"Kill\\\";\\n case SIGPIPE:\\n return \\\"Write on a pipe with no one to read it\\\";\\n case SIGQUIT:\\n return \\\"Terminal quit signal\\\";\\n case SIGSTOP:\\n return \\\"Stop executing\\\";\\n case SIGTERM:\\n return \\\"Termination signal\\\";\\n case SIGTSTP:\\n return \\\"Terminal stop signal\\\";\\n case SIGTTIN:\\n return \\\"Background process attempting read\\\";\\n case SIGTTOU:\\n return \\\"Background process attempting write\\\";\\n case SIGUSR1:\\n return \\\"User-defined signal 1\\\";\\n case SIGUSR2:\\n return \\\"User-defined signal 2\\\";\\n case SIGPROF:\\n return \\\"Profiling timer expired\\\";\\n case SIGSYS:\\n return \\\"Bad system call\\\";\\n case SIGVTALRM:\\n return \\\"Virtual timer expired\\\";\\n case SIGURG:\\n return \\\"High bandwidth data is available at a socket\\\";\\n case SIGXCPU:\\n return \\\"CPU time limit exceeded\\\";\\n case SIGXFSZ:\\n return \\\"File size limit exceeded\\\";\\n default:\\n switch(code) {\\n case SI_USER:\\n return \\\"Signal sent by kill()\\\";\\n case SI_QUEUE:\\n return \\\"Signal sent by the sigqueue()\\\";\\n case SI_TIMER:\\n return \\\"Signal generated by expiration of a timer set by timer_settime()\\\";\\n case SI_ASYNCIO:\\n return \\\"Signal generated by completion of an asynchronous I/O request\\\";\\n case SI_MESGQ:\\n return\\n \\\"Signal generated by arrival of a message on an empty message queue\\\";\\n default:\\n return \\\"Unknown signal\\\";\\n }\\n break;\\n }\\n}\\n\\n/**\\n * Get the backtrace size. Returns 0 if no backtrace is available.\\n */\\nsize_t coffeecatch_get_backtrace_size(void) {\\n#ifdef USE_UNWIND\\n const native_code_handler_struct* const t = coffeecatch_get();\\n if (t != NULL) {\\n return t->frames_size;\\n } else {\\n return 0;\\n }\\n#else\\n return 0;\\n#endif\\n}\\n\\n/**\\n * Get the th element of the backtrace, or 0 upon error.\\n */\\nuintptr_t coffeecatch_get_backtrace(ssize_t index) {\\n#ifdef USE_UNWIND\\n const native_code_handler_struct* const t = coffeecatch_get();\\n if (t != NULL) {\\n if (index < 0) {\\n index = t->frames_size + index;\\n }\\n if (index >= 0 && (size_t) index < t->frames_size) {\\n#ifdef USE_CORKSCREW\\n return t->frames[index].absolute_pc;\\n#else\\n return t->frames[index];\\n#endif\\n }\\n }\\n#else\\n (void) index;\\n#endif\\n return 0;\\n}\\n\\n/**\\n * Get the program counter, given a pointer to a ucontext_t context.\\n **/\\nstatic uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) {\\n#if (defined(__arm__))\\n return uc->uc_mcontext.arm_pc;\\n#elif defined(__aarch64__)\\n return uc->uc_mcontext.pc;\\n#elif (defined(__x86_64__))\\n return uc->uc_mcontext.gregs[REG_RIP];\\n#elif (defined(__i386))\\n return uc->uc_mcontext.gregs[REG_EIP];\\n#elif (defined (__ppc__)) || (defined (__powerpc__))\\n return uc->uc_mcontext.regs->nip;\\n#elif (defined(__hppa__))\\n return uc->uc_mcontext.sc_iaoq[0] & ~0x3UL;\\n#elif (defined(__sparc__) && defined (__arch64__))\\n return uc->uc_mcontext.mc_gregs[MC_PC];\\n#elif (defined(__sparc__) && !defined (__arch64__))\\n return uc->uc_mcontext.gregs[REG_PC];\\n#elif (defined(__mips__))\\n return uc->uc_mcontext.gregs[31];\\n#else\\n#error \\\"Architecture is unknown, please report me!\\\"\\n#endif\\n}\\n\\n/* Is this module name look like a DLL ?\\n FIXME: find a better way to do that... */\\nstatic int coffeecatch_is_dll(const char *name) {\\n size_t i;\\n for(i = 0; name[i] != '\\\\0'; i++) {\\n if (name[i + 0] == '.' &&\\n name[i + 1] == 's' &&\\n name[i + 2] == 'o' &&\\n ( name[i + 3] == '\\\\0' || name[i + 3] == '.') ) {\\n return 1;\\n }\\n }\\n return 0;\\n}\\n\\n/* Extract a line information on a PC address. */\\nstatic void format_pc_address_cb(uintptr_t pc, \\n void (*fun)(void *arg, const char *module, \\n uintptr_t addr,\\n const char *function,\\n uintptr_t offset), void *arg) {\\n if (pc != 0) {\\n Dl_info info;\\n void * const addr = (void*) pc;\\n /* dladdr() returns 0 on error, and nonzero on success. */\\n if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {\\n const uintptr_t near = (uintptr_t) info.dli_saddr;\\n const uintptr_t offs = pc - near;\\n const uintptr_t addr_rel = pc - (uintptr_t) info.dli_fbase;\\n /* We need the absolute address for the main module (?).\\n TODO FIXME to be investigated. */\\n const uintptr_t addr_to_use = coffeecatch_is_dll(info.dli_fname)\\n ? addr_rel : pc;\\n fun(arg, info.dli_fname, addr_to_use, info.dli_sname, offs);\\n } else {\\n fun(arg, NULL, pc, NULL, 0);\\n }\\n }\\n}\\n\\ntypedef struct t_print_fun {\\n char *buffer;\\n size_t buffer_size;\\n} t_print_fun;\\n\\nstatic void print_fun(void *arg, const char *module, uintptr_t uaddr,\\n const char *function, uintptr_t offset) {\\n t_print_fun *const t = (t_print_fun*) arg;\\n char *const buffer = t->buffer;\\n const size_t buffer_size = t->buffer_size;\\n const void*const addr = (void*) uaddr;\\n if (module == NULL) {\\n snprintf(buffer, buffer_size, \\\"[at %p]\\\", addr);\\n } else if (function != NULL) {\\n snprintf(buffer, buffer_size, \\\"[at %s:%p (%s+0x%x)]\\\", module, addr,\\n function, (int) offset);\\n } else {\\n snprintf(buffer, buffer_size, \\\"[at %s:%p]\\\", module, addr);\\n }\\n}\\n\\n/* Format a line information on a PC address. */\\nstatic void format_pc_address(char *buffer, size_t buffer_size, uintptr_t pc) {\\n t_print_fun t;\\n t.buffer = buffer;\\n t.buffer_size = buffer_size;\\n format_pc_address_cb(pc, print_fun, &t);\\n}\\n\\n/**\\n * Get the full error message associated with the crash.\\n */\\nconst char* coffeecatch_get_message() {\\n const int error = errno;\\n const native_code_handler_struct* const t = coffeecatch_get();\\n\\n /* Found valid handler. */\\n if (t != NULL) {\\n char * const buffer = t->stack_buffer;\\n const size_t buffer_len = t->stack_buffer_size;\\n size_t buffer_offs = 0;\\n\\n const char* const posix_desc =\\n coffeecatch_desc_sig(t->si.si_signo, t->si.si_code);\\n\\n /* Assertion failure ? */\\n if ((t->code == SIGABRT\\n#ifdef __ANDROID__\\n /* See Android BUG #16672:\\n * \\\"C assert() failure causes SIGSEGV when it should cause SIGABRT\\\" */\\n || (t->code == SIGSEGV && (uintptr_t) t->si.si_addr == 0xdeadbaad)\\n#endif\\n ) && t->expression != NULL) {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,\\n \\\"assertion '%s' failed at %s:%d\\\",\\n t->expression, t->file, t->line);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n }\\n /* Signal */\\n else {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, \\\"signal %d\\\",\\n t->si.si_signo);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n\\n /* Description */\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, \\\" (%s)\\\",\\n posix_desc);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n\\n /* Address of faulting instruction */\\n if (t->si.si_signo == SIGILL || t->si.si_signo == SIGSEGV) {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, \\\" at address %p\\\",\\n t->si.si_addr);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n }\\n }\\n\\n /* [POSIX] If non-zero, an errno value associated with this signal,\\n as defined in . */\\n if (t->si.si_errno != 0) {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, \\\": \\\");\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n if (strerror_r(t->si.si_errno, &buffer[buffer_offs],\\n buffer_len - buffer_offs) == 0) {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,\\n \\\"unknown error\\\");\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n }\\n }\\n\\n /* Sending process ID. */\\n if (t->si.si_signo == SIGCHLD && t->si.si_pid != 0) {\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,\\n \\\" (sent by pid %d)\\\", (int) t->si.si_pid);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n }\\n\\n /* Faulting program counter location. */\\n if (coffeecatch_get_pc_from_ucontext(&t->uc) != 0) {\\n const uintptr_t pc = coffeecatch_get_pc_from_ucontext(&t->uc);\\n snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, \\\" \\\");\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n format_pc_address(&buffer[buffer_offs], buffer_len - buffer_offs, pc);\\n buffer_offs += strlen(&buffer[buffer_offs]);\\n }\\n\\n /* Return string. */\\n buffer[buffer_offs] = '\\\\0';\\n return t->stack_buffer;\\n } else {\\n /* Static buffer in case of emergency */\\n static char buffer[256];\\n#ifdef _GNU_SOURCE\\n return strerror_r(error, &buffer[0], sizeof(buffer));\\n#else\\n const int code = strerror_r(error, &buffer[0], sizeof(buffer));\\n errno = error;\\n if (code == 0) {\\n return buffer;\\n } else {\\n return \\\"unknown error during crash handler setup\\\";\\n }\\n#endif\\n }\\n}\\n\\n#if (defined(USE_CORKSCREW))\\ntypedef struct t_coffeecatch_backtrace_symbols_fun {\\n void (*fun)(void *arg, const char *module, uintptr_t addr,\\n const char *function, uintptr_t offset);\\n void *arg;\\n} t_coffeecatch_backtrace_symbols_fun;\\n\\nstatic void coffeecatch_backtrace_symbols_fun(void *arg, const backtrace_symbol_t *sym) {\\n t_coffeecatch_backtrace_symbols_fun *const bt =\\n (t_coffeecatch_backtrace_symbols_fun*) arg;\\n const char *symbol = sym->demangled_name != NULL \\n ? sym->demangled_name : sym->symbol_name;\\n const uintptr_t rel = sym->relative_pc - sym->relative_symbol_addr;\\n bt->fun(bt->arg, sym->map_name, sym->relative_pc, symbol, rel);\\n}\\n#endif\\n\\n/**\\n * Enumerate backtrace information.\\n */\\nvoid coffeecatch_get_backtrace_info(void (*fun)(void *arg,\\n const char *module,\\n uintptr_t addr,\\n const char *function,\\n uintptr_t offset), void *arg) {\\n const native_code_handler_struct* const t = coffeecatch_get();\\n if (t != NULL) {\\n size_t i;\\n#if (defined(USE_CORKSCREW))\\n t_coffeecatch_backtrace_symbols_fun bt;\\n bt.fun = fun;\\n bt.arg = arg;\\n if (coffeecatch_backtrace_symbols(t->frames, t->frames_size,\\n coffeecatch_backtrace_symbols_fun,\\n &bt)) {\\n return;\\n }\\n#endif\\n for(i = 0; i < t->frames_size; i++) {\\n const uintptr_t pc = t->frames[i].absolute_pc;\\n format_pc_address_cb(pc, fun, arg);\\n }\\n }\\n}\\n\\n/**\\n * Returns 1 if we are already inside a coffeecatch block, 0 otherwise.\\n */\\nint coffeecatch_inside() {\\n native_code_handler_struct *const t = coffeecatch_get();\\n if (t != NULL && t->reenter > 0) {\\n t->reenter++;\\n return 1;\\n }\\n return 0;\\n}\\n\\n/**\\n * Calls coffeecatch_handler_setup(1) to setup a crash handler, mark the\\n * context as valid, and return 0 upon success.\\n */\\nint coffeecatch_setup() {\\n if (coffeecatch_handler_setup(1) == 0) {\\n native_code_handler_struct *const t = coffeecatch_get();\\n assert(t != NULL);\\n assert(t->reenter == 0);\\n t->reenter = 1;\\n t->ctx_is_set = 1;\\n return 0;\\n } else {\\n return -1;\\n }\\n}\\n\\n/**\\n * Calls coffeecatch_handler_cleanup()\\n */\\nvoid coffeecatch_cleanup() {\\n native_code_handler_struct *const t = coffeecatch_get();\\n assert(t != NULL);\\n assert(t->reenter > 0);\\n t->reenter--;\\n if (t->reenter == 0) {\\n t->ctx_is_set = 0;\\n coffeecatch_handler_cleanup();\\n }\\n}\\n\\nsigjmp_buf* coffeecatch_get_ctx() {\\n native_code_handler_struct* t = coffeecatch_get();\\n assert(t != NULL);\\n return &t->ctx;\\n}\\n\\nvoid coffeecatch_abort(const char* exp, const char* file, int line) {\\n native_code_handler_struct *const t = coffeecatch_get();\\n if (t != NULL) {\\n t->expression = exp;\\n t->file = file;\\n t->line = line;\\n }\\n abort();\\n}\\n\")\n") diff --git a/cmd/ebitenmobile/coffeecatch.h.go.go b/cmd/ebitenmobile/coffeecatch.h.go.go new file mode 100644 index 000000000..df35a920c --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch.h.go.go @@ -0,0 +1,6 @@ +// Code generated by file2byteslice. DO NOT EDIT. +// (gofmt is fine after generating) + +package main + +var coffeecatch_h_go = []byte("// Code generated by file2byteslice. DO NOT EDIT.\n// (gofmt is fine after generating)\n\n// +build ebitenmobilegobind\n\npackage main\n\nvar coffeecatch_h = []byte(\"/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.\\n * (especially for Android/Dalvik)\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n * See the \\\"License\\\" section below for the licensing terms.\\n *\\n * Description:\\n *\\n * Allows to \\\"gracefully\\\" recover from a signal (segv, sibus...) as if it was\\n * a Java exception. It will not gracefully recover from allocator/mutexes\\n * corruption etc., however, but at least \\\"most\\\" gentle crashes (null pointer\\n * dereferencing, integer division, stack overflow etc.) should be handled\\n * without too much troubles.\\n *\\n * The handler is thread-safe, but client must have exclusive control on the\\n * signal handlers (ie. the library is installing its own signal handlers on\\n * top of the existing ones).\\n *\\n * You must build all your libraries with `-funwind-tables', to get proper\\n * unwinding information on all binaries. On ARM, you may also use the\\n * `--no-merge-exidx-entries` linker switch, to solve certain issues with\\n * unwinding (the switch is possibly not needed anymore).\\n * On Android, this can be achieved by using this line in the Android.mk file\\n * in each library block:\\n * LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries\\n *\\n * Example:\\n *\\n * COFFEE_TRY() {\\n * call_some_native_function()\\n * } COFFEE_CATCH() {\\n * const char*const message = coffeecatch_get_message();\\n * jclass cls = (*env)->FindClass(env, \\\"java/lang/RuntimeException\\\");\\n * (*env)->ThrowNew(env, cls, strdup(message));\\n * } COFFEE_END();\\n *\\n * Implementation notes:\\n *\\n * Currently the library is installing both alternate stack and signal\\n * handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,\\n * SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to\\n * \\\"userland\\\" (compared to signal handler context). As a security, an alarm\\n * is started as soon as a fatal signal is detected (ie. not something the\\n * JVM will handle) to kill the process after a grace period. Be sure your\\n * program will exit quickly after the error is caught, or call alarm(0)\\n * to cancel the pending time-bomb.\\n * The signal handlers had to be written with caution, because the virtual\\n * machine might be using signals (including SEGV) to handle JIT compiler,\\n * and some clever optimizations (such as NullPointerException handling)\\n * We are using several signal-unsafe functions, namely:\\n * - siglongjmp() to return to userland\\n * - pthread_getspecific() to get thread-specific setup\\n *\\n * License:\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n *\\n * Redistribution and use in source and binary forms, with or without\\n * modification, are permitted provided that the following conditions are met:\\n *\\n * 1. Redistributions of source code must retain the above copyright notice, this\\n * list of conditions and the following disclaimer.\\n * 2. Redistributions in binary form must reproduce the above copyright notice,\\n * this list of conditions and the following disclaimer in the documentation\\n * and/or other materials provided with the distribution.\\n *\\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND\\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\n */\\n\\n#ifndef COFFEECATCH_H\\n#define COFFEECATCH_H\\n\\n#include \\n#include \\n\\n#ifdef __cplusplus\\nextern \\\"C\\\" {\\n#endif\\n\\n/**\\n * Setup crash handler to enter in a protected section. If a recognized signal\\n * is received in this section, the execution will be diverted to the\\n * COFFEE_CATCH() block.\\n *\\n * Note: you MUST use the following pattern when using this macro:\\n * COFFEE_TRY() {\\n * .. protected section without exit point\\n * } COFFEE_CATCH() {\\n * .. handler section without exit point\\n * } COFFEE_END();\\n *\\n * You can not exit the protected section block, or the handler section block,\\n * using statements such as \\\"return\\\", because the cleanup code would not be\\n * executed.\\n *\\n * It is advised to enclose this complete try/catch/end block in a dedicated\\n * function declared extern or __attribute__ ((noinline)).\\n *\\n * Example:\\n *\\n * void my_native_function(JNIEnv* env, jobject object, jint *retcode) {\\n * COFFEE_TRY() {\\n * *retcode = call_dangerous_function(env, object);\\n * } COFFEE_CATCH() {\\n * const char*const message = coffeecatch_get_message();\\n * jclass cls = (*env)->FindClass(env, \\\"java/lang/RuntimeException\\\");\\n * (*env)->ThrowNew(env, cls, strdup(message));\\n * *retcode = -1;\\n * } COFFEE_END();\\n * }\\n *\\n * In addition, the following restrictions MUST be followed:\\n * - the function must be declared extern, or with the special attribute\\n * __attribute__ ((noinline)).\\n * - you must not use local variables before the complete try/catch/end block,\\n * or define them as \\\"volatile\\\".\\n * - your function should not ignore the crash silently, as the library will\\n * ensure the process is killed after a grace period (typically 30s) to\\n * prevent any deadlock that may occur if the crash was caught inside a\\n * non-signal-safe function, for example (such as malloc()).\\n *\\nCOFFEE_TRY()\\n **/\\n\\n/**\\n * Declare the signal handler block. This block will be executed if a signal\\n * was received, and recognized, in the previous COFFEE_TRY() {} section.\\n * You may call audit functions in this block, such as coffeecatch_get_signal()\\n * or coffeecatch_get_message().\\n *\\nCOFFEE_CATCH()\\n **/\\n\\n/**\\n * Declare the end of the COFFEE_TRY()/COFFEE_CATCH() section.\\n * Diagnostic functions must not be called beyond this point.\\n *\\nCOFFEE_END()\\n **/\\n\\n/**\\n * Get the signal associated with the crash.\\n * This function can only be called inside a COFFEE_CATCH() block.\\n */\\nextern int coffeecatch_get_signal(void);\\n\\n/**\\n * Get the full error message associated with the crash.\\n * This function can only be called inside a COFFEE_CATCH() block, and the\\n * returned pointer is only valid within this block. (you may want to copy\\n * the string in a static buffer, or use strdup())\\n */\\nconst char* coffeecatch_get_message(void);\\n\\n/**\\n * Raise an abort() signal in the current thread. If the current code section\\n * is protected, the 'exp', 'file' and 'line' information are stored for\\n * further audit.\\n */\\nextern void coffeecatch_abort(const char* exp, const char* file, int line);\\n\\n/**\\n * Assertion check. If the expression is false, an abort() signal is raised\\n * using coffeecatch_abort().\\n */\\n#define coffeecatch_assert(EXP) (void)( (EXP) || (coffeecatch_abort(#EXP, __FILE__, __LINE__), 0) )\\n\\n/**\\n * Get the backtrace size, or 0 upon error.\\n * This function can only be called inside a COFFEE_CATCH() block.\\n */\\nextern size_t coffeecatch_get_backtrace_size(void);\\n\\n/**\\n * Get the backtrace pointer, or 0 upon error.\\n * This function can only be called inside a COFFEE_CATCH() block.\\n */\\nextern uintptr_t coffeecatch_get_backtrace(ssize_t index);\\n\\n/**\\n * Enumerate the backtrace with information.\\n * This function can only be called inside a COFFEE_CATCH() block.\\n */\\nextern void coffeecatch_get_backtrace_info(void (*fun)(void *arg,\\n const char *module,\\n uintptr_t addr,\\n const char *function,\\n uintptr_t offset), void *arg);\\n\\n/**\\n * Cancel any pending alarm() triggered after a signal was caught.\\n * Calling this function is dangerous, because it exposes the process to\\n * a possible deadlock if the signal was caught due to internal low-level\\n * library error (mutex being in a locked state, for example).\\n */\\nextern int coffeecatch_cancel_pending_alarm(void);\\n\\n/** Internal functions & definitions, not to be used directly. **/\\n#include \\nextern int coffeecatch_inside(void);\\nextern int coffeecatch_setup(void);\\nextern sigjmp_buf* coffeecatch_get_ctx(void);\\nextern void coffeecatch_cleanup(void);\\n#define COFFEE_TRY() \\\\\\n if (coffeecatch_inside() || \\\\\\n (coffeecatch_setup() == 0 \\\\\\n && sigsetjmp(*coffeecatch_get_ctx(), 1) == 0))\\n#define COFFEE_CATCH() else\\n#define COFFEE_END() coffeecatch_cleanup()\\n/** End of internal functions & definitions. **/\\n\\n#ifdef __cplusplus\\n}\\n#endif\\n\\n#endif\\n\\n\")\n") diff --git a/cmd/ebitenmobile/coffeecatch/LICENSE b/cmd/ebitenmobile/coffeecatch/LICENSE new file mode 100644 index 000000000..1a44ae19c --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/LICENSE @@ -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. diff --git a/cmd/ebitenmobile/coffeecatch/Makefile b/cmd/ebitenmobile/coffeecatch/Makefile new file mode 100644 index 000000000..645fe27fc --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/Makefile @@ -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 + diff --git a/cmd/ebitenmobile/coffeecatch/README.md b/cmd/ebitenmobile/coffeecatch/README.md new file mode 100644 index 000000000..2d65adaee --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/README.md @@ -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. + diff --git a/cmd/ebitenmobile/coffeecatch/coffeecatch.c b/cmd/ebitenmobile/coffeecatch/coffeecatch.c new file mode 100644 index 000000000..c0938c2b1 --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/coffeecatch.c @@ -0,0 +1,1418 @@ +/* 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. + */ + +#ifdef __ANDROID__ +#define USE_UNWIND +#define USE_CORKSCREW +#define USE_LIBUNWIND +#endif + +/* #undef NO_USE_SIGALTSTACK */ +/* #undef USE_SILENT_SIGALTSTACK */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__ANDROID__) && !defined(__BIONIC_HAVE_UCONTEXT_T) && \ + defined(__arm__) && !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT) +#include +#endif +#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW)) +#include +#endif +#include +#include +#include "coffeecatch.h" + +/*#define NDK_DEBUG 1*/ +#if ( defined(NDK_DEBUG) && ( NDK_DEBUG == 1 ) ) +#define DEBUG(A) do { A; } while(0) +#define FD_ERRNO 2 +static void print(const char *const s) { + size_t count; + for(count = 0; s[count] != '\0'; count++) ; + /* write() is async-signal-safe. */ + (void) write(FD_ERRNO, s, count); +} +#else +#define DEBUG(A) +#endif + +/* Alternative stack size. */ +#define SIG_STACK_BUFFER_SIZE SIGSTKSZ + +#ifdef USE_UNWIND +/* Number of backtraces to get. */ +#define BACKTRACE_FRAMES_MAX 32 +#endif + +/* Signals to be caught. */ +#define SIG_CATCH_COUNT 7 +static const int native_sig_catch[SIG_CATCH_COUNT + 1] + = { SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV +#ifdef SIGSTKFLT + , SIGSTKFLT +#endif + , 0 }; + +/* Maximum value of a caught signal. */ +#define SIG_NUMBER_MAX 32 + +#if defined(__ANDROID__) +#ifndef ucontext_h_seen +#define ucontext_h_seen + +/* stack_t definition */ +#include + +#if defined(__arm__) + +/* Taken from richard.quirk's header file. (Android does not have it) */ + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +typedef struct ucontext { + unsigned long uc_flags; + struct ucontext *uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + unsigned long uc_sigmask; +} ucontext_t; +#endif + +#elif defined(__aarch64__) + +#elif defined(__i386__) + +/* Taken from Google Breakpad. */ + +/* 80-bit floating-point register */ +/*struct _libc_fpreg { + unsigned short significand[4]; + unsigned short exponent; +};*/ + +/* Simple floating-point state, see FNSTENV instruction */ +/*struct _libc_fpstate { + unsigned long cw; + unsigned long sw; + unsigned long tag; + unsigned long ipoff; + unsigned long cssel; + unsigned long dataoff; + unsigned long datasel; + struct _libc_fpreg _st[8]; + unsigned long status; +};*/ + +//typedef uint32_t greg_t; + +/*typedef struct { + uint32_t gregs[19]; + struct _libc_fpstate* fpregs; + uint32_t oldmask; + uint32_t cr2; +} mcontext_t;*/ + +/*enum { + REG_GS = 0, + REG_FS, + REG_ES, + REG_DS, + REG_EDI, + REG_ESI, + REG_EBP, + REG_ESP, + REG_EBX, + REG_EDX, + REG_ECX, + REG_EAX, + REG_TRAPNO, + REG_ERR, + REG_EIP, + REG_CS, + REG_EFL, + REG_UESP, + REG_SS, +};*/ + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; +} ucontext_t; +#endif + +#elif defined(__mips__) + +/* Taken from Google Breakpad. */ + +typedef struct { + uint32_t regmask; + uint32_t status; + uint64_t pc; + uint64_t gregs[32]; + uint64_t fpregs[32]; + uint32_t acx; + uint32_t fpc_csr; + uint32_t fpc_eir; + uint32_t used_math; + uint32_t dsp; + uint64_t mdhi; + uint64_t mdlo; + uint32_t hi1; + uint32_t lo1; + uint32_t hi2; + uint32_t lo2; + uint32_t hi3; + uint32_t lo3; +} mcontext_t; + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; +} ucontext_t; +#endif + +#else +#error "Architecture is not supported (unknown ucontext layout)" +#endif + +#endif + +#ifdef USE_CORKSCREW +typedef struct map_info_t map_info_t; +/* Extracted from Android's include/corkscrew/backtrace.h */ +typedef struct { + uintptr_t absolute_pc; + uintptr_t stack_top; + size_t stack_size; +} backtrace_frame_t; +typedef struct { + uintptr_t relative_pc; + uintptr_t relative_symbol_addr; + char* map_name; + char* symbol_name; + char* demangled_name; +} backtrace_symbol_t; +/* Extracted from Android's libcorkscrew/arch-arm/backtrace-arm.c */ +typedef ssize_t (*t_unwind_backtrace_signal_arch) +(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt, +size_t ignore_depth, size_t max_depth); +typedef map_info_t* (*t_acquire_my_map_info_list)(); +typedef void (*t_release_my_map_info_list)(map_info_t* milist); +typedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace, + size_t frames, + backtrace_symbol_t* symbols); +typedef void (*t_free_backtrace_symbols)(backtrace_symbol_t* symbols, + size_t frames); +#endif + +#endif + +/* Process-wide crash handler structure. */ +typedef struct native_code_global_struct { + /* Initialized. */ + int initialized; + + /* Lock. */ + pthread_mutex_t mutex; + + /* Backup of sigaction. */ + struct sigaction *sa_old; +} native_code_global_struct; +#define NATIVE_CODE_GLOBAL_INITIALIZER { 0, PTHREAD_MUTEX_INITIALIZER, NULL } + +/* Thread-specific crash handler structure. */ +typedef struct native_code_handler_struct { + /* Restore point context. */ + sigjmp_buf ctx; + int ctx_is_set; + int reenter; + + /* Alternate stack. */ + char *stack_buffer; + size_t stack_buffer_size; + stack_t stack_old; + + /* Signal code and info. */ + int code; + siginfo_t si; + ucontext_t uc; + + /* Uwind context. */ +#if (defined(USE_CORKSCREW)) + backtrace_frame_t frames[BACKTRACE_FRAMES_MAX]; +#elif (defined(USE_UNWIND)) + uintptr_t frames[BACKTRACE_FRAMES_MAX]; +#endif +#ifdef USE_LIBUNWIND + void* uframes[BACKTRACE_FRAMES_MAX]; +#endif + size_t frames_size; + size_t frames_skip; + + /* Custom assertion failures. */ + const char *expression; + const char *file; + int line; + + /* Alarm was fired. */ + int alarm; +} native_code_handler_struct; + +/* Global crash handler structure. */ +static native_code_global_struct native_code_g = + NATIVE_CODE_GLOBAL_INITIALIZER; + +/* Thread variable holding context. */ +pthread_key_t native_code_thread; + +#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW)) +/* Unwind callback */ +static _Unwind_Reason_Code +coffeecatch_unwind_callback(struct _Unwind_Context* context, void* arg) { + native_code_handler_struct *const s = (native_code_handler_struct*) arg; + + const uintptr_t ip = _Unwind_GetIP(context); + + DEBUG(print("called unwind callback\n")); + + if (ip != 0x0) { + if (s->frames_skip == 0) { + s->frames[s->frames_size] = ip; + s->frames_size++; + } else { + s->frames_skip--; + } + } + + if (s->frames_size == BACKTRACE_FRAMES_MAX) { + return _URC_END_OF_STACK; + } else { + DEBUG(print("returned _URC_OK\n")); + return _URC_OK; + } +} +#endif + +/* Use libcorkscrew to get a backtrace inside a signal handler. + Will only return a non-zero code on Android >= 4 (with libcorkscrew.so + being shipped) */ +#ifdef USE_CORKSCREW +static size_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc, + backtrace_frame_t* frames, + size_t ignore_depth, + size_t max_depth) { + void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL); + if (libcorkscrew != NULL) { + t_unwind_backtrace_signal_arch unwind_backtrace_signal_arch + = (t_unwind_backtrace_signal_arch) + dlsym(libcorkscrew, "unwind_backtrace_signal_arch"); + t_acquire_my_map_info_list acquire_my_map_info_list + = (t_acquire_my_map_info_list) + dlsym(libcorkscrew, "acquire_my_map_info_list"); + t_release_my_map_info_list release_my_map_info_list + = (t_release_my_map_info_list) + dlsym(libcorkscrew, "release_my_map_info_list"); + if (unwind_backtrace_signal_arch != NULL + && acquire_my_map_info_list != NULL + && release_my_map_info_list != NULL) { + map_info_t*const info = acquire_my_map_info_list(); + const ssize_t size = + unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth, + max_depth); + release_my_map_info_list(info); + return size >= 0 ? size : 0; + } else { + DEBUG(print("symbols not found in libcorkscrew.so\n")); + } + dlclose(libcorkscrew); + } else { + DEBUG(print("libcorkscrew.so could not be loaded\n")); + } + return 0; +} + +static int coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace, + size_t frames, + void (*fun)(void *arg, + const backtrace_symbol_t *sym), + void *arg) { + int success = 0; + void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL); + if (libcorkscrew != NULL) { + t_get_backtrace_symbols get_backtrace_symbols + = (t_get_backtrace_symbols) + dlsym(libcorkscrew, "get_backtrace_symbols"); + t_free_backtrace_symbols free_backtrace_symbols + = (t_free_backtrace_symbols) + dlsym(libcorkscrew, "free_backtrace_symbols"); + if (get_backtrace_symbols != NULL + && free_backtrace_symbols != NULL) { + backtrace_symbol_t symbols[BACKTRACE_FRAMES_MAX]; + size_t i; + if (frames > BACKTRACE_FRAMES_MAX) { + frames = BACKTRACE_FRAMES_MAX; + } + get_backtrace_symbols(backtrace, frames, symbols); + for(i = 0; i < frames; i++) { + fun(arg, &symbols[i]); + } + free_backtrace_symbols(symbols, frames); + success = 1; + } else { + DEBUG(print("symbols not found in libcorkscrew.so\n")); + } + dlclose(libcorkscrew); + } else { + DEBUG(print("libcorkscrew.so could not be loaded\n")); + } + return success; +} +#endif + +/* Use libunwind to get a backtrace inside a signal handler. + Will only return a non-zero code on Android >= 5 (with libunwind.so + being shipped) */ +#ifdef USE_LIBUNWIND +static ssize_t coffeecatch_unwind_signal(siginfo_t* si, void* sc, + void** frames, + size_t ignore_depth, + size_t max_depth) { + void *libunwind = dlopen("libunwind.so", RTLD_LAZY | RTLD_LOCAL); + if (libunwind != NULL) { + int (*backtrace)(void **buffer, int size) = + dlsym(libunwind, "unw_backtrace"); + if (backtrace != NULL) { + int nb = backtrace(frames, max_depth); + if (nb > 0) { + } + return nb; + } else { + DEBUG(print("symbols not found in libunwind.so\n")); + } + dlclose(libunwind); + } else { + DEBUG(print("libunwind.so could not be loaded\n")); + } + return -1; +} +#endif + +/* Call the old handler. */ +static void coffeecatch_call_old_signal_handler(const int code, siginfo_t *const si, + void * const sc) { + /* Call the "real" Java handler for JIT and internals. */ + if (code >= 0 && code < SIG_NUMBER_MAX) { + if (native_code_g.sa_old[code].sa_sigaction != NULL) { + native_code_g.sa_old[code].sa_sigaction(code, si, sc); + } else if (native_code_g.sa_old[code].sa_handler != NULL) { + native_code_g.sa_old[code].sa_handler(code); + } + } +} + +/* Unflag "on stack" */ +static void coffeecatch_revert_alternate_stack(void) { +#ifndef NO_USE_SIGALTSTACK + stack_t ss; + if (sigaltstack(NULL, &ss) == 0) { + ss.ss_flags &= ~SS_ONSTACK; + sigaltstack (&ss, NULL); + } +#endif +} + +/* Try to jump to userland. */ +static void coffeecatch_try_jump_userland(native_code_handler_struct* + const t, + const int code, + siginfo_t *const si, + void * const sc) { + (void) si; /* UNUSED */ + (void) sc; /* UNUSED */ + + /* Valid context ? */ + if (t != NULL && t->ctx_is_set) { + DEBUG(print("calling siglongjmp()\n")); + + /* Invalidate the context */ + t->ctx_is_set = 0; + + /* We need to revert the alternate stack before jumping. */ + coffeecatch_revert_alternate_stack(); + + /* + * Note on async-signal-safety of siglongjmp() [POSIX] : + * "Note that longjmp() and siglongjmp() are not in the list of + * async-signal-safe functions. This is because the code executing after + * longjmp() and siglongjmp() can call any unsafe functions with the same + * danger as calling those unsafe functions directly from the signal + * handler. Applications that use longjmp() and siglongjmp() from within + * signal handlers require rigorous protection in order to be portable. + * Many of the other functions that are excluded from the list are + * traditionally implemented using either malloc() or free() functions or + * the standard I/O library, both of which traditionally use data + * structures in a non-async-signal-safe manner. Since any combination of + * different functions using a common data structure can cause + * async-signal-safety problems, this volume of POSIX.1-2008 does not + * define the behavior when any unsafe function is called in a signal + * handler that interrupts an unsafe function." + */ + siglongjmp(t->ctx, code); + } +} + +static void coffeecatch_start_alarm(void) { + /* Ensure we do not deadlock. Default of ALRM is to die. + * (signal() and alarm() are signal-safe) */ + (void) alarm(30); +} + +static void coffeecatch_mark_alarm(native_code_handler_struct *const t) { + t->alarm = 1; +} + +/* Copy context infos (signal code, etc.) */ +static void coffeecatch_copy_context(native_code_handler_struct *const t, + const int code, siginfo_t *const si, + void *const sc) { + t->code = code; + t->si = *si; + if (sc != NULL) { + ucontext_t *const uc = (ucontext_t*) sc; + t->uc = *uc; + } else { + memset(&t->uc, 0, sizeof(t->uc)); + } + +#ifdef USE_UNWIND + /* Frame buffer initial position. */ + t->frames_size = 0; + + /* Skip us and the caller. */ + t->frames_skip = 2; + + /* Use the corkscrew library to extract the backtrace. */ +#ifdef USE_CORKSCREW + t->frames_size = coffeecatch_backtrace_signal(si, sc, t->frames, 0, + BACKTRACE_FRAMES_MAX); +#else + /* Unwind frames (equivalent to backtrace()) */ + _Unwind_Backtrace(coffeecatch_unwind_callback, t); +#endif + +#ifdef USE_LIBUNWIND + if (t->frames_size == 0) { + size_t i; + t->frames_size = coffeecatch_unwind_signal(si, sc, t->uframes, 0, + BACKTRACE_FRAMES_MAX); + for(i = 0 ; i < t->frames_size ; i++) { + t->frames[i].absolute_pc = (uintptr_t) t->uframes[i]; + t->frames[i].stack_top = 0; + t->frames[i].stack_size = 0; + } + } +#endif + + if (t->frames_size != 0) { + DEBUG(print("called _Unwind_Backtrace()\n")); + } else { + DEBUG(print("called _Unwind_Backtrace(), but no traces\n")); + } +#endif +} + +/* Return the thread-specific native_code_handler_struct structure, or + * @c null if no such structure is available. */ +static native_code_handler_struct* coffeecatch_get() { + return (native_code_handler_struct*) + pthread_getspecific(native_code_thread); +} + +int coffeecatch_cancel_pending_alarm() { + native_code_handler_struct *const t = coffeecatch_get(); + if (t != NULL && t->alarm) { + t->alarm = 0; + /* "If seconds is 0, a pending alarm request, if any, is canceled." */ + alarm(0); + return 0; + } + return -1; +} + +/* Internal signal pass-through. Allows to peek the "real" crash before + * calling the Java handler. Remember than Java needs many of the signals + * (for the JIT, for test-free NullPointerException handling, etc.) + * We record the siginfo_t context in this function each time it is being + * called, to be able to know what error caused an issue. + */ +static void coffeecatch_signal_pass(const int code, siginfo_t *const si, + void *const sc) { + native_code_handler_struct *t; + + DEBUG(print("caught signal\n")); + + /* Call the "real" Java handler for JIT and internals. */ + coffeecatch_call_old_signal_handler(code, si, sc); + + /* Still here ? + * FIXME TODO: This is the Dalvik behavior - but is it the SunJVM one ? */ + + /* Ensure we do not deadlock. Default of ALRM is to die. + * (signal() and alarm() are signal-safe) */ + signal(code, SIG_DFL); + coffeecatch_start_alarm(); + + /* Available context ? */ + t = coffeecatch_get(); + if (t != NULL) { + /* An alarm() call was triggered. */ + coffeecatch_mark_alarm(t); + + /* Take note of the signal. */ + coffeecatch_copy_context(t, code, si, sc); + + /* Back to the future. */ + coffeecatch_try_jump_userland(t, code, si, sc); + } + + /* Nope. (abort() is signal-safe) */ + DEBUG(print("calling abort()\n")); + signal(SIGABRT, SIG_DFL); + abort(); +} + +/* Internal crash handler for abort(). Java calls abort() if its signal handler + * could not resolve the signal ; thus calling us through this handler. */ +static void coffeecatch_signal_abort(const int code, siginfo_t *const si, + void *const sc) { + native_code_handler_struct *t; + + (void) sc; /* UNUSED */ + + DEBUG(print("caught abort\n")); + + /* Ensure we do not deadlock. Default of ALRM is to die. + * (signal() and alarm() are signal-safe) */ + signal(code, SIG_DFL); + coffeecatch_start_alarm(); + + /* Available context ? */ + t = coffeecatch_get(); + if (t != NULL) { + /* An alarm() call was triggered. */ + coffeecatch_mark_alarm(t); + + /* Take note (real "abort()") */ + coffeecatch_copy_context(t, code, si, sc); + + /* Back to the future. */ + coffeecatch_try_jump_userland(t, code, si, sc); + } + + /* No such restore point, call old signal handler then. */ + DEBUG(print("calling old signal handler\n")); + coffeecatch_call_old_signal_handler(code, si, sc); + + /* Nope. (abort() is signal-safe) */ + DEBUG(print("calling abort()\n")); + abort(); +} + +/* Internal globals initialization. */ +static int coffeecatch_handler_setup_global(void) { + if (native_code_g.initialized++ == 0) { + size_t i; + struct sigaction sa_abort; + struct sigaction sa_pass; + + DEBUG(print("installing global signal handlers\n")); + + /* Setup handler structure. */ + memset(&sa_abort, 0, sizeof(sa_abort)); + sigemptyset(&sa_abort.sa_mask); + sa_abort.sa_sigaction = coffeecatch_signal_abort; + sa_abort.sa_flags = SA_SIGINFO | SA_ONSTACK; + + memset(&sa_pass, 0, sizeof(sa_pass)); + sigemptyset(&sa_pass.sa_mask); + sa_pass.sa_sigaction = coffeecatch_signal_pass; + sa_pass.sa_flags = SA_SIGINFO | SA_ONSTACK; + + /* Allocate */ + native_code_g.sa_old = calloc(sizeof(struct sigaction), SIG_NUMBER_MAX); + if (native_code_g.sa_old == NULL) { + return -1; + } + + /* Setup signal handlers for SIGABRT (Java calls abort()) and others. **/ + for (i = 0; native_sig_catch[i] != 0; i++) { + const int sig = native_sig_catch[i]; + const struct sigaction * const action = + sig == SIGABRT ? &sa_abort : &sa_pass; + assert(sig < SIG_NUMBER_MAX); + if (sigaction(sig, action, &native_code_g.sa_old[sig]) != 0) { + return -1; + } + } + + /* Initialize thread var. */ + if (pthread_key_create(&native_code_thread, NULL) != 0) { + return -1; + } + + DEBUG(print("installed global signal handlers\n")); + } + + /* OK. */ + return 0; +} + +/** + * Free a native_code_handler_struct structure. + **/ +static int coffeecatch_native_code_handler_struct_free(native_code_handler_struct *const t) { + int code = 0; + + if (t == NULL) { + return -1; + } + +#ifndef NO_USE_SIGALTSTACK + /* Restore previous alternative stack. */ + if (t->stack_old.ss_sp != NULL && sigaltstack(&t->stack_old, NULL) != 0) { +#ifndef USE_SILENT_SIGALTSTACK + code = -1; +#endif + } +#endif + + /* Free alternative stack */ + if (t->stack_buffer != NULL) { + free(t->stack_buffer); + t->stack_buffer = NULL; + t->stack_buffer_size = 0; + } + + /* Free structure. */ + free(t); + + return code; +} + +/** + * Create a native_code_handler_struct structure. + **/ +static native_code_handler_struct* coffeecatch_native_code_handler_struct_init(void) { + stack_t stack; + native_code_handler_struct *const t = + calloc(sizeof(native_code_handler_struct), 1); + + if (t == NULL) { + return NULL; + } + + DEBUG(print("installing thread alternative stack\n")); + + /* Initialize structure */ + t->stack_buffer_size = SIG_STACK_BUFFER_SIZE; + t->stack_buffer = malloc(t->stack_buffer_size); + if (t->stack_buffer == NULL) { + coffeecatch_native_code_handler_struct_free(t); + return NULL; + } + + /* Setup alternative stack. */ + memset(&stack, 0, sizeof(stack)); + stack.ss_sp = t->stack_buffer; + stack.ss_size = t->stack_buffer_size; + stack.ss_flags = 0; + +#ifndef NO_USE_SIGALTSTACK + /* Install alternative stack. This is thread-safe */ + if (sigaltstack(&stack, &t->stack_old) != 0) { +#ifndef USE_SILENT_SIGALTSTACK + coffeecatch_native_code_handler_struct_free(t); + return NULL; +#endif + } +#endif + + return t; +} + +/** + * Acquire the crash handler for the current thread. + * The coffeecatch_handler_cleanup() must be called to release allocated + * resources. + **/ +static int coffeecatch_handler_setup(int setup_thread) { + int code; + + DEBUG(print("setup for a new handler\n")); + + /* Initialize globals. */ + if (pthread_mutex_lock(&native_code_g.mutex) != 0) { + return -1; + } + code = coffeecatch_handler_setup_global(); + if (pthread_mutex_unlock(&native_code_g.mutex) != 0) { + return -1; + } + + /* Global initialization failed. */ + if (code != 0) { + return -1; + } + + /* Initialize locals. */ + if (setup_thread && coffeecatch_get() == NULL) { + native_code_handler_struct *const t = + coffeecatch_native_code_handler_struct_init(); + + if (t == NULL) { + return -1; + } + + DEBUG(print("installing thread alternative stack\n")); + + /* Set thread-specific value. */ + if (pthread_setspecific(native_code_thread, t) != 0) { + coffeecatch_native_code_handler_struct_free(t); + return -1; + } + + DEBUG(print("installed thread alternative stack\n")); + } + + /* OK. */ + return 0; +} + +/** + * Release the resources allocated by a previous call to + * coffeecatch_handler_setup(). + * This function must be called as many times as + * coffeecatch_handler_setup() was called to fully release allocated + * resources. + **/ +static int coffeecatch_handler_cleanup() { + /* Cleanup locals. */ + native_code_handler_struct *const t = coffeecatch_get(); + if (t != NULL) { + DEBUG(print("removing thread alternative stack\n")); + + /* Erase thread-specific value now (detach). */ + if (pthread_setspecific(native_code_thread, NULL) != 0) { + assert(! "pthread_setspecific() failed"); + } + + /* Free handler and reset slternate stack */ + if (coffeecatch_native_code_handler_struct_free(t) != 0) { + return -1; + } + + DEBUG(print("removed thread alternative stack\n")); + } + + /* Cleanup globals. */ + if (pthread_mutex_lock(&native_code_g.mutex) != 0) { + assert(! "pthread_mutex_lock() failed"); + } + assert(native_code_g.initialized != 0); + if (--native_code_g.initialized == 0) { + size_t i; + + DEBUG(print("removing global signal handlers\n")); + + /* Restore signal handler. */ + for(i = 0; native_sig_catch[i] != 0; i++) { + const int sig = native_sig_catch[i]; + assert(sig < SIG_NUMBER_MAX); + if (sigaction(sig, &native_code_g.sa_old[sig], NULL) != 0) { + return -1; + } + } + + /* Free old structure. */ + free(native_code_g.sa_old); + native_code_g.sa_old = NULL; + + /* Delete thread var. */ + if (pthread_key_delete(native_code_thread) != 0) { + assert(! "pthread_key_delete() failed"); + } + + DEBUG(print("removed global signal handlers\n")); + } + if (pthread_mutex_unlock(&native_code_g.mutex) != 0) { + assert(! "pthread_mutex_unlock() failed"); + } + + return 0; +} + +/** + * Get the signal associated with the crash. + */ +int coffeecatch_get_signal() { + const native_code_handler_struct* const t = coffeecatch_get(); + if (t != NULL) { + return t->code; + } else { + return -1; + } +} + +/* Signal descriptions. + See +*/ +static const char* coffeecatch_desc_sig(int sig, int code) { + switch(sig) { + case SIGILL: + switch(code) { + case ILL_ILLOPC: + return "Illegal opcode"; + case ILL_ILLOPN: + return "Illegal operand"; + case ILL_ILLADR: + return "Illegal addressing mode"; + case ILL_ILLTRP: + return "Illegal trap"; + case ILL_PRVOPC: + return "Privileged opcode"; + case ILL_PRVREG: + return "Privileged register"; + case ILL_COPROC: + return "Coprocessor error"; + case ILL_BADSTK: + return "Internal stack error"; + default: + return "Illegal operation"; + } + break; + case SIGFPE: + switch(code) { + case FPE_INTDIV: + return "Integer divide by zero"; + case FPE_INTOVF: + return "Integer overflow"; + case FPE_FLTDIV: + return "Floating-point divide by zero"; + case FPE_FLTOVF: + return "Floating-point overflow"; + case FPE_FLTUND: + return "Floating-point underflow"; + case FPE_FLTRES: + return "Floating-point inexact result"; + case FPE_FLTINV: + return "Invalid floating-point operation"; + case FPE_FLTSUB: + return "Subscript out of range"; + default: + return "Floating-point"; + } + break; + case SIGSEGV: + switch(code) { + case SEGV_MAPERR: + return "Address not mapped to object"; + case SEGV_ACCERR: + return "Invalid permissions for mapped object"; + default: + return "Segmentation violation"; + } + break; + case SIGBUS: + switch(code) { + case BUS_ADRALN: + return "Invalid address alignment"; + case BUS_ADRERR: + return "Nonexistent physical address"; + case BUS_OBJERR: + return "Object-specific hardware error"; + default: + return "Bus error"; + } + break; + case SIGTRAP: + switch(code) { + case TRAP_BRKPT: + return "Process breakpoint"; + case TRAP_TRACE: + return "Process trace trap"; + default: + return "Trap"; + } + break; + case SIGCHLD: + switch(code) { + case CLD_EXITED: + return "Child has exited"; + case CLD_KILLED: + return "Child has terminated abnormally and did not create a core file"; + case CLD_DUMPED: + return "Child has terminated abnormally and created a core file"; + case CLD_TRAPPED: + return "Traced child has trapped"; + case CLD_STOPPED: + return "Child has stopped"; + case CLD_CONTINUED: + return "Stopped child has continued"; + default: + return "Child"; + } + break; + case SIGPOLL: + switch(code) { + case POLL_IN: + return "Data input available"; + case POLL_OUT: + return "Output buffers available"; + case POLL_MSG: + return "Input message available"; + case POLL_ERR: + return "I/O error"; + case POLL_PRI: + return "High priority input available"; + case POLL_HUP: + return "Device disconnected"; + default: + return "Pool"; + } + break; + case SIGABRT: + return "Process abort signal"; + case SIGALRM: + return "Alarm clock"; + case SIGCONT: + return "Continue executing, if stopped"; + case SIGHUP: + return "Hangup"; + case SIGINT: + return "Terminal interrupt signal"; + case SIGKILL: + return "Kill"; + case SIGPIPE: + return "Write on a pipe with no one to read it"; + case SIGQUIT: + return "Terminal quit signal"; + case SIGSTOP: + return "Stop executing"; + case SIGTERM: + return "Termination signal"; + case SIGTSTP: + return "Terminal stop signal"; + case SIGTTIN: + return "Background process attempting read"; + case SIGTTOU: + return "Background process attempting write"; + case SIGUSR1: + return "User-defined signal 1"; + case SIGUSR2: + return "User-defined signal 2"; + case SIGPROF: + return "Profiling timer expired"; + case SIGSYS: + return "Bad system call"; + case SIGVTALRM: + return "Virtual timer expired"; + case SIGURG: + return "High bandwidth data is available at a socket"; + case SIGXCPU: + return "CPU time limit exceeded"; + case SIGXFSZ: + return "File size limit exceeded"; + default: + switch(code) { + case SI_USER: + return "Signal sent by kill()"; + case SI_QUEUE: + return "Signal sent by the sigqueue()"; + case SI_TIMER: + return "Signal generated by expiration of a timer set by timer_settime()"; + case SI_ASYNCIO: + return "Signal generated by completion of an asynchronous I/O request"; + case SI_MESGQ: + return + "Signal generated by arrival of a message on an empty message queue"; + default: + return "Unknown signal"; + } + break; + } +} + +/** + * Get the backtrace size. Returns 0 if no backtrace is available. + */ +size_t coffeecatch_get_backtrace_size(void) { +#ifdef USE_UNWIND + const native_code_handler_struct* const t = coffeecatch_get(); + if (t != NULL) { + return t->frames_size; + } else { + return 0; + } +#else + return 0; +#endif +} + +/** + * Get the th element of the backtrace, or 0 upon error. + */ +uintptr_t coffeecatch_get_backtrace(ssize_t index) { +#ifdef USE_UNWIND + const native_code_handler_struct* const t = coffeecatch_get(); + if (t != NULL) { + if (index < 0) { + index = t->frames_size + index; + } + if (index >= 0 && (size_t) index < t->frames_size) { +#ifdef USE_CORKSCREW + return t->frames[index].absolute_pc; +#else + return t->frames[index]; +#endif + } + } +#else + (void) index; +#endif + return 0; +} + +/** + * Get the program counter, given a pointer to a ucontext_t context. + **/ +static uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) { +#if (defined(__arm__)) + return uc->uc_mcontext.arm_pc; +#elif defined(__aarch64__) + return uc->uc_mcontext.pc; +#elif (defined(__x86_64__)) + return uc->uc_mcontext.gregs[REG_RIP]; +#elif (defined(__i386)) + return uc->uc_mcontext.gregs[REG_EIP]; +#elif (defined (__ppc__)) || (defined (__powerpc__)) + return uc->uc_mcontext.regs->nip; +#elif (defined(__hppa__)) + return uc->uc_mcontext.sc_iaoq[0] & ~0x3UL; +#elif (defined(__sparc__) && defined (__arch64__)) + return uc->uc_mcontext.mc_gregs[MC_PC]; +#elif (defined(__sparc__) && !defined (__arch64__)) + return uc->uc_mcontext.gregs[REG_PC]; +#elif (defined(__mips__)) + return uc->uc_mcontext.gregs[31]; +#else +#error "Architecture is unknown, please report me!" +#endif +} + +/* Is this module name look like a DLL ? + FIXME: find a better way to do that... */ +static int coffeecatch_is_dll(const char *name) { + size_t i; + for(i = 0; name[i] != '\0'; i++) { + if (name[i + 0] == '.' && + name[i + 1] == 's' && + name[i + 2] == 'o' && + ( name[i + 3] == '\0' || name[i + 3] == '.') ) { + return 1; + } + } + return 0; +} + +/* Extract a line information on a PC address. */ +static void format_pc_address_cb(uintptr_t pc, + void (*fun)(void *arg, const char *module, + uintptr_t addr, + const char *function, + uintptr_t offset), void *arg) { + if (pc != 0) { + Dl_info info; + void * const addr = (void*) pc; + /* dladdr() returns 0 on error, and nonzero on success. */ + if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) { + const uintptr_t near = (uintptr_t) info.dli_saddr; + const uintptr_t offs = pc - near; + const uintptr_t addr_rel = pc - (uintptr_t) info.dli_fbase; + /* We need the absolute address for the main module (?). + TODO FIXME to be investigated. */ + const uintptr_t addr_to_use = coffeecatch_is_dll(info.dli_fname) + ? addr_rel : pc; + fun(arg, info.dli_fname, addr_to_use, info.dli_sname, offs); + } else { + fun(arg, NULL, pc, NULL, 0); + } + } +} + +typedef struct t_print_fun { + char *buffer; + size_t buffer_size; +} t_print_fun; + +static void print_fun(void *arg, const char *module, uintptr_t uaddr, + const char *function, uintptr_t offset) { + t_print_fun *const t = (t_print_fun*) arg; + char *const buffer = t->buffer; + const size_t buffer_size = t->buffer_size; + const void*const addr = (void*) uaddr; + if (module == NULL) { + snprintf(buffer, buffer_size, "[at %p]", addr); + } else if (function != NULL) { + snprintf(buffer, buffer_size, "[at %s:%p (%s+0x%x)]", module, addr, + function, (int) offset); + } else { + snprintf(buffer, buffer_size, "[at %s:%p]", module, addr); + } +} + +/* Format a line information on a PC address. */ +static void format_pc_address(char *buffer, size_t buffer_size, uintptr_t pc) { + t_print_fun t; + t.buffer = buffer; + t.buffer_size = buffer_size; + format_pc_address_cb(pc, print_fun, &t); +} + +/** + * Get the full error message associated with the crash. + */ +const char* coffeecatch_get_message() { + const int error = errno; + const native_code_handler_struct* const t = coffeecatch_get(); + + /* Found valid handler. */ + if (t != NULL) { + char * const buffer = t->stack_buffer; + const size_t buffer_len = t->stack_buffer_size; + size_t buffer_offs = 0; + + const char* const posix_desc = + coffeecatch_desc_sig(t->si.si_signo, t->si.si_code); + + /* Assertion failure ? */ + if ((t->code == SIGABRT +#ifdef __ANDROID__ + /* See Android BUG #16672: + * "C assert() failure causes SIGSEGV when it should cause SIGABRT" */ + || (t->code == SIGSEGV && (uintptr_t) t->si.si_addr == 0xdeadbaad) +#endif + ) && t->expression != NULL) { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, + "assertion '%s' failed at %s:%d", + t->expression, t->file, t->line); + buffer_offs += strlen(&buffer[buffer_offs]); + } + /* Signal */ + else { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, "signal %d", + t->si.si_signo); + buffer_offs += strlen(&buffer[buffer_offs]); + + /* Description */ + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " (%s)", + posix_desc); + buffer_offs += strlen(&buffer[buffer_offs]); + + /* Address of faulting instruction */ + if (t->si.si_signo == SIGILL || t->si.si_signo == SIGSEGV) { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " at address %p", + t->si.si_addr); + buffer_offs += strlen(&buffer[buffer_offs]); + } + } + + /* [POSIX] If non-zero, an errno value associated with this signal, + as defined in . */ + if (t->si.si_errno != 0) { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, ": "); + buffer_offs += strlen(&buffer[buffer_offs]); + if (strerror_r(t->si.si_errno, &buffer[buffer_offs], + buffer_len - buffer_offs) == 0) { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, + "unknown error"); + buffer_offs += strlen(&buffer[buffer_offs]); + } + } + + /* Sending process ID. */ + if (t->si.si_signo == SIGCHLD && t->si.si_pid != 0) { + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, + " (sent by pid %d)", (int) t->si.si_pid); + buffer_offs += strlen(&buffer[buffer_offs]); + } + + /* Faulting program counter location. */ + if (coffeecatch_get_pc_from_ucontext(&t->uc) != 0) { + const uintptr_t pc = coffeecatch_get_pc_from_ucontext(&t->uc); + snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " "); + buffer_offs += strlen(&buffer[buffer_offs]); + format_pc_address(&buffer[buffer_offs], buffer_len - buffer_offs, pc); + buffer_offs += strlen(&buffer[buffer_offs]); + } + + /* Return string. */ + buffer[buffer_offs] = '\0'; + return t->stack_buffer; + } else { + /* Static buffer in case of emergency */ + static char buffer[256]; +#ifdef _GNU_SOURCE + return strerror_r(error, &buffer[0], sizeof(buffer)); +#else + const int code = strerror_r(error, &buffer[0], sizeof(buffer)); + errno = error; + if (code == 0) { + return buffer; + } else { + return "unknown error during crash handler setup"; + } +#endif + } +} + +#if (defined(USE_CORKSCREW)) +typedef struct t_coffeecatch_backtrace_symbols_fun { + void (*fun)(void *arg, const char *module, uintptr_t addr, + const char *function, uintptr_t offset); + void *arg; +} t_coffeecatch_backtrace_symbols_fun; + +static void coffeecatch_backtrace_symbols_fun(void *arg, const backtrace_symbol_t *sym) { + t_coffeecatch_backtrace_symbols_fun *const bt = + (t_coffeecatch_backtrace_symbols_fun*) arg; + const char *symbol = sym->demangled_name != NULL + ? sym->demangled_name : sym->symbol_name; + const uintptr_t rel = sym->relative_pc - sym->relative_symbol_addr; + bt->fun(bt->arg, sym->map_name, sym->relative_pc, symbol, rel); +} +#endif + +/** + * Enumerate backtrace information. + */ +void coffeecatch_get_backtrace_info(void (*fun)(void *arg, + const char *module, + uintptr_t addr, + const char *function, + uintptr_t offset), void *arg) { + const native_code_handler_struct* const t = coffeecatch_get(); + if (t != NULL) { + size_t i; +#if (defined(USE_CORKSCREW)) + t_coffeecatch_backtrace_symbols_fun bt; + bt.fun = fun; + bt.arg = arg; + if (coffeecatch_backtrace_symbols(t->frames, t->frames_size, + coffeecatch_backtrace_symbols_fun, + &bt)) { + return; + } +#endif + for(i = 0; i < t->frames_size; i++) { + const uintptr_t pc = t->frames[i].absolute_pc; + format_pc_address_cb(pc, fun, arg); + } + } +} + +/** + * Returns 1 if we are already inside a coffeecatch block, 0 otherwise. + */ +int coffeecatch_inside() { + native_code_handler_struct *const t = coffeecatch_get(); + if (t != NULL && t->reenter > 0) { + t->reenter++; + return 1; + } + return 0; +} + +/** + * Calls coffeecatch_handler_setup(1) to setup a crash handler, mark the + * context as valid, and return 0 upon success. + */ +int coffeecatch_setup() { + if (coffeecatch_handler_setup(1) == 0) { + native_code_handler_struct *const t = coffeecatch_get(); + assert(t != NULL); + assert(t->reenter == 0); + t->reenter = 1; + t->ctx_is_set = 1; + return 0; + } else { + return -1; + } +} + +/** + * Calls coffeecatch_handler_cleanup() + */ +void coffeecatch_cleanup() { + native_code_handler_struct *const t = coffeecatch_get(); + assert(t != NULL); + assert(t->reenter > 0); + t->reenter--; + if (t->reenter == 0) { + t->ctx_is_set = 0; + coffeecatch_handler_cleanup(); + } +} + +sigjmp_buf* coffeecatch_get_ctx() { + native_code_handler_struct* t = coffeecatch_get(); + assert(t != NULL); + return &t->ctx; +} + +void coffeecatch_abort(const char* exp, const char* file, int line) { + native_code_handler_struct *const t = coffeecatch_get(); + if (t != NULL) { + t->expression = exp; + t->file = file; + t->line = line; + } + abort(); +} diff --git a/cmd/ebitenmobile/coffeecatch/coffeecatch.h b/cmd/ebitenmobile/coffeecatch/coffeecatch.h new file mode 100644 index 000000000..1f6d1b7fe --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/coffeecatch.h @@ -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 +#include + +#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 +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 + diff --git a/cmd/ebitenmobile/coffeecatch/coffeejni.c b/cmd/ebitenmobile/coffeecatch/coffeejni.c new file mode 100644 index 000000000..60b69d5aa --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/coffeejni.c @@ -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 +#include +#include +#include +#include +#include +#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 ""; + } +} + +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 ""; + } +} + +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, "", "(Ljava/lang/String;)V"); + jmethodID cons_cause = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;Ljava/lang/Throwable;)V"); + jmethodID cons_ste = (*env)->GetMethodID(env, cls_ste, "", + "(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 diff --git a/cmd/ebitenmobile/coffeecatch/coffeejni.h b/cmd/ebitenmobile/coffeecatch/coffeejni.h new file mode 100644 index 000000000..78a171e70 --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/coffeejni.h @@ -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 + +#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 diff --git a/cmd/ebitenmobile/coffeecatch/sample.c b/cmd/ebitenmobile/coffeecatch/sample.c new file mode 100644 index 000000000..31463abaa --- /dev/null +++ b/cmd/ebitenmobile/coffeecatch/sample.c @@ -0,0 +1,43 @@ +#include +#include + +#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; +} + diff --git a/cmd/ebitenmobile/coffeejni.c.go.go b/cmd/ebitenmobile/coffeejni.c.go.go new file mode 100644 index 000000000..f82cd9bfb --- /dev/null +++ b/cmd/ebitenmobile/coffeejni.c.go.go @@ -0,0 +1,6 @@ +// Code generated by file2byteslice. DO NOT EDIT. +// (gofmt is fine after generating) + +package main + +var coffeejni_c_go = []byte("// Code generated by file2byteslice. DO NOT EDIT.\n// (gofmt is fine after generating)\n\n// +build ebitenmobilegobind\n\npackage main\n\nvar coffeejni_c = []byte(\"/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.\\n * (especially for Android/Dalvik)\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n *\\n * Redistribution and use in source and binary forms, with or without\\n * modification, are permitted provided that the following conditions are met:\\n *\\n * 1. Redistributions of source code must retain the above copyright notice, this\\n * list of conditions and the following disclaimer.\\n * 2. Redistributions in binary form must reproduce the above copyright notice,\\n * this list of conditions and the following disclaimer in the documentation\\n * and/or other materials provided with the distribution.\\n *\\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND\\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\n */\\n\\n#ifndef COFFEECATCH_JNI_H\\n#define COFFEECATCH_JNI_H\\n\\n#include \\r\\n#include \\r\\n#include \\n#include \\n#include \\n#include \\r\\n#include \\\"coffeecatch.h\\\"\\n\\n#ifdef __cplusplus\\nextern \\\"C\\\" {\\n#endif\\n\\ntypedef struct t_bt_fun {\\n JNIEnv* env;\\n jclass cls;\\n jclass cls_ste;\\n jmethodID cons_ste;\\n jobjectArray elements;\\n size_t size;\\n size_t index;\\n} t_bt_fun;\\n\\nstatic char* bt_print(const char *function, uintptr_t offset) {\\n if (function != NULL) {\\n char buffer[256];\\n snprintf(buffer, sizeof(buffer), \\\"%s:%p\\\", function, (void*) offset);\\n return strdup(buffer);\\n } else {\\n return \\\"\\\";\\n }\\n}\\n\\nstatic char* bt_addr(uintptr_t addr) {\\n char buffer[32];\\n snprintf(buffer, sizeof(buffer), \\\"%p\\\", (void*) addr);\\n return strdup(buffer);\\n}\\n\\n#define IS_VALID_CLASS_CHAR(C) ( \\\\\\n ((C) >= 'a' && (C) <= 'z') \\\\\\n || ((C) >= 'A' && (C) <= 'Z') \\\\\\n || ((C) >= '0' && (C) <= '9') \\\\\\n || (C) == '_' \\\\\\n )\\n\\nstatic char* bt_module(const char *module) {\\n if (module != NULL) {\\n size_t i;\\n char *copy;\\n if (*module == '/') {\\n module++;\\n }\\n copy = strdup(module);\\n /* Pseudo-java-class. */\\n for(i = 0; copy[i] != '\\\\0'; i++) {\\n if (copy[i] == '/') {\\n copy[i] = '.';\\n } else if (!IS_VALID_CLASS_CHAR(copy[i])) {\\n copy[i] = '_';\\n }\\n }\\n return copy;\\n } else {\\n return \\\"\\\";\\n }\\n}\\n\\nstatic void bt_fun(void *arg, const char *module, uintptr_t addr, \\n const char *function, uintptr_t offset) {\\n t_bt_fun *const t = (t_bt_fun*) arg;\\n JNIEnv*const env = t->env;\\n jstring declaringClass = (*env)->NewStringUTF(env, bt_module(module));\\n jstring methodName = (*env)->NewStringUTF(env, bt_addr(addr));\\n jstring fileName = (*env)->NewStringUTF(env, bt_print(function, offset));\\n const int lineNumber = function != NULL ? 0 : -2; /* \\\"-2\\\" is \\\"inside JNI code\\\" */\\n jobject trace = (*env)->NewObject(env, t->cls_ste, t->cons_ste, \\n declaringClass, methodName, fileName,\\n lineNumber);\\n if (t->index < t->size) {\\n (*t->env)->SetObjectArrayElement(t->env, t->elements, t->index++, trace);\\n }\\n}\\n\\nvoid coffeecatch_throw_exception(JNIEnv* env) {\\n jclass cls = (*env)->FindClass(env, \\\"java/lang/Error\\\");\\n jclass cls_ste = (*env)->FindClass(env, \\\"java/lang/StackTraceElement\\\");\\n\\n jmethodID cons = (*env)->GetMethodID(env, cls, \\\"\\\", \\\"(Ljava/lang/String;)V\\\");\\n jmethodID cons_cause = (*env)->GetMethodID(env, cls, \\\"\\\", \\\"(Ljava/lang/String;Ljava/lang/Throwable;)V\\\");\\n jmethodID cons_ste = (*env)->GetMethodID(env, cls_ste, \\\"\\\",\\n \\\"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V\\\");\\n jmethodID meth_sste = (*env)->GetMethodID(env, cls, \\\"setStackTrace\\\",\\n \\\"([Ljava/lang/StackTraceElement;)V\\\");\\n\\n /* Exception message. */\\n const char*const message = coffeecatch_get_message();\\n jstring str = (*env)->NewStringUTF(env, strdup(message));\\n\\n /* Final exception. */\\n jthrowable exception;\\n\\n /* Add pseudo-stack trace. */\\n const ssize_t bt_size = coffeecatch_get_backtrace_size();\\n\\n assert(cls != NULL);\\n assert(cls_ste != NULL);\\n assert(cons != NULL);\\n assert(cons_cause != NULL);\\n assert(cons_ste != NULL);\\n assert(meth_sste != NULL);\\n\\n assert(message != NULL);\\n assert(str != NULL);\\n\\n /* Can we produce a stack trace ? */\\n if (bt_size > 0) {\\n /* Create secondary exception. */\\n jthrowable cause = (jthrowable) (*env)->NewObject(env, cls, cons, str);\\n\\n /* Stack trace. */\\n jobjectArray elements =\\n (*env)->NewObjectArray(env, bt_size, cls_ste, NULL);\\n if (elements != NULL) {\\n t_bt_fun t;\\n t.env = env;\\n t.cls = cls;\\n t.cls_ste = cls_ste;\\n t.cons_ste = cons_ste;\\n t.elements = elements;\\n t.index = 0;\\n t.size = bt_size;\\n coffeecatch_get_backtrace_info(bt_fun, &t);\\n (*env)->CallVoidMethod(env, cause, meth_sste, elements);\\n }\\n\\n /* Primary exception */\\n exception = (jthrowable) (*env)->NewObject(env, cls, cons_cause, str, cause);\\n } else {\\n /* Simple exception */\\n exception = (jthrowable) (*env)->NewObject(env, cls, cons, str);\\n }\\n\\n /* Throw exception. */\\n if (exception != NULL) {\\n (*env)->Throw(env, exception);\\n } else {\\n (*env)->ThrowNew(env, cls, strdup(message));\\n }\\n}\\n\\n#ifdef __cplusplus\\n}\\n#endif\\n\\n#endif\\n\")\n") diff --git a/cmd/ebitenmobile/coffeejni.h.go.go b/cmd/ebitenmobile/coffeejni.h.go.go new file mode 100644 index 000000000..d386b950d --- /dev/null +++ b/cmd/ebitenmobile/coffeejni.h.go.go @@ -0,0 +1,6 @@ +// Code generated by file2byteslice. DO NOT EDIT. +// (gofmt is fine after generating) + +package main + +var coffeejni_h_go = []byte("// Code generated by file2byteslice. DO NOT EDIT.\n// (gofmt is fine after generating)\n\n// +build ebitenmobilegobind\n\npackage main\n\nvar coffeejni_h = []byte(\"/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.\\n * (especially for Android/Dalvik)\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n * See the \\\"License\\\" section below for the licensing terms.\\n *\\n * Description:\\n *\\n * Allows to \\\"gracefully\\\" recover from a signal (segv, sibus...) as if it was\\n * a Java exception. It will not gracefully recover from allocator/mutexes\\n * corruption etc., however, but at least \\\"most\\\" gentle crashes (null pointer\\n * dereferencing, integer division, stack overflow etc.) should be handled\\n * without too much troubles.\\n *\\n * The handler is thread-safe, but client must have exclusive control on the\\n * signal handlers (ie. the library is installing its own signal handlers on\\n * top of the existing ones).\\n *\\n * You must build all your libraries with `-funwind-tables', to get proper\\n * unwinding information on all binaries. On ARM, you may also use the\\n * `--no-merge-exidx-entries` linker switch, to solve certain issues with\\n * unwinding (the switch is possibly not needed anymore).\\n * On Android, this can be achieved by using this line in the Android.mk file\\n * in each library block:\\n * LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries\\n *\\n * Example:\\n * COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));\\n *\\n * Implementation notes:\\n *\\n * Currently the library is installing both alternate stack and signal\\n * handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,\\n * SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to\\n * \\\"userland\\\" (compared to signal handler context). As a security, an alarm\\n * is started as soon as a fatal signal is detected (ie. not something the\\n * JVM will handle) to kill the process after a grace period. Be sure your\\n * program will exit quickly after the error is caught, or call alarm(0)\\n * to cancel the pending time-bomb.\\n * The signal handlers had to be written with caution, because the virtual\\n * machine might be using signals (including SEGV) to handle JIT compiler,\\n * and some clever optimizations (such as NullPointerException handling)\\n * We are using several signal-unsafe functions, namely:\\n * - siglongjmp() to return to userland\\n * - pthread_getspecific() to get thread-specific setup\\n *\\n * License:\\n *\\n * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)\\n * All rights reserved.\\n *\\n * Redistribution and use in source and binary forms, with or without\\n * modification, are permitted provided that the following conditions are met:\\n *\\n * 1. Redistributions of source code must retain the above copyright notice, this\\n * list of conditions and the following disclaimer.\\n * 2. Redistributions in binary form must reproduce the above copyright notice,\\n * this list of conditions and the following disclaimer in the documentation\\n * and/or other materials provided with the distribution.\\n *\\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND\\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\n */\\n\\n#ifndef COFFEECATCH_JNI_H\\n#define COFFEECATCH_JNI_H\\n\\n#include \\n\\n#ifdef __cplusplus\\nextern \\\"C\\\" {\\n#endif\\n\\n/**\\n * Setup crash handler to enter in a protected section. If a recognized signal\\n * is received in this section, an appropriate native Java Error will be\\n * raised.\\n *\\n * You can not exit the protected section block CODE_TO_BE_EXECUTED, using \\n * statements such as \\\"return\\\", because the cleanup code would not be\\n * executed.\\n *\\n * It is advised to enclose the complete CODE_TO_BE_EXECUTED block in a\\n * dedicated function declared extern or __attribute__ ((noinline)).\\n *\\n * You must build all your libraries with `-funwind-tables', to get proper\\n * unwinding information on all binaries. On Android, this can be achieved\\n * by using this line in the Android.mk file in each library block:\\n * LOCAL_CFLAGS := -funwind-tables\\n *\\n * Example:\\n *\\n * void my_native_function(JNIEnv* env, jobject object, jint *retcode) {\\n * COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));\\n * }\\n *\\n * In addition, the following restrictions MUST be followed:\\n * - the function must be declared extern, or with the special attribute\\n * __attribute__ ((noinline)).\\n * - you must not use local variables before the COFFEE_TRY_JNI block,\\n * or define them as \\\"volatile\\\".\\n *\\nCOFFEE_TRY_JNI(JNIEnv* env, CODE_TO_BE_EXECUTED)\\n */\\n\\n/** Internal functions & definitions, not to be used directly. **/\\nextern void coffeecatch_throw_exception(JNIEnv* env);\\n#define COFFEE_TRY_JNI(ENV, CODE) \\\\\\n do { \\\\\\n COFFEE_TRY() { \\\\\\n CODE; \\\\\\n } COFFEE_CATCH() { \\\\\\n coffeecatch_throw_exception(ENV); \\\\\\n } COFFEE_END(); \\\\\\n } while(0)\\n/** End of internal functions & definitions. **/\\n\\n#ifdef __cplusplus\\n}\\n#endif\\n\\n#endif\\n\")\n") diff --git a/cmd/ebitenmobile/generate.go b/cmd/ebitenmobile/generate.go index d63f19d48..2b5ab4092 100644 --- a/cmd/ebitenmobile/generate.go +++ b/cmd/ebitenmobile/generate.go @@ -12,6 +12,22 @@ // See the License for the specific language governing permissions and // 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 diff --git a/cmd/ebitenmobile/gobind.go b/cmd/ebitenmobile/gobind.go index b9b1fba40..073a1ece4 100644 --- a/cmd/ebitenmobile/gobind.go +++ b/cmd/ebitenmobile/gobind.go @@ -80,8 +80,16 @@ func invokeOriginalGobind(lang string) (pkgName string, err error) { } func run() error { - writeFile := func(filename string, content string) error { - if err := ioutil.WriteFile(filepath.Join(*outdir, filename), []byte(content), 0644); err != nil { + readFile := func(filename string) ([]byte, error) { + 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 nil @@ -105,22 +113,44 @@ func run() error { switch lang { case "objc": - // iOS - if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), replacePrefixes(objcM)); err != nil { + if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), []byte(replacePrefixes(objcM))); err != nil { return err } case "java": - // Android dir := filepath.Join(strings.Split(*javaPkg, ".")...) 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 } - 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 } 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: panic(fmt.Sprintf("unsupported language: %s", lang)) } @@ -129,6 +159,42 @@ func run() error { 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. // +build ios @@ -196,7 +262,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT. // TODO: Notify this to Go world? } -- (void)drawFrame{ +- (void)drawFrame { @synchronized(self) { if (!active_) { return; @@ -252,8 +318,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT. [self updateTouches:touches]; } -- (void)suspendGame -{ +- (void)suspendGame { NSAssert(started_, @"suspendGame msut not be called before viewDidLoad is called"); @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"); @synchronized(self) { diff --git a/cmd/ebitenmobile/gobind.go.go b/cmd/ebitenmobile/gobind.go.go new file mode 100644 index 000000000..5236b0c75 --- /dev/null +++ b/cmd/ebitenmobile/gobind.go.go @@ -0,0 +1,6 @@ +// Code generated by file2byteslice. DO NOT EDIT. +// (gofmt is fine after generating) + +package main + +var gobind_go = []byte("// Copyright 2019 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ebitenmobilegobind\n\n// gobind is a wrapper of the original gobind. This command adds extra files like a view controller.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"golang.org/x/tools/go/packages\"\n)\n\nvar (\n\tlang = flag.String(\"lang\", \"\", \"\")\n\toutdir = flag.String(\"outdir\", \"\", \"\")\n\tjavaPkg = flag.String(\"javapkg\", \"\", \"\")\n\tprefix = flag.String(\"prefix\", \"\", \"\")\n\tbootclasspath = flag.String(\"bootclasspath\", \"\", \"\")\n\tclasspath = flag.String(\"classpath\", \"\", \"\")\n\ttags = flag.String(\"tags\", \"\", \"\")\n)\n\nvar usage = `The Gobind tool generates Java language bindings for Go.\n\nFor usage details, see doc.go.`\n\nfunc main() {\n\tflag.Parse()\n\tif err := run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc invokeOriginalGobind(lang string) (pkgName string, err error) {\n\tcmd := exec.Command(\"gobind-original\", os.Args[1:]...)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tcfgtags := strings.Join(strings.Split(*tags, \",\"), \" \")\n\tcfg := &packages.Config{}\n\tswitch lang {\n\tcase \"java\":\n\t\tcfg.Env = append(os.Environ(), \"GOOS=android\")\n\tcase \"objc\":\n\t\tcfg.Env = append(os.Environ(), \"GOOS=darwin\")\n\t\tif cfgtags != \"\" {\n\t\t\tcfgtags += \" \"\n\t\t}\n\t\tcfgtags += \"ios\"\n\t}\n\tcfg.BuildFlags = []string{\"-tags\", cfgtags}\n\tpkgs, err := packages.Load(cfg, flag.Args()[0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn pkgs[0].Name, nil\n}\n\nfunc run() error {\n\treadFile := func(filename string) ([]byte, error) {\n\t\tcontent, err := ioutil.ReadFile(filepath.Join(*outdir, filename))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn content, nil\n\t}\n\n\twriteFile := func(filename string, content []byte) error {\n\t\tif err := ioutil.WriteFile(filepath.Join(*outdir, filename), content, 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Add additional files.\n\tlangs := strings.Split(*lang, \",\")\n\tfor _, lang := range langs {\n\t\tpkgName, err := invokeOriginalGobind(lang)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tprefixLower := *prefix + pkgName\n\t\tprefixUpper := strings.Title(*prefix) + strings.Title(pkgName)\n\t\treplacePrefixes := func(content string) string {\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.PrefixUpper}}\", prefixUpper)\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.PrefixLower}}\", prefixLower)\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.JavaPkg}}\", *javaPkg)\n\t\t\treturn content\n\t\t}\n\n\t\tswitch lang {\n\t\tcase \"objc\":\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", prefixLower+\"ebitenviewcontroller_ios.m\"), []byte(replacePrefixes(objcM))); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"java\":\n\t\t\tdir := filepath.Join(strings.Split(*javaPkg, \".\")...)\n\t\t\tdir = filepath.Join(dir, prefixLower)\n\t\t\tif err := writeFile(filepath.Join(\"java\", dir, \"EbitenView.java\"), []byte(replacePrefixes(viewJava))); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"java\", dir, \"EbitenSurfaceView.java\"), []byte(replacePrefixes(surfaceViewJava))); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Use CoffeeCatch (https://github.com/xroche/coffeecatch)\n\t\t\tbinding, err := readFile(filepath.Join(\"src\", \"gobind\", \"ebitenmobileview_android.c\"))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"ebitenmobileview_android.c\"), []byte(addCoffeeCatch(string(binding)))); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"go\":\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"coffeecatch.c\"), coffeecatch_c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"coffeecatch.h\"), coffeecatch_h); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"coffeejni.c\"), coffeejni_c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"coffeejni.h\"), coffeejni_h); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", \"cgoflags.go\"), []byte(cgoFlagsGo)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"unsupported language: %s\", lang))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc addInclude(src string, filename string) string {\n\tidx0 := strings.LastIndex(src, \"#include \")\n\tidx1 := strings.Index(src[idx0:], \"\\n\") + idx0\n\treturn src[:idx1] + \"\\n#include \\\"\" + filename + \"\\\"\" + src[idx1:]\n}\n\nfunc addCoffeeCatch(src string) string {\n\tsrc = addInclude(src, \"coffeecatch.h\")\n\tsrc = addInclude(src, \"coffeejni.h\")\n\n\tconst sig = \"ebitenmobileview_Ebitenmobileview_update(JNIEnv* env, jclass _clazz) {\"\n\tidx0 := strings.Index(src, sig) + len(sig)\n\tidx1 := strings.Index(src[idx0:], \"}\") + idx0\n\tp0 := src[:idx0]\n\tp1 := src[idx0:idx1]\n\tp2 := src[idx1:]\n\n\tconst coffeeCatchPre = `\n\tCOFFEE_TRY() {\n`\n\tconst coffeeCatchPost = `\n\t} COFFEE_CATCH() {\n\t\tcoffeecatch_throw_exception(env);\n\t} COFFEE_END();\n`\n\treturn p0 + coffeeCatchPre + p1 + coffeeCatchPost + p2\n}\n\nconst cgoFlagsGo = `// Code generated by ebitenmobile. DO NOT EDIT.\n\npackage main\n\n// #cgo CFLAGS: -D_REENTRANT\nimport \"C\"\n`\n\nconst objcM = `// Code generated by ebitenmobile. DO NOT EDIT.\n\n// +build ios\n\n#import \n#import \n#import \n#import \"Ebitenmobileview.objc.h\"\n\n@interface {{.PrefixUpper}}EbitenViewController : UIViewController\n@end\n\n@implementation {{.PrefixUpper}}EbitenViewController {\n GLKView* glkView_;\n bool started_;\n bool active_;\n bool error_;\n}\n\n- (GLKView*)glkView {\n if (!glkView_) {\n glkView_ = [[GLKView alloc] init];\n glkView_.multipleTouchEnabled = YES;\n }\n return glkView_;\n}\n\n- (void)viewDidLoad {\n [super viewDidLoad];\n\n if (!started_) {\n @synchronized(self) {\n active_ = true;\n }\n started_ = true;\n }\n\n self.glkView.delegate = (id)(self);\n [self.view addSubview: self.glkView];\n\n EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];\n [self glkView].context = context;\n\t\n [EAGLContext setCurrentContext:context];\n\t\n CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];\n [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];\n}\n\n- (void)viewDidLayoutSubviews {\n [super viewDidLayoutSubviews];\n CGRect viewRect = [[self view] frame];\n\n EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height, (id)self);\n}\n\n- (void)setViewRect:(long)x y:(long)y width:(long)width height:(long)height {\n CGRect glkViewRect = CGRectMake(x, y, width, height);\n [[self glkView] setFrame:glkViewRect];\n}\n\n- (void)didReceiveMemoryWarning {\n [super didReceiveMemoryWarning];\n // Dispose of any resources that can be recreated.\n // TODO: Notify this to Go world?\n}\n\n- (void)drawFrame {\n @synchronized(self) {\n if (!active_) {\n return;\n }\n\n [[self glkView] setNeedsDisplay];\n }\n}\n\n- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {\n @synchronized(self) {\n if (error_) {\n return;\n }\n NSError* err = nil;\n EbitenmobileviewUpdate(&err);\n if (err != nil) {\n [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)\n withObject:err\n waitUntilDone:NO];\n error_ = true;\n }\n }\n}\n\n- (void)onErrorOnGameUpdate:(NSError*)err {\n NSLog(@\"Error: %@\", err);\n}\n\n- (void)updateTouches:(NSSet*)touches {\n for (UITouch* touch in touches) {\n if (touch.view != [self glkView]) {\n continue;\n }\n CGPoint location = [touch locationInView:touch.view];\n EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);\n }\n}\n\n- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)suspendGame {\n NSAssert(started_, @\"suspendGame msut not be called before viewDidLoad is called\");\n\n @synchronized(self) {\n active_ = false;\n }\n}\n\n- (void)resumeGame {\n NSAssert(started_, @\"resumeGame msut not be called before viewDidLoad is called\");\n\n @synchronized(self) {\n active_ = true;\n }\n}\n\n@end\n`\n\nconst viewJava = `// Code generated by ebitenmobile. DO NOT EDIT.\n\npackage {{.JavaPkg}}.{{.PrefixLower}};\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.ViewGroup;\n\nimport {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;\nimport {{.JavaPkg}}.ebitenmobileview.ViewRectSetter;\n\npublic class EbitenView extends ViewGroup {\n private double getDeviceScale() {\n if (deviceScale_ == 0.0) {\n deviceScale_ = getResources().getDisplayMetrics().density;\n }\n return deviceScale_;\n }\n\n private double pxToDp(double x) {\n return x / getDeviceScale();\n }\n\n private double dpToPx(double x) {\n return x * getDeviceScale();\n }\n\n private double deviceScale_ = 0.0;\n\n public EbitenView(Context context) {\n super(context);\n ebitenSurfaceView_ = new EbitenSurfaceView(context);\n }\n\n public EbitenView(Context context, AttributeSet attrs) {\n super(context, attrs);\n ebitenSurfaceView_ = new EbitenSurfaceView(context, attrs);\n }\n\n @Override\n protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n if (!initialized_) {\n LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n addView(ebitenSurfaceView_, params);\n initialized_ = true;\n }\n\n int widthInDp = (int)Math.floor(pxToDp(right - left));\n int heightInDp = (int)Math.floor(pxToDp(bottom - top));\n Ebitenmobileview.layout(widthInDp, heightInDp, new ViewRectSetter() {\n @Override\n public void setViewRect(long xInDp, long yInDp, long widthInDp, long heightInDp) {\n final int widthInPx = (int)Math.ceil(dpToPx(widthInDp));\n final int heightInPx = (int)Math.ceil(dpToPx(heightInDp));\n final int xInPx = (int)Math.ceil(dpToPx(xInDp));\n final int yInPx = (int)Math.ceil(dpToPx(yInDp));\n new Handler(Looper.getMainLooper()).post(new Runnable() {\n @Override\n public void run() {\n ebitenSurfaceView_.layout(xInPx, yInPx, xInPx + widthInPx, yInPx + heightInPx);\n }\n });\n }\n });\n }\n\n // suspendGame suspends the game.\n // It is recommended to call this when the application is being suspended e.g.,\n // Activity's onPause is called.\n public void suspendGame() {\n if (initialized_) {\n ebitenSurfaceView_.onPause();\n }\n }\n\n // resumeGame resumes the game.\n // It is recommended to call this when the application is being resumed e.g.,\n // Activity's onResume is called.\n public void resumeGame() {\n if (initialized_) {\n ebitenSurfaceView_.onResume();\n }\n }\n\n // onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.\n // You can define your own error handler, e.g., using Crashlytics, by overwriting this method.\n protected void onErrorOnGameUpdate(Exception e) {\n Log.e(\"Go\", e.toString());\n }\n\n private EbitenSurfaceView ebitenSurfaceView_;\n private boolean initialized_ = false;\n}\n`\n\nconst surfaceViewJava = `// Code generated by ebitenmobile. DO NOT EDIT.\n\npackage {{.JavaPkg}}.{{.PrefixLower}};\n\nimport android.content.Context;\nimport android.opengl.GLSurfaceView;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\nimport {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;\nimport {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;\n\nclass EbitenSurfaceView extends GLSurfaceView {\n\n private class EbitenRenderer implements GLSurfaceView.Renderer {\n\n private boolean errored_ = false;\n\n @Override\n public void onDrawFrame(GL10 gl) {\n if (errored_) {\n return;\n }\n try {\n Ebitenmobileview.update();\n } catch (final Exception e) {\n new Handler(Looper.getMainLooper()).post(new Runnable() {\n @Override\n public void run() {\n onErrorOnGameUpdate(e);\n }\n });\n errored_ = true;\n }\n }\n\n @Override\n public void onSurfaceCreated(GL10 gl, EGLConfig config) {\n }\n\n @Override\n public void onSurfaceChanged(GL10 gl, int width, int height) {\n }\n }\n\n public EbitenSurfaceView(Context context) {\n super(context);\n initialize();\n }\n\n public EbitenSurfaceView(Context context, AttributeSet attrs) {\n super(context, attrs);\n initialize();\n }\n\n private void initialize() {\n setEGLContextClientVersion(2);\n setEGLConfigChooser(8, 8, 8, 8, 0, 0);\n setRenderer(new EbitenRenderer());\n }\n\n private double getDeviceScale() {\n if (deviceScale_ == 0.0) {\n deviceScale_ = getResources().getDisplayMetrics().density;\n }\n return deviceScale_;\n }\n\n private double pxToDp(double x) {\n return x / getDeviceScale();\n }\n\n @Override\n public boolean onTouchEvent(MotionEvent e) {\n for (int i = 0; i < e.getPointerCount(); i++) {\n int id = e.getPointerId(i);\n int x = (int)e.getX(i);\n int y = (int)e.getY(i);\n Ebitenmobileview.updateTouchesOnAndroid(e.getActionMasked(), id, (int)pxToDp(x), (int)pxToDp(y));\n }\n return true;\n }\n\n private void onErrorOnGameUpdate(Exception e) {\n ((EbitenView)getParent()).onErrorOnGameUpdate(e);\n }\n\n private double deviceScale_ = 0.0;\n}\n`\n") diff --git a/cmd/ebitenmobile/gobind.src.go b/cmd/ebitenmobile/gobind.src.go deleted file mode 100644 index f4cf24307..000000000 --- a/cmd/ebitenmobile/gobind.src.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated by file2byteslice. DO NOT EDIT. -// (gofmt is fine after generating) - -package main - -var gobindsrc = []byte("// Copyright 2019 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ebitenmobilegobind\n\n// gobind is a wrapper of the original gobind. This command adds extra files like a view controller.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"golang.org/x/tools/go/packages\"\n)\n\nvar (\n\tlang = flag.String(\"lang\", \"\", \"\")\n\toutdir = flag.String(\"outdir\", \"\", \"\")\n\tjavaPkg = flag.String(\"javapkg\", \"\", \"\")\n\tprefix = flag.String(\"prefix\", \"\", \"\")\n\tbootclasspath = flag.String(\"bootclasspath\", \"\", \"\")\n\tclasspath = flag.String(\"classpath\", \"\", \"\")\n\ttags = flag.String(\"tags\", \"\", \"\")\n)\n\nvar usage = `The Gobind tool generates Java language bindings for Go.\n\nFor usage details, see doc.go.`\n\nfunc main() {\n\tflag.Parse()\n\tif err := run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc invokeOriginalGobind(lang string) (pkgName string, err error) {\n\tcmd := exec.Command(\"gobind-original\", os.Args[1:]...)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tcfgtags := strings.Join(strings.Split(*tags, \",\"), \" \")\n\tcfg := &packages.Config{}\n\tswitch lang {\n\tcase \"java\":\n\t\tcfg.Env = append(os.Environ(), \"GOOS=android\")\n\tcase \"objc\":\n\t\tcfg.Env = append(os.Environ(), \"GOOS=darwin\")\n\t\tif cfgtags != \"\" {\n\t\t\tcfgtags += \" \"\n\t\t}\n\t\tcfgtags += \"ios\"\n\t}\n\tcfg.BuildFlags = []string{\"-tags\", cfgtags}\n\tpkgs, err := packages.Load(cfg, flag.Args()[0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn pkgs[0].Name, nil\n}\n\nfunc run() error {\n\twriteFile := func(filename string, content string) error {\n\t\tif err := ioutil.WriteFile(filepath.Join(*outdir, filename), []byte(content), 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Add additional files.\n\tlangs := strings.Split(*lang, \",\")\n\tfor _, lang := range langs {\n\t\tpkgName, err := invokeOriginalGobind(lang)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tprefixLower := *prefix + pkgName\n\t\tprefixUpper := strings.Title(*prefix) + strings.Title(pkgName)\n\t\treplacePrefixes := func(content string) string {\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.PrefixUpper}}\", prefixUpper)\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.PrefixLower}}\", prefixLower)\n\t\t\tcontent = strings.ReplaceAll(content, \"{{.JavaPkg}}\", *javaPkg)\n\t\t\treturn content\n\t\t}\n\n\t\tswitch lang {\n\t\tcase \"objc\":\n\t\t\t// iOS\n\t\t\tif err := writeFile(filepath.Join(\"src\", \"gobind\", prefixLower+\"ebitenviewcontroller_ios.m\"), replacePrefixes(objcM)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"java\":\n\t\t\t// Android\n\t\t\tdir := filepath.Join(strings.Split(*javaPkg, \".\")...)\n\t\t\tdir = filepath.Join(dir, prefixLower)\n\t\t\tif err := writeFile(filepath.Join(\"java\", dir, \"EbitenView.java\"), replacePrefixes(viewJava)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := writeFile(filepath.Join(\"java\", dir, \"EbitenSurfaceView.java\"), replacePrefixes(surfaceViewJava)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"go\":\n\t\t\t// Do nothing.\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"unsupported language: %s\", lang))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nconst objcM = `// Code generated by ebitenmobile. DO NOT EDIT.\n\n// +build ios\n\n#import \n#import \n#import \n#import \"Ebitenmobileview.objc.h\"\n\n@interface {{.PrefixUpper}}EbitenViewController : UIViewController\n@end\n\n@implementation {{.PrefixUpper}}EbitenViewController {\n GLKView* glkView_;\n bool started_;\n bool active_;\n bool error_;\n}\n\n- (GLKView*)glkView {\n if (!glkView_) {\n glkView_ = [[GLKView alloc] init];\n glkView_.multipleTouchEnabled = YES;\n }\n return glkView_;\n}\n\n- (void)viewDidLoad {\n [super viewDidLoad];\n\n if (!started_) {\n @synchronized(self) {\n active_ = true;\n }\n started_ = true;\n }\n\n self.glkView.delegate = (id)(self);\n [self.view addSubview: self.glkView];\n\n EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];\n [self glkView].context = context;\n\t\n [EAGLContext setCurrentContext:context];\n\t\n CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];\n [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];\n}\n\n- (void)viewDidLayoutSubviews {\n [super viewDidLayoutSubviews];\n CGRect viewRect = [[self view] frame];\n\n EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height, (id)self);\n}\n\n- (void)setViewRect:(long)x y:(long)y width:(long)width height:(long)height {\n CGRect glkViewRect = CGRectMake(x, y, width, height);\n [[self glkView] setFrame:glkViewRect];\n}\n\n- (void)didReceiveMemoryWarning {\n [super didReceiveMemoryWarning];\n // Dispose of any resources that can be recreated.\n // TODO: Notify this to Go world?\n}\n\n- (void)drawFrame{\n @synchronized(self) {\n if (!active_) {\n return;\n }\n\n [[self glkView] setNeedsDisplay];\n }\n}\n\n- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {\n @synchronized(self) {\n if (error_) {\n return;\n }\n NSError* err = nil;\n EbitenmobileviewUpdate(&err);\n if (err != nil) {\n [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)\n withObject:err\n waitUntilDone:NO];\n error_ = true;\n }\n }\n}\n\n- (void)onErrorOnGameUpdate:(NSError*)err {\n NSLog(@\"Error: %@\", err);\n}\n\n- (void)updateTouches:(NSSet*)touches {\n for (UITouch* touch in touches) {\n if (touch.view != [self glkView]) {\n continue;\n }\n CGPoint location = [touch locationInView:touch.view];\n EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);\n }\n}\n\n- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {\n [self updateTouches:touches];\n}\n\n- (void)suspendGame\n{\n NSAssert(started_, @\"suspendGame msut not be called before viewDidLoad is called\");\n\n @synchronized(self) {\n active_ = false;\n }\n}\n\n- (void)resumeGame\n{\n NSAssert(started_, @\"resumeGame msut not be called before viewDidLoad is called\");\n\n @synchronized(self) {\n active_ = true;\n }\n}\n\n@end\n`\n\nconst viewJava = `// Code generated by ebitenmobile. DO NOT EDIT.\n\npackage {{.JavaPkg}}.{{.PrefixLower}};\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.ViewGroup;\n\nimport {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;\nimport {{.JavaPkg}}.ebitenmobileview.ViewRectSetter;\n\npublic class EbitenView extends ViewGroup {\n private double getDeviceScale() {\n if (deviceScale_ == 0.0) {\n deviceScale_ = getResources().getDisplayMetrics().density;\n }\n return deviceScale_;\n }\n\n private double pxToDp(double x) {\n return x / getDeviceScale();\n }\n\n private double dpToPx(double x) {\n return x * getDeviceScale();\n }\n\n private double deviceScale_ = 0.0;\n\n public EbitenView(Context context) {\n super(context);\n ebitenSurfaceView_ = new EbitenSurfaceView(context);\n }\n\n public EbitenView(Context context, AttributeSet attrs) {\n super(context, attrs);\n ebitenSurfaceView_ = new EbitenSurfaceView(context, attrs);\n }\n\n @Override\n protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n if (!initialized_) {\n LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n addView(ebitenSurfaceView_, params);\n initialized_ = true;\n }\n\n int widthInDp = (int)Math.floor(pxToDp(right - left));\n int heightInDp = (int)Math.floor(pxToDp(bottom - top));\n Ebitenmobileview.layout(widthInDp, heightInDp, new ViewRectSetter() {\n @Override\n public void setViewRect(long xInDp, long yInDp, long widthInDp, long heightInDp) {\n final int widthInPx = (int)Math.ceil(dpToPx(widthInDp));\n final int heightInPx = (int)Math.ceil(dpToPx(heightInDp));\n final int xInPx = (int)Math.ceil(dpToPx(xInDp));\n final int yInPx = (int)Math.ceil(dpToPx(yInDp));\n new Handler(Looper.getMainLooper()).post(new Runnable() {\n @Override\n public void run() {\n ebitenSurfaceView_.layout(xInPx, yInPx, xInPx + widthInPx, yInPx + heightInPx);\n }\n });\n }\n });\n }\n\n // suspendGame suspends the game.\n // It is recommended to call this when the application is being suspended e.g.,\n // Activity's onPause is called.\n public void suspendGame() {\n if (initialized_) {\n ebitenSurfaceView_.onPause();\n }\n }\n\n // resumeGame resumes the game.\n // It is recommended to call this when the application is being resumed e.g.,\n // Activity's onResume is called.\n public void resumeGame() {\n if (initialized_) {\n ebitenSurfaceView_.onResume();\n }\n }\n\n // onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.\n // You can define your own error handler, e.g., using Crashlytics, by overwriting this method.\n protected void onErrorOnGameUpdate(Exception e) {\n Log.e(\"Go\", e.toString());\n }\n\n private EbitenSurfaceView ebitenSurfaceView_;\n private boolean initialized_ = false;\n}\n`\n\nconst surfaceViewJava = `// Code generated by ebitenmobile. DO NOT EDIT.\n\npackage {{.JavaPkg}}.{{.PrefixLower}};\n\nimport android.content.Context;\nimport android.opengl.GLSurfaceView;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\nimport {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;\nimport {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;\n\nclass EbitenSurfaceView extends GLSurfaceView {\n\n private class EbitenRenderer implements GLSurfaceView.Renderer {\n\n private boolean errored_ = false;\n\n @Override\n public void onDrawFrame(GL10 gl) {\n if (errored_) {\n return;\n }\n try {\n Ebitenmobileview.update();\n } catch (final Exception e) {\n new Handler(Looper.getMainLooper()).post(new Runnable() {\n @Override\n public void run() {\n onErrorOnGameUpdate(e);\n }\n });\n errored_ = true;\n }\n }\n\n @Override\n public void onSurfaceCreated(GL10 gl, EGLConfig config) {\n }\n\n @Override\n public void onSurfaceChanged(GL10 gl, int width, int height) {\n }\n }\n\n public EbitenSurfaceView(Context context) {\n super(context);\n initialize();\n }\n\n public EbitenSurfaceView(Context context, AttributeSet attrs) {\n super(context, attrs);\n initialize();\n }\n\n private void initialize() {\n setEGLContextClientVersion(2);\n setEGLConfigChooser(8, 8, 8, 8, 0, 0);\n setRenderer(new EbitenRenderer());\n }\n\n private double getDeviceScale() {\n if (deviceScale_ == 0.0) {\n deviceScale_ = getResources().getDisplayMetrics().density;\n }\n return deviceScale_;\n }\n\n private double pxToDp(double x) {\n return x / getDeviceScale();\n }\n\n @Override\n public boolean onTouchEvent(MotionEvent e) {\n for (int i = 0; i < e.getPointerCount(); i++) {\n int id = e.getPointerId(i);\n int x = (int)e.getX(i);\n int y = (int)e.getY(i);\n Ebitenmobileview.updateTouchesOnAndroid(e.getActionMasked(), id, (int)pxToDp(x), (int)pxToDp(y));\n }\n return true;\n }\n\n private void onErrorOnGameUpdate(Exception e) {\n ((EbitenView)getParent()).onErrorOnGameUpdate(e);\n }\n\n private double deviceScale_ = 0.0;\n}\n`\n") diff --git a/cmd/ebitenmobile/gomobile.go b/cmd/ebitenmobile/gomobile.go index 17b1953ab..10cf349f9 100644 --- a/cmd/ebitenmobile/gomobile.go +++ b/cmd/ebitenmobile/gomobile.go @@ -108,11 +108,22 @@ func prepareGomobileCommands() error { if err := os.Mkdir("src", 0755); err != nil { 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 } - - if err := runGo("build", "-o", filepath.Join("bin", "gobind"), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil { + if err := ioutil.WriteFile(filepath.Join("src", "coffeecatch.c.go"), coffeecatch_c_go, 0644); 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 } diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index 6675e1b5c..e412a94e2 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -18,9 +18,7 @@ package mobile import ( "context" - "fmt" "image" - "runtime/debug" "sync" "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) { - // 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.width = width u.height = height