iOS【Crash捕获】
摘录:lltree
Crash分类
Crash的主要原因是你的应用收到了未处理的信号。未处理信号可能来源于三个地方:kernel
、其他进程
、以及App本身
。因此,crash异常也分为三种:
Mach异常:底层内核级异常。用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常。
Unix信号:又称BSD信号。它是UNIX层的异常信号标准,通过方法threadsignal()将信号投递到出错线程。可以通过方法signal(x, SignalHandler)来捕获single。
NSException:应用级异常。它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获。
三者关系
Mach异常、Unix信号、NSException异常是什么?它们之间有什么相互关系?
Darwin是Mac OS和iOS的操作系统,而XNU是Darwin操作系统的内核部分。XNU是混合内核,兼具宏内核和微内核的特性,而Mach即为其微内核。
Mac可执行下述命令查看Darwin版本号。
fengqican@fengqicandeMacBook-Pro ~ % system_profiler SPSoftwareDataType
Software:
System Software Overview:
System Version: macOS 10.15.5 (19F96)
Kernel Version: Darwin 19.5.0
Boot Volume: Macintosh HD
Boot Mode: Normal
Computer Name: 风起残的MacBook Pro
User Name: occ (fengqican)
Secure Virtual Memory: Enabled
System Integrity Protection: Enabled
Time since boot: 7 days 20:27
fengqican@fengqicandeMacBook-Pro ~ %
关系:
1 Mach异常是内核态的异常,属于底层异常。
2 转换Unix信号是为了兼容更为流行的POSIX标准(SUS规范),这样不必了解Mach内核也可以通过Unix信号的方式来兼容开发。
3 因为硬件产生的信号(通过CPU陷阱)被Mach层捕获,然后才转换为对应的Unix信号;苹果为了统一机制,于是操作系统和用户产生的信号(通过调用kill和pthread_kill)也首先沉下来被转换为Mach异常,再转换为Unix信号。
Carsh传递流程:
硬件产生信号或者kill或pthread_kill信号 --> Mach异常 --> Unix信号(SIGABRT)
Mach异常捕获
image- 1、硬件处理器陷阱产生的信号被
Mach层
捕获 - 2、Mach异常处理程序
exception_triage()
通过调用exception_deliver()
首先尝试将异常抛给thread端口、然后尝试抛给task端口,最后再抛给host端口(默认端口),exception_deliver
通过调用mach_exception_raise
,触发异常; - 3、异常在内核中以消息机制进行处理,通过
task_set_exception_posrts()
设置自定义的接收Mach异常消息的端口,相当于插入了一个exception
处理程序。
实现:mach异常
以消息机制处理
而不是通过函数调用
,exception messages
可以被转发到先前注册的Mach exception处理程序
。这意味着你可以插入一个exception处理程序
,而不干扰现有的无论是调试器或Apple's crash reporter。
Signal异常捕获
Signal是Unix标准下的处理机制,让开发者不必关系底层内核相关。为了维护一个统一的机制,操作系统和用户尝试的信号首先被转换为Mach异常,然后再转换为信号(Signals)。
Mach 异常在Mach层被捕获并抛出后,会在BSD层被catch_mach_exception_raise处理,并通过ux_exception()将异常转换为对应的UNIX信号,并通过threadsignal()将信号投递到出错线程,iOS中的 POSIX API 就是通过 Mach 之上的 BSD 层实现的
NSException异常捕获
NSException异常属于OC层异常。该异常在OC层如果有对应的NSException(OC异常),就转换成OC异常,OC异常可以在OC层得到处理;如果OC异常一直得不到处理,程序会强行发送SIGABRT信号中断程序。在OC层如果没有对应的NSException,就只能让Unix标准的signal机制来处理了。
代码示例:注册过程
NSSetUncaughtExceptionHandler(&handleUncaughtException);
代码示例:捕获处理
void HandleException(NSException *exception){
// 异常的堆栈信息
NSArray *stackArray = [exception callStackSymbols];
// 出现异常的原因
NSString *reason = [exception reason];
// 异常名称
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
[UncaughtExceptionHandler saveCreash:exceptionInfo];
}
/**
2020-06-11 12:08:46.487981+0800 LXDAppMonitor[40850:3018444] ============崩溃==========
2020-06-11 12:08:46.488272+0800 LXDAppMonitor[40850:3018444]
(
0 CoreFoundation 0x00007fff23e3cf0e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff50ba89b2 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23e5dc34 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x00007fff23e4190c ___forwarding___ + 1436
4 CoreFoundation 0x00007fff23e43bf8 _CF_forwarding_prep_0 + 120
5 LXDAppMonitor 0x00000001024e1390 -[ViewController tableView:didSelectRowAtIndexPath:] + 192
6 UIKitCore 0x00007fff48e87362 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:] + 1354
7 UIKitCore 0x00007fff48e86e01 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 97
8 UIKitCore 0x00007fff48e87742 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 334
9 UIKitCore 0x00007fff48c94bfa _runAfterCACommitDeferredBlocks + 352
10 UIKitCore 0x00007fff48c85388 _cleanUpAfterCAFlushAndRunDeferredBlocks + 248
11 UIKitCore 0x00007fff48cb5b91 _afterCACommitHandler + 85
12 CoreFoundation 0x00007fff23da0127 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
13 CoreFoundation 0x00007fff23d9abde __CFRunLoopDoObservers + 430
14 CoreFoundation 0x00007fff23d9b12a __CFRunLoopRun + 1226
15 CoreFoundation 0x00007fff23d9a944 CFRunLoopRunSpecific + 404
16 GraphicsServices 0x00007fff38ba6c1a GSEventRunModal + 139
17 UIKitCore 0x00007fff48c8b9ec UIApplicationMain + 1605
18 LXDAppMonitor 0x00000001024ef790 main + 112
19 libdyld.dylib 0x00007fff51a231fd start + 1
)
-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x7fff8062d430
NSInvalidArgumentException
2020-06-11 12:08:46.495015+0800 LXDAppMonitor[40850:3018444] ============崩溃==========
*/
void (*other_exception_caught_handler)(NSException * exception) = NULL;
static void __lxd_exception_caught(NSException * exception) {
NSLog(@"============崩溃==========");
NSArray *arr = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSLog(@"\n%@\n%@\n%@",arr,reason,name);
NSLog(@"============崩溃==========");
NSDictionary * infoDict = [[NSBundle mainBundle] infoDictionary];
NSString * appInfo = [NSString stringWithFormat: @"Device: %@\nOS Version: %@\nOS System: %@", [UIDevice currentDevice].model, infoDict[@"CFBundleShortVersionString"], [[UIDevice currentDevice].systemName stringByAppendingString: [UIDevice currentDevice].systemVersion]];
if (other_exception_caught_handler != NULL) {
(*other_exception_caught_handler)(exception);
}
}
CF_INLINE NSString * __signal_name(int signal) {
switch (signal) {
/// 非法指令
case SIGILL:
return @"SIGILL";
/// 计算错误
case SIGFPE:
return @"SIGFPE";
/// 总线错误
case SIGBUS:
return @"SIGBUS";
/// 无进程接手数据
case SIGPIPE:
return @"SIGPIPE";
/// 无效地址
case SIGSEGV:
return @"SIGSEGV";
/// abort信号
case SIGABRT:
return @"SIGABRT";
default:
return @"Unknown";
}
}
CF_INLINE NSString * __signal_reason(int signal) {
switch (signal) {
/// 非法指令
case SIGILL:
return @"Invalid Command";
/// 计算错误
case SIGFPE:
return @"Math Type Error";
/// 总线错误
case SIGBUS:
return @"Bus Error";
/// 无进程接手数据
case SIGPIPE:
return @"No Data Receiver";
/// 无效地址
case SIGSEGV:
return @"Invalid Address";
/// abort信号
case SIGABRT:
return @"Abort Signal";
default:
return @"Unknown";
}
}
static void __lxd_signal_handler(int signal) {
__lxd_exception_caught([NSException exceptionWithName: __signal_name(signal) reason: __signal_reason(signal) userInfo: nil]);
[LXDCrashMonitor _killApp];
}
#pragma mark - Public
+ (void)startMonitoring {
other_exception_caught_handler = NSGetUncaughtExceptionHandler();
NSSetUncaughtExceptionHandler(__lxd_exception_caught);
signal(SIGILL, __lxd_signal_handler);
signal(SIGFPE, __lxd_signal_handler);
signal(SIGBUS, __lxd_signal_handler);
signal(SIGPIPE, __lxd_signal_handler);
signal(SIGSEGV, __lxd_signal_handler);
signal(SIGABRT, __lxd_signal_handler);
}
#pragma mark - Private
+ (void)_killApp {
NSSetUncaughtExceptionHandler(NULL);
signal(SIGILL, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGABRT, SIG_DFL);
kill(getpid(), SIGKILL);
}
主打抛出异常
//创建异常
NSString *exceptionName = @"一个异常";
NSString *excaptionReason = @"我要让程序崩溃";
NSDictionary *exceptionUserInfo = nil;
NSException *exception = [NSException exceptionWithName:exceptionName reason:excaptionReason userInfo:exceptionUserInfo];
//抛出异常
@throw exception;
NSMutableArray *array = [NSMutableArray array];
NSString *nilStr = nil;
@try {
//有可能出现异常的代码,这里写的代码一定会出现问题
[array insertObject:nilStr atIndex:0];
} @catch(NSException *exception) {
//如果@try的代码出现异常,就会执行这里的代码,也就可以在这里进行相应的操作
NSLog(@"%@",exception.reason);
//如果想要抛出异常就执行如下代码,程序就会崩溃,便于调试
//@throw exception;
} @finally {
//这里的代码一定会执行
NSLog(@"已成功处理异常");
}
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Extension)
+ (void)load {
Class arrayMClass = NSClassFromString(@"__NSArrayM");
//获取系统的添加元素的方法
Method addObject = class_getInstanceMethod(arrayMClass, @selector(addObject:));
//获取我们自定义添加元素的方法
Method avoidCrashAddObject = class_getInstanceMethod(arrayMClass, @selector(avoidCrashAddObject:));
//将两个方法进行交换
method_exchangeImplementations(addObject, avoidCrashAddObject);
}
- (void)avoidCrashAddObject:(id)object {
@try {
//可能出现异常的方法 比如数组不能添加空对象 所以addObject可能会出现异常
[self avoidCrashAddObject:object];//本质是调用addObject
} @catch (NSException *exception) {
//捕捉到异常后对该异常进行处理
NSLog(@"\n异常名称:%@\n异常原因:%@",exception.name,exception.reason);
} @finally {
//这里的代码一定会执行,可以进行相应的操作
}
}
@end