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()内部又会调用 unwindunwind可以简单理解为函数调用的逆调用,主要用来清理函数调用过程中每个函数生成的局部变量,一直到最外层的catch语句所在的函数,并把控制移交给catch语句,这就是C++异常的堆栈消失原因。

整个C++异常的触发和调用流程如下图所示:
软件测试之SDK开发(ios)——Cpp Exception捕获-LMLPHP
可以使用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;
}

软件测试之SDK开发(ios)——Cpp Exception捕获-LMLPHP
已经成功捕获了CPP Exception,该功能已经集成到了我开发的SDK里面,效果如下图所示:
软件测试之SDK开发(ios)——Cpp Exception捕获-LMLPHP
通过CPP Exception的捕获成功将异常的堆栈进行还原,进入到Signal(SIGABRT),可以发现通过signal机制捕获的堆栈出现了异常堆栈缺失问题。如下图所示:
软件测试之SDK开发(ios)——Cpp Exception捕获-LMLPHP

08-25 14:20