iOS-App端崩溃日志的收集+Xcode工具symbolicatecrash解析iOS Crash文件

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

收集方式

Mach异常+Unix信号方式

Mach异常:Mach异常是指最底层的内核级异常,被定义在 下 。
每个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工具symbolicatecrash解析iOS Crash文件)