-
Notifications
You must be signed in to change notification settings - Fork 134
/
coffeecatch.h
228 lines (210 loc) · 8.66 KB
/
coffeecatch.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
* (especially for Android/Dalvik)
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
* See the "License" section below for the licensing terms.
*
* Description:
*
* Allows to "gracefully" recover from a signal (segv, sibus...) as if it was
* a Java exception. It will not gracefully recover from allocator/mutexes
* corruption etc., however, but at least "most" gentle crashes (null pointer
* dereferencing, integer division, stack overflow etc.) should be handled
* without too much troubles.
*
* The handler is thread-safe, but client must have exclusive control on the
* signal handlers (ie. the library is installing its own signal handlers on
* top of the existing ones).
*
* You must build all your libraries with `-funwind-tables', to get proper
* unwinding information on all binaries. On ARM, you may also use the
* `--no-merge-exidx-entries` linker switch, to solve certain issues with
* unwinding (the switch is possibly not needed anymore).
* On Android, this can be achieved by using this line in the Android.mk file
* in each library block:
* LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
*
* Example:
*
* COFFEE_TRY() {
* call_some_native_function()
* } COFFEE_CATCH() {
* const char*const message = coffeecatch_get_message();
* jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
* (*env)->ThrowNew(env, cls, strdup(message));
* } COFFEE_END();
*
* Implementation notes:
*
* Currently the library is installing both alternate stack and signal
* handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,
* SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to
* "userland" (compared to signal handler context). As a security, an alarm
* is started as soon as a fatal signal is detected (ie. not something the
* JVM will handle) to kill the process after a grace period. Be sure your
* program will exit quickly after the error is caught, or call alarm(0)
* to cancel the pending time-bomb.
* The signal handlers had to be written with caution, because the virtual
* machine might be using signals (including SEGV) to handle JIT compiler,
* and some clever optimizations (such as NullPointerException handling)
* We are using several signal-unsafe functions, namely:
* - siglongjmp() to return to userland
* - pthread_getspecific() to get thread-specific setup
*
* License:
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COFFEECATCH_H
#define COFFEECATCH_H
#include <stdint.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Setup crash handler to enter in a protected section. If a recognized signal
* is received in this section, the execution will be diverted to the
* COFFEE_CATCH() block.
*
* Note: you MUST use the following pattern when using this macro:
* COFFEE_TRY() {
* .. protected section without exit point
* } COFFEE_CATCH() {
* .. handler section without exit point
* } COFFEE_END();
*
* You can not exit the protected section block, or the handler section block,
* using statements such as "return", because the cleanup code would not be
* executed.
*
* It is advised to enclose this complete try/catch/end block in a dedicated
* function declared extern or __attribute__ ((noinline)).
*
* Example:
*
* void my_native_function(JNIEnv* env, jobject object, jint *retcode) {
* COFFEE_TRY() {
* *retcode = call_dangerous_function(env, object);
* } COFFEE_CATCH() {
* const char*const message = coffeecatch_get_message();
* jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
* (*env)->ThrowNew(env, cls, strdup(message));
* *retcode = -1;
* } COFFEE_END();
* }
*
* In addition, the following restrictions MUST be followed:
* - the function must be declared extern, or with the special attribute
* __attribute__ ((noinline)).
* - you must not use local variables before the complete try/catch/end block,
* or define them as "volatile".
* - your function should not ignore the crash silently, as the library will
* ensure the process is killed after a grace period (typically 30s) to
* prevent any deadlock that may occur if the crash was caught inside a
* non-signal-safe function, for example (such as malloc()).
*
COFFEE_TRY()
**/
/**
* Declare the signal handler block. This block will be executed if a signal
* was received, and recognized, in the previous COFFEE_TRY() {} section.
* You may call audit functions in this block, such as coffeecatch_get_signal()
* or coffeecatch_get_message().
*
COFFEE_CATCH()
**/
/**
* Declare the end of the COFFEE_TRY()/COFFEE_CATCH() section.
* Diagnostic functions must not be called beyond this point.
*
COFFEE_END()
**/
/**
* Get the signal associated with the crash.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern int coffeecatch_get_signal(void);
/**
* Get the full error message associated with the crash.
* This function can only be called inside a COFFEE_CATCH() block, and the
* returned pointer is only valid within this block. (you may want to copy
* the string in a static buffer, or use strdup())
*/
const char* coffeecatch_get_message(void);
/**
* Raise an abort() signal in the current thread. If the current code section
* is protected, the 'exp', 'file' and 'line' information are stored for
* further audit.
*/
extern void coffeecatch_abort(const char* exp, const char* file, int line);
/**
* Assertion check. If the expression is false, an abort() signal is raised
* using coffeecatch_abort().
*/
#define coffeecatch_assert(EXP) (void)( (EXP) || (coffeecatch_abort(#EXP, __FILE__, __LINE__), 0) )
/**
* Get the backtrace size, or 0 upon error.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern size_t coffeecatch_get_backtrace_size(void);
/**
* Get the backtrace pointer, or 0 upon error.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern uintptr_t coffeecatch_get_backtrace(ssize_t index);
/**
* Enumerate the backtrace with information.
* This function can only be called inside a COFFEE_CATCH() block.
*/
extern void coffeecatch_get_backtrace_info(void (*fun)(void *arg,
const char *module,
uintptr_t addr,
const char *function,
uintptr_t offset), void *arg);
/**
* Cancel any pending alarm() triggered after a signal was caught.
* Calling this function is dangerous, because it exposes the process to
* a possible deadlock if the signal was caught due to internal low-level
* library error (mutex being in a locked state, for example).
*/
extern int coffeecatch_cancel_pending_alarm(void);
/** Internal functions & definitions, not to be used directly. **/
#include <setjmp.h>
extern int coffeecatch_inside(void);
extern int coffeecatch_setup(void);
extern sigjmp_buf* coffeecatch_get_ctx(void);
extern void coffeecatch_cleanup(void);
#define COFFEE_TRY() \
if (coffeecatch_inside() || \
(coffeecatch_setup() == 0 \
&& sigsetjmp(*coffeecatch_get_ctx(), 1) == 0))
#define COFFEE_CATCH() else
#define COFFEE_END() coffeecatch_cleanup()
/** End of internal functions & definitions. **/
#ifdef __cplusplus
}
#endif
#endif