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)

image

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 
 
@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

你可能感兴趣的:(iOS【Crash捕获】)