I recently discovered CoffeeCatch, which I want to use to log the C/C native crashes on Android. I haven't managed, but still I am curious about how it works internally.
My understanding is that it basically catches and emitted signal (e.g. SIGSEGV) and allows the user to do something with it; in my case I would like save the stack trace and crash.
It is used like this:
COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
Where call_dangerous_function()
is a function that could crash and therefore emit a signal.
The macro is defined here:
#define COFFEE_TRY_JNI(ENV, CODE) \
do { \
COFFEE_TRY() { \
CODE; \
} COFFEE_CATCH() { \
coffeecatch_throw_exception(ENV); \
} COFFEE_END(); \
} while(0)
Which resolves to something like this:
do {
if (coffeecatch_inside() ||
(coffeecatch_setup() == 0
&& sigsetjmp(*coffeecatch_get_ctx(), 1) == 0)) { \
call_dangerous_function(env, object);
} else {
coffeecatch_throw_exception(ENV);
} coffeecatch_cleanup();
} while(0)
Here, my understanding is that sigsetjmp(*coffeecatch_get_ctx(), 1) == 0)
somehow sets a pointer to the current "context", which I guess is something like the execution state of this thread at this moment.
So somehow, later, siglongjmp(t->ctx, code); will be called (after the environment has been somehow prepared for it) to jump back here.
What's not clear to me is if we then jump at the if
statement (and evaluate the statement again), or somehow end up into the else
statement for some reason.
My expectation is that siglongjmp
now results in going into the else
statement (where in my case a Java exception could be thrown). But:
- First, that's not what I see (my program emits the SIGSEGV signal right after
siglongjmp
is called without ever reaching theelse
statement). - Second, I don't get where
siglongjmp
should actually jump. If it goes to theif
statement again and evaluates the condition again, it will evaluate totrue
again and that's a loop. How would it go toelse
?
CodePudding user response:
siglongjmp
jumps back to where sigsetjmp
was called, and makes it look like sigsetjmp
returned the value that siglongjmp
was passed. So in this case, if siglongjmp
is called with a non-zero value, then it will jump back to the sigsetjmp(*coffeecatch_get_ctx(), 1) == 0
condition, which will evaluate to false
and thus the if
will not be satisfied and the else
block will execute.
It's very unlikely you can meaningfully recover from a SIGSEGV however. By the time a SIGSEGV happens your program has wandered so far off into undefined behavior that it is impossible to reason about its current state. There is a high chance that data has been corrupted and/or your call stack has been destroyed. The only meaningful action is to terminate the process.
Note: Using (sig)?setjmp
/(sig)?longjmp
in C is a very bad idea. They do not execute object destructors, and thus can easily leak memory and/or violate class invariants.