美文网首页
iOS-App端崩溃日志的收集+Xcode工具symbolica

iOS-App端崩溃日志的收集+Xcode工具symbolica

作者: 鼬殿 | 来源:发表于2019-03-29 11:48 被阅读0次

    以前对于应用层的崩溃日志,没有主动收集过,只是下过应用沙盒下的日志,还有就是集成了bugly来应对测试和线上的崩溃,最近是碰到一个需求,在sdk里面上报应用层的崩溃日志,lz就了解一下,与大家分享一下。

    收集方式

    Mach异常+Unix信号方式

    Mach异常:Mach异常是指最底层的内核级异常,被定义在 <mach/exception_types.h>下 。
    每个threadtaskhost都有一个异常端口数组,Mach的部分API暴露给了 用户态 ,用户态的开发者可以直接通过Mach API设置threadtaskhost的异常端口,来捕获Mach异常,抓取Crash事件。

    UNIX信号: 所有的Mach异常都在host层被转换成相应的UNIX信号,并通过threadsignal将信号投递到出错的线程。(iOS中的 POSIX API 就是通过 Mach 之上的 BSD 层实现的。)

    因此: EXC_BAD_ACCESS (SIGSEGV)表示的意思是:Mach层的EXC_BAD_ACCESS异常在host层被转换成了SIGSEGV信号,并投递到出错的线程。

    Mach异常和UNIX信号都可以抓取crash事件,这两种方式哪个更好?

    优选Mach异常,因为Mach异常处理会先于Unix信号处理发生,如果Mach异常的handler让程序exit了,那么Unix信号就永远不会到达这个进程了。

    多个Crash日志收集服务的冲突

    有时候为了防止与三方的一些日志收集服务的冲突,我们需要拿到之前的服务注册的handler,然后备份,等自己的回调函数处理完后再把之前备份的handler注册回去。

    相关代码如下:

    + (void)defaultHandler {
        //保存handler
        previousHandler = NSGetUncaughtExceptionHandler();
        //1.捕获一些异常导致的崩溃
        NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
        // 2.捕获非异常情况,通过signal传递出来的崩溃
        signal(SIGABRT, SignalHandler);
        signal(SIGILL, SignalHandler);
        signal(SIGSEGV, SignalHandler);
        signal(SIGFPE, SignalHandler);
        signal(SIGBUS, SignalHandler);
        signal(SIGPIPE, SignalHandler);
    }
    

    应用级的异常NSException

    // 崩溃时的回调函数
    void UncaughtExceptionHandler(NSException * exception) {
        // 获取异常的堆栈信息
        NSArray *callStack = [exception callStackSymbols];
        //获取异常的名称
        NSString *exceptionName = [exception name];
        //获取异常的原因
        NSString *excepReason = [exception reason];
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
        [userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];
        NSException *customException = [NSException exceptionWithName:exceptionName reason:excepReason userInfo:userInfo];
        [CatchCrash performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
        //将异常塞给之前的第三方
        if (previousHandler) {
             previousHandler(exception);
        }
    }
    

    通过系统的函数我们很容易就能获取到异常的堆栈信息,原因,名称,例如我们常见的 数组越界,插入空的数据,未实现的方法等都会走这个方法,lz在下面的demo里面也写的例子演示

    Unix信号

    void SignalHandler(int signal)
    {
        // 这种情况的崩溃信息,就另某他法来捕获吧
        NSArray *callStack = [CatchCrash backtrace];
        NSException *customException = [NSException exceptionWithName:kSignalExceptionName
                                                               reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
                                                             userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
        [CatchCrash performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
    }
    
    + (NSArray *)backtrace
    {
        void* callstack[128];
        int frames = backtrace(callstack, 128);
        char **strs = backtrace_symbols(callstack, frames);
        NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
        for (int i = 0; i < frames; i++) {
            [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
        }
        free(strs);
        return backtrace;
    }
    

    关于Unix信号方式方式捕获的异常,通常见于EXC_BAD_ACESS野指针错误,demo里面也有例子

    关于signal信号的捕捉,在Xcode调试时,Debugger模式会先于我们的代码catch到所有的crash
    想看看到此类崩溃,demo不能连着xcode,不然看不到

    对于搜集到的崩溃日志的处理

    + (void)handleException:(NSException *)exception
    {
        NSString *exceptionInfo = [NSString stringWithFormat:@"========异常错误报告========:\n%@\n%@\n%@",[exception name],[exception reason],[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
        NSString * path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"];
        // 将一个txt文件写入沙盒
        [exceptionInfo writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
    

    Mach异常+Unix信号方式产生的崩溃,lz放到了沙盒下面,在下一次程序启动的时候就上传,上传完成之后就移除沙盒下面的文件

    参看链接
    demo传送门

    本地日志解析

    在本地新建一个项目,写一个简单的崩溃程序

    NSString *str = nil;
    NSDictionary *dict = @{@"test":str};
    

    然后用Xcode打包,把生成的ipa上传到蒲公英,然后下载到手机,点开运行一下,桌面创建一个文件夹crash

    • 获取crash文件
      Xcode->Window->Devices and Simulators->View Device Logs->右键导出crash文件。
    • 获取dSYM文件
      Window->Organizer->.xcarchive->右键显示包内容->dSYMs文件->xxx.app.dSYM
    • symbolicatecrash工具
      通过find找到symbolicatecrash工具的路径
    find /Applications/Xcode.app -name symbolicatecrash -type f
    

    前往文件夹->粘贴路径->symbolicatecrash工具
    把crash文件+dSYM文件+symbolicatecrash工具copy到刚才创建的crash文件
    cd到crash文件,执行命令

    ./symbolicatecrash ./*.crash ./*.app.dSYM > symbol.crash
    

    如果出现 Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69
    执行命令

    export DEVELOPER_DIR=/Applications/XCode.app/Contents/Developer
    

    然后在执行

    ./symbolicatecrash ./*.crash ./*.app.dSYM > symbol.crash
    

    可以看到本地生成的日志分析文件symbol.crash文件

    Application Specific Information:
    abort() called
    
    Last Exception Backtrace:
    0   CoreFoundation                  0x1a067d27c __exceptionPreprocess + 228
    1   libobjc.A.dylib                 0x19f8579f8 objc_exception_throw + 55
    2   CoreFoundation                  0x1a05f6ce8 _CFThrowFormattedException + 111
    3   CoreFoundation                  0x1a057e9a8 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 351
    4   CoreFoundation                  0x1a056f584 +[NSDictionary dictionaryWithObjects:forKeys:count:] + 63
    5   testaaa                         0x102a5a548 _hidden#0_ + 25928 (__hidden#4_:22)
    6   UIKitCore                       0x1cca0dfc8 -[UIViewController loadViewIfRequired] + 1011
    7   UIKitCore                       0x1cca0e3cc -[UIViewController view] + 27
    8   UIKitCore                       0x1ccfecba4 -[UIWindow addRootViewControllerViewIfPossible] + 135
    9   UIKitCore                       0x1ccfed14c -[UIWindow _setHidden:forced:] + 271
    10  UIKitCore                       0x1ccffda28 -[UIWindow makeKeyAndVisible] + 47
    11  UIKitCore                       0x1ccfb0648 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 3531
    12  UIKitCore                       0x1ccfb5d20 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1539
    13  UIKitCore                       0x1cc8792dc __111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 775
    14  UIKitCore                       0x1cc881874 +[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 159
    15  UIKitCore                       0x1cc878f60 -[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:] + 235
    16  UIKitCore                       0x1cc879850 -[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:] + 1063
    17  UIKitCore                       0x1cc877b9c __82-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:]_block_invoke + 743
    18  UIKitCore                       0x1cc877864 -[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:] + 427
    19  UIKitCore                       0x1cc87c3a4 __125-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:]_block_invoke + 219
    20  UIKitCore                       0x1cc87d188 _performActionsWithDelayForTransitionContext + 111
    21  UIKitCore                       0x1cc87c25c -[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:] + 243
    22  UIKitCore                       0x1cc880f5c -[_UICanvas scene:didUpdateWithDiff:transitionContext:completion:] + 359
    23  UIKitCore                       0x1ccfb4328 -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 539
    24  UIKitCore                       0x1ccbb0ba8 -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 359
    25  FrontBoardServices              0x1a2ff89fc -[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 439
    26  FrontBoardServices              0x1a300240c __56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 255
    27  FrontBoardServices              0x1a3001c14 __40-[FBSWorkspace _performDelegateCallOut:]_block_invoke + 63
    28  libdispatch.dylib               0x1a00bd7d4 _dispatch_client_callout + 15
    29  libdispatch.dylib               0x1a00625d8 _dispatch_block_invoke_direct$VARIANT$mp + 223
    30  FrontBoardServices              0x1a3033040 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 39
    31  FrontBoardServices              0x1a3032cdc -[FBSSerialQueue _performNext] + 407
    32  FrontBoardServices              0x1a3033294 -[FBSSerialQueue _performNextFromRunLoopSource] + 51
    33  CoreFoundation                  0x1a060f018 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 23
    34  CoreFoundation                  0x1a060ef98 __CFRunLoopDoSource0 + 87
    35  CoreFoundation                  0x1a060e880 __CFRunLoopDoSources0 + 175
    36  CoreFoundation                  0x1a06097bc __CFRunLoopRun + 1003
    37  CoreFoundation                  0x1a06090b0 CFRunLoopRunSpecific + 435
    38  GraphicsServices                0x1a280979c GSEventRunModal + 103
    39  UIKitCore                       0x1ccfb7978 UIApplicationMain + 211
    40  testaaa                         0x102a5a5d4 main + 26068 (__hidden#7_:14)
    41  libdyld.dylib                   0x1a00ce8e0 start + 3
    
    [__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 351
    [NSDictionary dictionaryWithObjects:forKeys:count:] + 63
    

    从上面的日志解析文件可以很明显的看出是字典初始化插入数据为空,但是没有定位到具体的代码,这一点比较难受

    相关文章

      网友评论

          本文标题:iOS-App端崩溃日志的收集+Xcode工具symbolica

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