iOS【Crash捕获】

作者: NJ_墨 | 来源:发表于2020-06-11 12:52 被阅读0次

    摘录: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 <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
    
    

    相关文章

      网友评论

        本文标题:iOS【Crash捕获】

        本文链接:https://www.haomeiwen.com/subject/adovtktx.html