一、引言
在 iOS 应用程序开发过程中,我们难免会碰到因各种异常而导致应用程序崩溃的情况。
对于开发过程中遇到的崩溃,我们可以根据本地崩溃信息快速定位问题。但对于线上版本发生的一些崩溃情况,我们只能通过收集崩溃信息来分析具体的原因。虽然 Apple 提供了崩溃信息上报的功能,但是并非所有的用户都开启了该功能。因此,对于数据采集 SDK 来说,采集崩溃信息并上报是一项必不可少的功能。
下面针对神策分析 iOS SDK 崩溃采集模块进行解析,希望能够给大家提供一些参考。
二、崩溃类型
采集应用程序的崩溃信息,主要分为以下两种场景:
-
NSException 异常;
-
Unix 信号异常。
设计崩溃采集方案之前,我们不妨先认识一下 NSException 和 Unix 信号。
2.1 NSException
NSException[1] 是 Foundation 框架提供的一个类。用于封装一些异常信息,在需要的时候向外抛出。封装的异常信息包括异常名称、异常原因、调用堆栈。
@interface NSException : NSObject <NSCopying, NSSecureCoding>
@property (readonly, copy) NSExceptionName name;
@property (nullable, readonly, copy) NSString *reason;
@property (readonly, copy) NSArray<NSString *> *callStackSymbols;
@end
在 iOS 应用程序中,最常见的就是通过 @throw 抛出的异常,如图 2-1 所示:
图 2-1 异常处理流程(图片来源于 Apple 开发者文档[2] )
比如常见的数组越界访问异常:
@throw [NSException exceptionWithName:@"NSRangeException" reason:@"index 2 beyond bounds [0 .. 1]" userInfo:nil];
运行程序会出现如下异常信息:
Terminating app due to uncaught exception 'NSRangeException', reason: 'index 2 beyond bounds [0 .. 1]'
terminating with uncaught exception of type NSException
2.2 Unix 信号
在 iOS 系统自动采集的崩溃日志中,经常可以看到类似下面的日志:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERNINVALIDADDRESS at 0x0000000001000010
VM Region Info: 0x1000010is not in any region. Bytes before following region: 4283498480
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START TEXT 0000000100510000-0000000100514000 [16K] r-x/r-x SM=COW
.app/Ekuaibao
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAl, Code 0xb Terminating Process: exc handler [21776]
Triggered by Thread: 9
其中,Exception Type 中的两个字段 EXC_BAD_ACCESS 和 SIGSEGV 分别指 Mach 异常和 Unix 信号。
那什么是 Mach 异常和 Unix 信号呢?
Mach 是 macOS 和 iOS 操作系统的微内核,Mach 异常是最底层的内核级异常[3]。Mach 异常会被转换成相应的 Unix 信号,并传递给出错的线程。上述 Exception Type 中的 EXC_BAD_ACCESS 是 Mach 层的异常,被转换成了 Unix 信号 SIGSEGV,然后传递给出错的线程。之所以会将 Mach 异常转换成 Unix 信号,是为了兼容 POSIX 标准(SUS 规范)[4],这样一来,开发者即使不了解 Mach 内核也可以通过 Unix 信号的方式进行兼容开发。
Unix 信号的种类有很多,在 iOS 应用程序中,常见的 Unix 信号[5]有如下几种:
-
SIGILL:程序非法指令信号,通常是因为可执行文件本身出现错误,或者试图执行数据段。堆栈溢出时也有可能产生该信号;
-
SIGABRT:程序中止命令中止信号,调用 abort 函数时产生该信号;
-
SIGBUS