/* 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(); }