Home > Mobile >  How does CoffeeCatch jump back to the COFFEE_CATCH clause?
How does CoffeeCatch jump back to the COFFEE_CATCH clause?

Time:06-14

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 the else statement).
  • Second, I don't get where siglongjmp should actually jump. If it goes to the if statement again and evaluates the condition again, it will evaluate to true again and that's a loop. How would it go to else?

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.

  •  Tags:  
  • c c
  • Related