diff --git a/android/LICENSE_COFFEECATCH b/android/LICENSE_COFFEECATCH new file mode 100644 index 000000000..1a44ae19c --- /dev/null +++ b/android/LICENSE_COFFEECATCH @@ -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/android/cpp/coffeecatch.c b/android/cpp/coffeecatch.c new file mode 100644 index 000000000..c5428aedc --- /dev/null +++ b/android/cpp/coffeecatch.c @@ -0,0 +1,1215 @@ +/* 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 +#endif + +/* #undef NO_USE_SIGALTSTACK */ +/* #undef USE_SILENT_SIGALTSTACK */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW)) +#include +#endif +#include +#include +#include + +/*#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 + +#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 + +/* 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; + + /* 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 + 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 ssize_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; + } else { + DEBUG(print("symbols not founs in libcorkscrew.so\n")); + } + dlclose(libcorkscrew); + } else { + DEBUG(print("libcorkscrew.so could not be loaded\n")); + } + return -1; +} + +static void coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace, + size_t frames, + void (*fun)(void *arg, + const backtrace_symbol_t *sym), + void *arg) { + 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); + } + dlclose(libcorkscrew); + } +} +#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 + + 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(__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) { +#if (defined(USE_CORKSCREW)) + t_coffeecatch_backtrace_symbols_fun bt; + bt.fun = fun; + bt.arg = arg; + coffeecatch_backtrace_symbols(t->frames, t->frames_size, + coffeecatch_backtrace_symbols_fun, &bt); +#elif (defined(USE_UNWIND)) + size_t i; + for(i = 0; i < t->frames_size; i++) { + const uintptr_t pc = t->frames[i]; + format_pc_address_cb(pc, fun, arg); + } +#else + (void) fun; + (void) arg; +#endif + } +} + +/** + * 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); + 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); + 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/android/cpp/coffeecatch.c.orig b/android/cpp/coffeecatch.c.orig new file mode 100644 index 000000000..1889dc0b9 --- /dev/null +++ b/android/cpp/coffeecatch.c.orig @@ -0,0 +1,1338 @@ +/* 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 +#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 + +/*#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__) && (!defined(__BIONIC_HAVE_UCONTEXT_T))) +#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) */ + +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; + +#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, +}; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; +} ucontext_t; + +#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; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; +} ucontext_t; + +#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; + + /* 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 + 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 ssize_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; + } else { + DEBUG(print("symbols not founs in libcorkscrew.so\n")); + } + dlclose(libcorkscrew); + } else { + DEBUG(print("libcorkscrew.so could not be loaded\n")); + } + return -1; +} + +static void coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace, + size_t frames, + void (*fun)(void *arg, + const backtrace_symbol_t *sym), + void *arg) { + 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); + } + dlclose(libcorkscrew); + } +} +#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 + + 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(__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) { +#if (defined(USE_CORKSCREW)) + t_coffeecatch_backtrace_symbols_fun bt; + bt.fun = fun; + bt.arg = arg; + coffeecatch_backtrace_symbols(t->frames, t->frames_size, + coffeecatch_backtrace_symbols_fun, &bt); +#elif (defined(USE_UNWIND)) + size_t i; + for(i = 0; i < t->frames_size; i++) { + const uintptr_t pc = t->frames[i]; + format_pc_address_cb(pc, fun, arg); + } +#else + (void) fun; + (void) arg; +#endif + } +} + +/** + * 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); + 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); + 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/android/cpp/coffeecatch_r10c.diff b/android/cpp/coffeecatch_r10c.diff new file mode 100644 index 000000000..ddee5b329 --- /dev/null +++ b/android/cpp/coffeecatch_r10c.diff @@ -0,0 +1,216 @@ +diff --git a/coffeecatch.c b/coffeecatch.c +index 448fe0f..f39fcdb 100644 +--- a/coffeecatch.c ++++ b/coffeecatch.c +@@ -43,10 +43,7 @@ + #include + #include + #include +-#if defined(__ANDROID__) && !defined(__BIONIC_HAVE_UCONTEXT_T) && \ +- defined(__arm__) && !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT) +-#include +-#endif ++#include + #if (defined(USE_UNWIND) && !defined(USE_CORKSCREW)) + #include + #endif +@@ -88,154 +85,34 @@ static const int native_sig_catch[SIG_CATCH_COUNT + 1] + /* Maximum value of a caught signal. */ + #define SIG_NUMBER_MAX 32 + +-#if (defined(__ANDROID__) && (!defined(__BIONIC_HAVE_UCONTEXT_T))) +-#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) */ +- +-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; +- +-#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, +-}; +- +-typedef struct ucontext { +- uint32_t uc_flags; +- struct ucontext* uc_link; +- stack_t uc_stack; +- mcontext_t uc_mcontext; +-} ucontext_t; +- +-#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; +- +-typedef struct ucontext { +- uint32_t uc_flags; +- struct ucontext* uc_link; +- stack_t uc_stack; +- mcontext_t uc_mcontext; +-} ucontext_t; +- +-#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; ++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, ++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. */ +@@ -341,7 +218,7 @@ static ssize_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc, + && 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 = ++ const ssize_t size = + unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth, + max_depth); + release_my_map_info_list(info); +@@ -1073,12 +950,12 @@ static uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) { + 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]; ++#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 \ No newline at end of file diff --git a/android/cpp/coffeejni.c b/android/cpp/coffeejni.c new file mode 100644 index 000000000..6dc9db5fa --- /dev/null +++ b/android/cpp/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 + +#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/android/cpp/jni.cpp b/android/cpp/jni.cpp index b8e11961a..17230817a 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include #include @@ -144,7 +146,8 @@ jlong JNICALL nativeCreate(JNIEnv* env, jobject obj, jstring cache_path_, jstrin mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreate"); cache_path = std_string_from_jstring(env, cache_path_); data_path = std_string_from_jstring(env, data_path_); - NativeMapView* native_map_view = new NativeMapView(env, obj); + NativeMapView* native_map_view = nullptr; + COFFEE_TRY_JNI(env, native_map_view = new NativeMapView(env, obj)); if (native_map_view == nullptr) { throw_error(env, "Unable to create NativeMapView."); return 0; @@ -165,7 +168,9 @@ void JNICALL nativeInitializeDisplay(JNIEnv* env, jobject obj, jlong native_map_ mbgl::Log::Debug(mbgl::Event::JNI, "nativeInitializeDisplay"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - if (!native_map_view->initializeDisplay()) { + bool ret = false; + COFFEE_TRY_JNI(env, ret = native_map_view->initializeDisplay()); + if (!ret) { throw_error(env, "Unable to initialize GL display."); } } @@ -174,14 +179,16 @@ void JNICALL nativeTerminateDisplay(JNIEnv* env, jobject obj, jlong native_map_v mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminateDisplay"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->terminateDisplay(); + COFFEE_TRY_JNI(env, native_map_view->terminateDisplay()); } void JNICALL nativeInitializeContext(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeInitializeContext"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - if (!native_map_view->initializeContext()) { + bool ret = false; + COFFEE_TRY_JNI(env, ret = native_map_view->initializeContext()); + if (!ret) { throw_error(env, "Unable to initialize GL context."); } } @@ -190,14 +197,16 @@ void JNICALL nativeTerminateContext(JNIEnv* env, jobject obj, jlong native_map_v mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminateContext"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->terminateContext(); + COFFEE_TRY_JNI(env, native_map_view->terminateContext()); } void JNICALL nativeCreateSurface(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jobject surface) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreateSurface"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - if (!native_map_view->createSurface(ANativeWindow_fromSurface(env, surface))) { + bool ret = false; + COFFEE_TRY_JNI(env, native_map_view->createSurface(ANativeWindow_fromSurface(env, surface))); + if (!ret) { throw_error(env, "Unable to create GL surface."); } } @@ -206,84 +215,86 @@ void JNICALL nativeDestroySurface(JNIEnv* env, jobject obj, jlong native_map_vie mbgl::Log::Debug(mbgl::Event::JNI, "nativeDestroySurface"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->destroySurface(); + COFFEE_TRY_JNI(env, native_map_view->destroySurface()); } void JNICALL nativeStart(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStart"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->start(); + COFFEE_TRY_JNI(env, native_map_view->start()); } void JNICALL nativeStop(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStop"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->stop(); + COFFEE_TRY_JNI(env, native_map_view->stop()); } void JNICALL nativePause(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativePause"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->pause(); + COFFEE_TRY_JNI(env, native_map_view->pause()); } void JNICALL nativeResume(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeResume"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->resume(); + COFFEE_TRY_JNI(env, native_map_view->resume()); } void JNICALL nativeRun(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeRun"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().run(); + COFFEE_TRY_JNI(env, native_map_view->getMap().run()); } void JNICALL nativeRerender(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeRerender"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().rerender(); + COFFEE_TRY_JNI(env, native_map_view->getMap().rerender()); } void JNICALL nativeUpdate(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeUpdate"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().update(); + COFFEE_TRY_JNI(env, native_map_view->getMap().update()); } void JNICALL nativeCleanup(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCleanup"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().cleanup(); + COFFEE_TRY_JNI(env, native_map_view->getMap().cleanup()); } void JNICALL nativeTerminate(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminate"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().terminate(); + COFFEE_TRY_JNI(env, native_map_view->getMap().terminate()); } jboolean JNICALL nativeNeedsSwap(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeNeedsSwap"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().needsSwap(); + jboolean ret = false; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().needsSwap()); + return ret; } void JNICALL nativeSwapped(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSwapped"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().swapped(); + COFFEE_TRY_JNI(env, (native_map_view->getMap().swapped())); } void JNICALL nativeResize(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jint width, jint height, jfloat ratio) { @@ -294,7 +305,7 @@ void JNICALL nativeResize(JNIEnv* env, jobject obj, jlong native_map_view_ptr, j assert(width <= UINT16_MAX); assert(height <= UINT16_MAX); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().resize(width, height, ratio); + COFFEE_TRY_JNI(env, native_map_view->getMap().resize(width, height, ratio)); } void JNICALL nativeResize(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jint width, jint height, jfloat ratio, jint fb_width, jint fb_height) { @@ -309,21 +320,23 @@ void JNICALL nativeResize(JNIEnv* env, jobject obj, jlong native_map_view_ptr, j assert(fb_width <= UINT16_MAX); assert(fb_height <= UINT16_MAX); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().resize(width, height, ratio, fb_width, fb_height); + COFFEE_TRY_JNI(env, native_map_view->getMap().resize(width, height, ratio, fb_width, fb_height)); } void JNICALL nativeSetAppliedClasses(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jobject classes) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAppliedClasses"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setAppliedClasses(std_vector_string_from_jobject(env, classes)); + COFFEE_TRY_JNI(env, native_map_view->getMap().setAppliedClasses(std_vector_string_from_jobject(env, classes))); } jobject JNICALL nativeGetAppliedClasses(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAppliedClasses"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return std_vector_string_to_jobject(env, native_map_view->getMap().getAppliedClasses()); + jobject ret = nullptr; + COFFEE_TRY_JNI(env, ret = std_vector_string_to_jobject(env, native_map_view->getMap().getAppliedClasses())); + return ret; } void JNICALL nativeSetDefaultTransitionDuration(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jlong duration_milliseconds) { @@ -331,56 +344,60 @@ void JNICALL nativeSetDefaultTransitionDuration(JNIEnv* env, jobject obj, jlong assert(native_map_view_ptr != 0); assert(duration_milliseconds >= 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setDefaultTransitionDuration(duration_milliseconds); + COFFEE_TRY_JNI(env, native_map_view->getMap().setDefaultTransitionDuration(duration_milliseconds)); } void JNICALL nativeSetStyleURL(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jstring url) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetStyleURL"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setStyleURL(std_string_from_jstring(env, url)); + COFFEE_TRY_JNI(env, native_map_view->getMap().setStyleURL(std_string_from_jstring(env, url))); } void JNICALL nativeSetStyleJSON(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jstring new_style_json, jstring base) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetStyleJSON"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setStyleJSON(std_string_from_jstring(env, new_style_json), std_string_from_jstring(env, base)); + COFFEE_TRY_JNI(env, native_map_view->getMap().setStyleJSON(std_string_from_jstring(env, new_style_json), std_string_from_jstring(env, base))); } jstring JNICALL nativeGetStyleJSON(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetStyleJSON"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return std_string_to_jstring(env, native_map_view->getMap().getStyleJSON()); + jstring ret = nullptr; + COFFEE_TRY_JNI(env, ret = std_string_to_jstring(env, native_map_view->getMap().getStyleJSON())); + return ret; } void JNICALL nativeSetAccessToken(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jstring access_token) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAccessToken"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setAccessToken(std_string_from_jstring(env, access_token)); + COFFEE_TRY_JNI(env, native_map_view->getMap().setAccessToken(std_string_from_jstring(env, access_token))); } jstring JNICALL nativeGetAccessToken(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAccessToken"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return std_string_to_jstring(env, native_map_view->getMap().getAccessToken()); + jstring ret = nullptr; + COFFEE_TRY_JNI(env, ret = std_string_to_jstring(env, native_map_view->getMap().getAccessToken())); + return ret; } void JNICALL nativeCancelTransitions(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCancelTransitions"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().cancelTransitions(); + COFFEE_TRY_JNI(env, native_map_view->getMap().cancelTransitions()); } void JNICALL nativeMoveBy(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble dx, jdouble dy, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeMoveBy"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().moveBy(dx, dy, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().moveBy(dx, dy, duration)); } void JNICALL nativeSetLonLat(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jobject lon_lat, jdouble duration) { @@ -400,15 +417,15 @@ void JNICALL nativeSetLonLat(JNIEnv* env, jobject obj, jlong native_map_view_ptr return; } - native_map_view->getMap().setLonLat(lon, lat, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().setLonLat(lon, lat, duration)); } jobject JNICALL nativeGetLonLat(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetLonLat"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - double lon, lat; - native_map_view->getMap().getLonLat(lon, lat); + double lon = 0.0, lat = 0.0; + COFFEE_TRY_JNI(env, native_map_view->getMap().getLonLat(lon, lat)); jobject ret = env->NewObject(lon_lat_class, lon_lat_constructor_id, lon, lat); if (ret == nullptr) { @@ -423,56 +440,60 @@ void JNICALL nativeStartPanning(JNIEnv* env, jobject obj, jlong native_map_view_ mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartPanning"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().startPanning(); + COFFEE_TRY_JNI(env, native_map_view->getMap().startPanning()); } void JNICALL nativeStopPanning(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopPanning"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().stopPanning(); + COFFEE_TRY_JNI(env, native_map_view->getMap().stopPanning()); } void JNICALL nativeResetPosition(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetPosition"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().resetPosition(); + COFFEE_TRY_JNI(env, native_map_view->getMap().resetPosition()); } void JNICALL nativeScaleBy(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble ds, jdouble cx, jdouble cy, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeScaleBy"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().scaleBy(ds, cx, cy, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().scaleBy(ds, cx, cy, duration)); } void JNICALL nativeSetScale(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble scale, jdouble cx, jdouble cy, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetScale"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setScale(scale, cx, cy, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().setScale(scale, cx, cy, duration)); } jdouble JNICALL nativeGetScale(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetScale"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getScale(); + jdouble ret = 0.0; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getScale()); + return ret; } void JNICALL nativeSetZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble zoom, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setZoom(zoom, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().setZoom(zoom, duration)); } jdouble JNICALL nativeGetZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getZoom(); + jdouble ret = 0.0; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getZoom()); + return ret; } void JNICALL nativeSetLonLatZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jobject lon_lat_zoom, jdouble duration) { @@ -498,15 +519,15 @@ void JNICALL nativeSetLonLatZoom(JNIEnv* env, jobject obj, jlong native_map_view return; } - native_map_view->getMap().setLonLatZoom(lon, lat, zoom, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().setLonLatZoom(lon, lat, zoom, duration)); } jobject JNICALL nativeGetLonLatZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetLonLatZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - double lon, lat, zoom; - native_map_view->getMap().getLonLatZoom(lon, lat, zoom); + double lon = 0.0, lat = 0.0, zoom = 0.0; + COFFEE_TRY_JNI(env, native_map_view->getMap().getLonLatZoom(lon, lat, zoom)); jobject ret = env->NewObject(lon_lat_zoom_class, lon_lat_zoom_constructor_id, lon, lat, zoom); if (ret == nullptr) { @@ -521,119 +542,129 @@ void JNICALL nativeResetZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().resetZoom(); + COFFEE_TRY_JNI(env, native_map_view->getMap().resetZoom()); } void JNICALL nativeStartScaling(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartScaling"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().startScaling(); + COFFEE_TRY_JNI(env, native_map_view->getMap().startScaling()); } void JNICALL nativeStopScaling(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopScaling"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().stopScaling(); + COFFEE_TRY_JNI(env, native_map_view->getMap().stopScaling()); } jdouble JNICALL nativeGetMinZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetMinZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getMinZoom(); + jdouble ret = 0.0; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getMinZoom()); + return ret; } jdouble JNICALL nativeGetMaxZoom(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetMaxZoom"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getMaxZoom(); + jdouble ret = 0.0; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getMaxZoom()); + return ret; } void JNICALL nativeRotateBy(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble sx, jdouble sy, jdouble ex, jdouble ey, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeRotateBy"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().rotateBy(sx, sy, ex, ey, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().rotateBy(sx, sy, ex, ey, duration)); } void JNICALL nativeSetBearing(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble degrees, jdouble duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetBearing"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setBearing(degrees, duration); + COFFEE_TRY_JNI(env, native_map_view->getMap().setBearing(degrees, duration)); } void JNICALL nativeSetBearing(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jdouble degrees, jdouble cx, jdouble cy) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetBearing"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setBearing(degrees, cx, cy); + COFFEE_TRY_JNI(env, native_map_view->getMap().setBearing(degrees, cx, cy)); } jdouble JNICALL nativeGetBearing(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetBearing"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getBearing(); + jdouble ret = 0.0; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getBearing()); + return ret; } void JNICALL nativeResetNorth(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetNorth"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().resetNorth(); + COFFEE_TRY_JNI(env, native_map_view->getMap().resetNorth()); } void JNICALL nativeStartRotating(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartRotating"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().startRotating(); + COFFEE_TRY_JNI(env, native_map_view->getMap().startRotating()); } void JNICALL nativeStopRotating(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopRotating"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().stopRotating(); + COFFEE_TRY_JNI(env, native_map_view->getMap().stopRotating()); } jboolean JNICALL nativeCanRotate(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCanRotate"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().canRotate(); + jboolean ret = false; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().canRotate()); + return ret; } void JNICALL nativeSetDebug(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jboolean debug) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetDebug"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setDebug(debug); + COFFEE_TRY_JNI(env, native_map_view->getMap().setDebug(debug)); } void JNICALL nativeToggleDebug(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeToggleDebug"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().toggleDebug(); + COFFEE_TRY_JNI(env, native_map_view->getMap().toggleDebug()); } jboolean JNICALL nativeGetDebug(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetDebug"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - return native_map_view->getMap().getDebug(); + jboolean ret = false; + COFFEE_TRY_JNI(env, ret = native_map_view->getMap().getDebug()); + return ret; } void JNICALL nativeSetReachability(JNIEnv* env, jobject obj, jlong native_map_view_ptr, jboolean status) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetReachability"); assert(native_map_view_ptr != 0); NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); - native_map_view->getMap().setReachability(status); + COFFEE_TRY_JNI(env, native_map_view->getMap().setReachability(status)); } } @@ -645,7 +676,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad"); - JNIEnv* env = nullptr; + JNIEnv* env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index c9cb96d5a..7a04688b0 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -329,7 +329,7 @@ bool NativeMapView::createSurface(ANativeWindow* window_) { log_gl_string(GL_SHADING_LANGUAGE_VERSION, "SL Version"); // In the emulator this returns NULL with error code 0? https://code.google.com/p/android/issues/detail?id=78977 log_gl_string(GL_EXTENSIONS, "Extensions"); - loadExtensions(); + loadExtensions(); if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { @@ -449,7 +449,7 @@ EGLConfig NativeMapView::chooseConfig(const EGLConfig configs[], eglGetError()); return nullptr; } - + mbgl::Log::Info(mbgl::Event::OpenGL, "Caveat: %d", caveat); mbgl::Log::Info(mbgl::Event::OpenGL, "Conformant: %d", conformant); mbgl::Log::Info(mbgl::Event::OpenGL, "Color: %d", bits); @@ -508,7 +508,7 @@ void NativeMapView::start() { assert(display != EGL_NO_DISPLAY); assert(context != EGL_NO_CONTEXT); - + map.start(true); } @@ -520,7 +520,7 @@ void NativeMapView::loadExtensions() { } std::string extensions(reinterpret_cast(str)); - + if (extensions.find("GL_OES_vertex_array_object") != std::string::npos) { mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_OES_vertex_array_object."); gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)eglGetProcAddress("glBindVertexArrayOES"); @@ -543,7 +543,7 @@ void NativeMapView::stop() { assert(display != EGL_NO_DISPLAY); assert(context != EGL_NO_CONTEXT); - + map.stop(); } @@ -553,7 +553,7 @@ void NativeMapView::pause(bool wait_for_pause) { assert(display != EGL_NO_DISPLAY); assert(context != EGL_NO_CONTEXT); - + map.pause(wait_for_pause); } diff --git a/android/mapboxgl-app.gyp b/android/mapboxgl-app.gyp index 118669542..b01050f0b 100644 --- a/android/mapboxgl-app.gyp +++ b/android/mapboxgl-app.gyp @@ -10,6 +10,8 @@ 'sources': [ './cpp/native_map_view.cpp', './cpp/jni.cpp', + './cpp/coffeecatch.c', + './cpp/coffeejni.c', ], 'cflags_cc': [ '-I<(boost_root)/include', diff --git a/include/coffeecatch/coffeecatch.h b/include/coffeecatch/coffeecatch.h new file mode 100644 index 000000000..3aa00ddd4 --- /dev/null +++ b/include/coffeecatch/coffeecatch.h @@ -0,0 +1,226 @@ +/* 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_setup(void); +extern sigjmp_buf* coffeecatch_get_ctx(void); +extern void coffeecatch_cleanup(void); +#define COFFEE_TRY() \ + if (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/include/coffeecatch/coffeejni.h b/include/coffeecatch/coffeejni.h new file mode 100644 index 000000000..3d7fe1b28 --- /dev/null +++ b/include/coffeecatch/coffeejni.h @@ -0,0 +1,133 @@ +/* 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 + +#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