1216 lines
35 KiB
C
1216 lines
35 KiB
C
/* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
#include <sys/ucontext.h>
|
|
#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
|
|
#include <unwind.h>
|
|
#endif
|
|
#include <pthread.h>
|
|
#include <dlfcn.h>
|
|
#include <coffeecatch/coffeecatch.h>
|
|
|
|
/*#define NDK_DEBUG 1*/
|
|
#if ( defined(NDK_DEBUG) && ( NDK_DEBUG == 1 ) )
|
|
#define DEBUG(A) do { A; } while(0)
|
|
#define FD_ERRNO 2
|
|
static void print(const char *const s) {
|
|
size_t count;
|
|
for(count = 0; s[count] != '\0'; count++) ;
|
|
/* write() is async-signal-safe. */
|
|
(void) write(FD_ERRNO, s, count);
|
|
}
|
|
#else
|
|
#define DEBUG(A)
|
|
#endif
|
|
|
|
/* Alternative stack size. */
|
|
#define SIG_STACK_BUFFER_SIZE SIGSTKSZ
|
|
|
|
#ifdef USE_UNWIND
|
|
/* Number of backtraces to get. */
|
|
#define BACKTRACE_FRAMES_MAX 32
|
|
#endif
|
|
|
|
/* Signals to be caught. */
|
|
#define SIG_CATCH_COUNT 7
|
|
static const int native_sig_catch[SIG_CATCH_COUNT + 1]
|
|
= { SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV
|
|
#ifdef SIGSTKFLT
|
|
, SIGSTKFLT
|
|
#endif
|
|
, 0 };
|
|
|
|
/* Maximum value of a caught signal. */
|
|
#define SIG_NUMBER_MAX 32
|
|
|
|
#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 <http://pubs.opengroup.org/onlinepubs/009696699/basedefs/signal.h.html>
|
|
*/
|
|
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 <index>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 <errno.h>. */
|
|
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();
|
|
}
|