CoffeeCatch触发信号后如何跳转回COFFEE_CATCH子句?
CoffeeCatch 原生崩溃捕获原理答疑
基础说明
CoffeeCatch是适配Android平台的C/C++原生崩溃捕获组件,核心逻辑是拦截SIGSEGV等系统异常信号,支持开发者插入自定义处理逻辑,可满足崩溃发生时保存栈轨迹信息的需求。
它的典型使用方式如下:
COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
其中call_dangerous_function()代表可能触发崩溃、产生异常信号的风险函数。COFFEE_TRY_JNI的宏定义如下:
#define COFFEE_TRY_JNI(ENV, CODE) \ do { \ COFFEE_TRY() { \ CODE; \ } COFFEE_CATCH() { \ coffeecatch_throw_exception(ENV); \ } COFFEE_END(); \ } while(0)
宏完全展开后的逻辑结构如下:
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)
核心疑问解答
siglongjmp的跳转目标与分支走向
首先明确sigsetjmp/siglongjmp的标准行为:
- 第一次执行到
sigsetjmp时,会将当前线程的寄存器状态、栈上下文、信号掩码保存到传入的上下文结构中,此时sigsetjmp返回值为0,整个if条件判定为真,进入if分支执行风险代码。 - 异常信号触发后,信号处理函数调用
siglongjmp跳转时,不会回到if语句的开头重新判定所有条件,而是直接跳转到sigsetjmp的调用点,此时sigsetjmp会返回传给siglongjmp的非0值,sigsetjmp(...) == 0表达式判定为假,整个if条件不成立,直接进入else分支执行异常处理逻辑,不会出现无限循环的问题。
这里的关键细节是逻辑与&&的短路求值规则:第一次执行if条件判定时,coffeecatch_setup() == 0已经求值为真,siglongjmp跳回后仅需要继续完成sigsetjmp(...) == 0的求值,不会重复执行coffeecatch_setup(),也不会回头重新判定coffeecatch_inside()的状态。
测试时siglongjmp触发二次SIGSEGV、无法进入else分支的原因
出现这个问题基本是以下两个原因导致的:
- 未配置备用信号栈:大部分SIGSEGV崩溃伴随栈溢出问题,默认情况下信号处理函数运行在当前线程栈上,如果栈已经损坏溢出,信号处理函数本身就会触发二次段错误,根本执行不到siglongjmp的逻辑。CoffeeCatch本身不会自动给所有线程配置备用信号栈,需要接入方手动调用
sigaltstack为每个需要捕获崩溃的线程配置独立的备用信号栈空间。 - 上下文被破坏:如果编译时开了O2及以上的优化等级,或者风险函数存在栈内存越界写入,会导致
sigsetjmp保存的上下文结构被踩坏,siglongjmp跳转到非法地址,直接触发二次崩溃。
内容的提问来源于stack exchange,提问作者JonasVautherin
相关产品推荐
相关产品推荐

