ios虽然是用OC语言或Swift语言进行编程,但是它同时也支持c++语法,底层的动态库也基本上都是C++编写的。所以ios在运行的时候,可能会抛出C++异常,如果该C++异常可以被转换为NSException,就抛给OC异常的捕获机制。如果不能被转换,就继续C++的terminate流程,也就是default_terminate_handler
。这个C++异常的默认terminate函数内部调用abort_message函数,最后触发了一个abort调用,系统产生一个SIGABRT
信号。
通常情况下,在系统抛出C++异常后,如果被try/catch掉,再重新抛出的C++异常(因为要判断该C++异常是否可以被转换为NSException),异常的现场堆栈已经消失,所以上层通过捕获SIGABRT
信号是无法还原发生异常时的场景,即通常我们说的异常堆栈缺失
。
为什么再重新抛出的C++异常的堆栈会消失,这个主要是因为try/cach语句内部会调用__cxa_rethrow()
抛出异常,__cxa_rethrow()
内部又会调用 unwind
,unwind
可以简单理解为函数调用的逆调用,主要用来清理函数调用过程中每个函数生成的局部变量,一直到最外层的catch语句所在的函数,并把控制移交给catch语句,这就是C++异常的堆栈消失原因。
整个C++异常的触发和调用流程如下图所示:
可以使用std::set_terminate();
设置崩溃回调,回调代码如下:
static void CPPExceptionTerminate(void){
NSLog(@"Trapped c++ exception");
const char* name = NULL;
std::type_info* tinfo = __cxxabiv1::__cxa_current_exception_type();
if(tinfo != NULL){
name = tinfo->name();
}
if(name == NULL || strcmp(name, "NSException") != 0){
char descriptionBuff[DESCRIPTION_BUFFER_LENGTH];
const char* description = descriptionBuff;
descriptionBuff[0] = 0;
NSLog(@"Discovering what kind of exception was thrown.");
captureStackTrace = false;
try
{
throw;
}
catch(std::exception& exc)
{
strncpy(descriptionBuff, exc.what(), sizeof(descriptionBuff));
}
#define CATCH_VALUE(TYPE, PRINTFTYPE) \
catch(TYPE value)\
{ \
snprintf(descriptionBuff, sizeof(descriptionBuff), "%" #PRINTFTYPE, value); \
}
CATCH_VALUE(char, d)
CATCH_VALUE(short, d)
CATCH_VALUE(int, d)
CATCH_VALUE(long, ld)
CATCH_VALUE(long long, lld)
CATCH_VALUE(unsigned char, u)
CATCH_VALUE(unsigned short, u)
CATCH_VALUE(unsigned int, u)
CATCH_VALUE(unsigned long, lu)
CATCH_VALUE(unsigned long long, llu)
CATCH_VALUE(float, f)
CATCH_VALUE(double, f)
CATCH_VALUE(long double, Lf)
CATCH_VALUE(char*, s)
catch(...)
{
description = NULL;
}
captureStackTrace = true;
NSString * callStackSymbols = [BSBacktraceLogger bs_backtrace:backtraceBuffer backtraceLength:backtraceLength] ;
NSString *date = [LLTool stringFromDate:[NSDate date]];
NSArray *appInfos = [LLRoute appInfos];
LLCrashModel *model = [[LLCrashModel alloc] initWithName:@"CPP Exception" reason:[NSString stringWithFormat:@"抛出异常:%@",description?[NSString stringWithUTF8String:description]:@"Unknown"] userInfo:nil stackSymbols:@[callStackSymbols] date:date userIdentity:[LLConfig sharedConfig].userIdentity appInfos:appInfos launchDate:[NSObject LL_launchDate]];
[LLCrashSignalHelper sharedHelper].crashModel = model;
[[LLStorageManager sharedManager] saveModel:model complete:^(BOOL result) {
NSLog(@"Save mach model success");
} synchronous:YES];
}else{
NSLog(@"Detected NSException. Letting the current NSException handler deal with it.");
}
NSLog(@"Calling original terminate handler.");
originalTerminateHandler() ;
}
效果演示
C++异常如果通过signal
机制进行捕获,会出现异常堆栈缺失
问题。测试代码如下:
-(void)testCPPException{
char* name= "测试Cpp Exception" ;
throw name;
}
已经成功捕获了CPP Exception,该功能已经集成到了我开发的SDK里面,效果如下图所示:
通过CPP Exception的捕获成功将异常的堆栈进行还原,进入到Signal(SIGABRT)
,可以发现通过signal
机制捕获的堆栈出现了异常堆栈缺失
问题。如下图所示: