iOS问题分析技巧-crash日志符号化
iOS问题分析技巧-crash日志符号化
iOS开发需要不停发版本,开发者要面临线上各种版本的奔溃日志(crash log),解决奔溃问题是移动开发者最日常的工作之一.
在实际的项目开发中,崩溃问题,依赖xcode,依赖于系统记录的崩溃日志或错误堆栈,在本地开发调试阶段,是没有问题的。
如果在发布的线上版本出现崩溃问题,开发者是无法即时准确的取得错误堆栈的,需要获取到crash日志,进行相应处理。
简单的崩溃还好说,复杂的崩溃就需要我们通过解析Crash文件来分析了,解析Crash文件在iOS开发中是比较常见的。对iOS的crash日志进行分析,做符号化,才能准确定位到发生崩溃的原因。
一、crash日志的结构
crash日志主要由6部分组成:
1、进程信息
2、基本信息
3、异常信息
4、线程回溯
5、crash调用堆栈(全是地址信息,需要使用符号表转成可读的)
6、动态库信息(第5部分依赖的库)
iOS的符号化也主要是根据第5和第6部分进行,只有把第5部分后面的二进制的地址信息映射成代码信息,才能定位到发生crash的原因。
// 1.进程信息 Incident Identifier: 340804F7-A1F5-49FE-92D4-31EB6348B1D8 CrashReporter Key: c1c8f486a95be9952fd5e9a2764b4d5a3e639f04 Hardware Model: iPad2,5 Process: myDemo [1435] Path: /private/var/containers/Bundle/Application/AC338CCD-4F64-4BAA-8BA0-4017D13EDE3E/myDemo.app/myDemo Identifier: com.huya.enterprise.myDemo Version: 632 (1.1.1) Code Type: ARM (Native) Parent Process: launchd [1] // 2.基本信息 Date/Time: 2018-04-11 17:55:14.14 +0800 Launch Time: 2018-04-11 17:54:34.34 +0800 OS Version: iOS 9.3.5 (13G36) Report Version: 104 // 3.异常信息、线程回溯 Exception Type: EXC_CRASH (SIGSEGV) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Triggered by Thread: 1 Filtered syslog: None found // 4.Crash调用堆栈,需要将堆栈的二进制地址转成可读的 Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libobjc.A.dylib 0x20b1067a objc_retain + 10 1 myDemo 0x000b1c46 0x76000 + 244806 2 UIKit 0x25a0b11a -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1126 3 UIKit 0x25ac6476 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 306 4 UIKit 0x25b7f4c8 _runAfterCACommitDeferredBlocks + 268 5 UIKit 0x25b8b7da _cleanUpAfterCAFlushAndRunDeferredBlocks + 90 6 UIKit 0x258c9b1c _afterCACommitHandler + 84 7 CoreFoundation 0x2131d6c8 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 20 8 CoreFoundation 0x2131b9cc __CFRunLoopDoObservers + 280 9 CoreFoundation 0x2131bdfe __CFRunLoopRun + 958 10 CoreFoundation 0x2126b228 CFRunLoopRunSpecific + 520 11 CoreFoundation 0x2126b014 CFRunLoopRunInMode + 108 12 GraphicsServices 0x2285bac8 GSEventRunModal + 160 13 UIKit 0x2593f188 UIApplicationMain + 144 14 myDemo 0x000e0250 0x76000 + 434768 15 libdyld.dylib 0x20f13872 start + 2 Thread 1 name: Dispatch queue: com.yy.hiido.ConfigsByAddress Thread 1 Crashed: 0 libsystem_kernel.dylib 0x20fd2920 semaphore_wait_trap + 8 1 libdispatch.dylib 0x20eda94a _dispatch_semaphore_wait_slow + 190 2 CFNetwork 0x218a2862 CFURLConnectionSendSynchronousRequest + 270 3 CFNetwork 0x218bcf56 +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 94 4 myDemo 0x004212fc 0x76000 + 3846908 5 myDemo 0x004215e4 0x76000 + 3847652 6 libdispatch.dylib 0x20ed4df4 _dispatch_barrier_sync_f_slow + 540 7 myDemo 0x0042155a 0x76000 + 3847514 8 myDemo 0x004216a4 0x76000 + 3847844 9 myDemo 0x004182f8 0x76000 + 3810040 10 myDemo 0x00418338 0x76000 + 3810104 11 myDemo 0x00429222 0x76000 + 3879458 12 myDemo 0x00421d1a 0x76000 + 3849498 13 libdispatch.dylib 0x20ec9822 _dispatch_call_block_and_release + 10 14 libdispatch.dylib 0x20ed85e8 _dispatch_root_queue_drain + 1560 15 libdispatch.dylib 0x20ed7fcc _dispatch_worker_thread3 + 96 16 libsystem_pthread.dylib 0x2108db28 _pthread_wqthread + 1024 17 libsystem_pthread.dylib 0x2108d718 start_wqthread + 8 // 5.动态库信息 Binary Images: 0x76000 - 0x1525fff myDemo armv7 <91f5d874b7ad3b4cb88fb982cd5a0cf8> /var/containers/Bundle/Application/AC338CCD-4F64-4BAA-8BA0-4017D13EDE3E/myDemo.app/myDemo 0x1fe72000 - 0x1fe99fff dyld armv7 <146dc907cdf7350eb7cf92a77291119f> /usr/lib/dyld 0x20a84000 - 0x20a85fff libSystem.B.dylib armv7 <39d6d6f7c2ac3de8bb29c40a1b66368a> /usr/lib/libSystem.B.dylib 0x20a86000 - 0x20ad1fff libc++.1.dylib armv7 <017dba6c16b63f9ebecb9ddd0d0a4520> /usr/lib/libc++.1.dylib ...
对crash日志进行符号化之后的结果如下:
// 1.进程信息 Incident Identifier: 340804F7-A1F5-49FE-92D4-31EB6348B1D8 CrashReporter Key: c1c8f486a95be9952fd5e9a2764b4d5a3e639f04 Hardware Model: iPad2,5 Process: keke [1435] Path: /private/var/containers/Bundle/Application/AC338CCD-4F64-4BAA-8BA0-4017D13EDE3E/keke.app/keke Identifier: com.huya.enterprise.keke Version: 632 (1.1.1) Code Type: ARM (Native) Parent Process: launchd [1] // 2.基本信息 Date/Time: 2018-04-11 17:55:14.14 +0800 Launch Time: 2018-04-11 17:54:34.34 +0800 OS Version: iOS 9.3.5 (13G36) Report Version: 104 // 3.异常信息、线程回溯 Exception Type: EXC_CRASH (SIGSEGV) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Triggered by Thread: 1 Filtered syslog: None found // 4.Crash调用堆栈,需要将堆栈的二进制地址转成可读的 Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libobjc.A.dylib 0x20b1067a objc_retain + 10 1 keke 0x000b1c46 -[HomeViewController tableView:didSelectRowAtIndexPath:] + 244806 (HomeViewController.m:465) 2 UIKit 0x25a0b11a -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1126 3 UIKit 0x25ac6476 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 306 4 UIKit 0x25b7f4c8 _runAfterCACommitDeferredBlocks + 268 5 UIKit 0x25b8b7da _cleanUpAfterCAFlushAndRunDeferredBlocks + 90 6 UIKit 0x258c9b1c _afterCACommitHandler + 84 7 CoreFoundation 0x2131d6c8 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 20 8 CoreFoundation 0x2131b9cc __CFRunLoopDoObservers + 280 9 CoreFoundation 0x2131bdfe __CFRunLoopRun + 958 10 CoreFoundation 0x2126b228 CFRunLoopRunSpecific + 520 11 CoreFoundation 0x2126b014 CFRunLoopRunInMode + 108 12 GraphicsServices 0x2285bac8 GSEventRunModal + 160 13 UIKit 0x2593f188 UIApplicationMain + 144 14 keke 0x000e0250 main + 434768 (main.m:16) 15 libdyld.dylib 0x20f13872 start + 2 Thread 1 name: Dispatch queue: com.yy.hiido.ConfigsByAddress Thread 1 Crashed: 0 libsystem_kernel.dylib 0x20fd2920 semaphore_wait_trap + 8 1 libdispatch.dylib 0x20eda94a _dispatch_semaphore_wait_slow + 190 2 CFNetwork 0x218a2862 CFURLConnectionSendSynchronousRequest + 270 3 CFNetwork 0x218bcf56 +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 94 4 keke 0x004212fc +[HDANA_FileHelper getConfigsByAddressAA:savedToFile:updateThan:] + 3846908 (FileHelper.m:114) 5 keke 0x004215e4 __65-[HDANA_FileHelper getConfigsByAddressBB:savedToFile:updateThan:]_block_invoke + 3847652 (FileHelper.m:147) 6 libdispatch.dylib 0x20ed4df4 _dispatch_barrier_sync_f_slow + 540 7 keke 0x0042155a -[HDANA_FileHelper getConfigsByAddressBB:savedToFile:updateThan:] + 3847514 (FileHelper.m:152) 8 keke 0x004216a4 +[HDANA_FileHelper getConfigsByAddress:savedToFile:updateThan:] + 3847844 (FileHelper.m:157) 9 keke 0x004182f8 +[UtilsHelper getAppConfig] + 3810040 (UtilsHelper.m:226) 10 keke 0x00418338 +[UtilsHelper getSDKConfig] + 3810104 (UtilsHelper.m:234) 11 keke 0x00429222 __30-[HDANA_HDSDK sdkConfigAction]_block_invoke + 3879458 (HDSDK.m:1535) 12 keke 0x00421d1a __28-[HDANA_HDSDK global_queue:]_block_invoke + 3849498 (HDSDK.m:146) 13 libdispatch.dylib 0x20ec9822 _dispatch_call_block_and_release + 10 14 libdispatch.dylib 0x20ed85e8 _dispatch_root_queue_drain + 1560 15 libdispatch.dylib 0x20ed7fcc _dispatch_worker_thread3 + 96 16 libsystem_pthread.dylib 0x2108db28 _pthread_wqthread + 1024 17 libsystem_pthread.dylib 0x2108d718 start_wqthread + 8 // 5.动态库信息 Binary Images: 0x76000 - 0x1525fff keke armv7 <91f5d874b7ad3b4cb88fb982cd5a0cf8> /var/containers/Bundle/Application/AC338CCD-4F64-4BAA-8BA0-4017D13EDE3E/keke.app/keke 0x1fe72000 - 0x1fe99fff dyld armv7 <146dc907cdf7350eb7cf92a77291119f> /usr/lib/dyld 0x20a84000 - 0x20a85fff libSystem.B.dylib armv7 <39d6d6f7c2ac3de8bb29c40a1b66368a> /usr/lib/libSystem.B.dylib 0x20a86000 - 0x20ad1fff libc++.1.dylib armv7 <017dba6c16b63f9ebecb9ddd0d0a4520> /usr/lib/libc++.1.dylib ...
二、crash日志的获取方式
在iOS中获取崩溃信息的方式有很多,比较常见的是使用友盟、百度等第三方分析工具,或者自己收集崩溃信息并上传公司服务器。
下面列举一些我们常用的崩溃分析方式:
1、使用友盟、百度等第三方崩溃统计工具。
2、自己实现应用内崩溃收集,并上传服务器。
3、Xcode-Devices中直接查看某个设备的崩溃信息。
4、使用苹果提供的Crash崩溃收集服务。(少用)
1、自己实现应用内奔溃收集
苹果给我们提供了异常处理的类,NSException类。这个类可以创建一个异常对象,也可以通过这个类获取一个异常对象。
这个类中我们最常用的还是一个获取崩溃信息的C函数,我们可以通过这个函数在程序发生异常的时候收集这个异常。
// 将系统提供的获取崩溃信息函数写在这个方法中,以保证在程序开始运行就具有获取崩溃信息的功能 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 将下面C函数的函数地址当做参数 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); return YES; } // 设置一个C函数,用来接收崩溃信息 void UncaughtExceptionHandler(NSException *exception){ // 可以通过exception对象获取一些崩溃信息,我们就是通过这些崩溃信息来进行解析的,例如下面的symbols数组就是我们的崩溃堆栈。 NSArray *symbols = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; } //我们也可以通过下面方法获取崩溃统计的函数指针: NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
2、通过Xcode查看设备crash信息
如果是我们自己直接使用手机连接崩溃或者崩溃之后连接手机,选择window-> devices -> 选择自己的手机 -> view device logs 就可以查看我们的崩溃信息了。
特别说明:
1、如果手机上app是这台电脑安装的,或者打包的,那么app奔溃之后,通过这台电脑xcode导出的crash日志信息,系统已经为我们符号化好了;
如果还是没有符号化完毕,我们选择文件,然后右击选择Re-Sysbomlicate就可以。
2、如果手机上app是其它电脑安装或者打包的,那么app奔溃之后,通过这台电脑xcode导出的crash日志信息,是还没有符号化的,需要我们导出之后,通过symbolicatecrash命令和dSYM符号表进行符号化处理,才能准确定位到crash的位置原因。
因为当前电脑的xcode编译打包的app,会在.app目录下,生成对应的dSYM文件,通过xcode查看的crash log系统已经通过对应的dSYM符号表帮我们符号化了。而在其它电脑,并这个设备crash文件相对应的dSYM符号表,必须要crash文件对应的.app文件的uuid和对应的dSYM文件的uuid一致,才能进行符号化。
(实际上Xcode的Organizer内置了symbolicatecrash工具,所以开发者才可以直接看到符号化的错误日志。)
3、通过bugly上传对应dSYM符号表
可以参考下面这篇文章
Bugly iOS 符号表手动配置详细教程
如果拿到的是未经过符号化的crash文件,就需要用到符号表,那么符号表是什么呢?有什么作用?如何生成?怎么使用符号表?
三、crash日志符号化的过程
1、什么是符号表
符号表就是指在Xcode项目编译后,在编译生成的二进制文件.app的同级目录下生成的同名的.dSYM文件。
.dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件。
一般地,Xcode项目每次编译后,都会生成一个新的.dSYM文件。
因此,App的每一个发布版本,都需要备份一个对应的.dSYM文件,以便后续调试定位问题。
项目每一次编译后,.app和.dSYM成对出现,并且二者有相同的UUID值,以标识是同一次编译的结果。
UUID值可以使用dwarfdump —uuid来检查:
// 查看 .dSYM文件的uuid dwarfdump --uuid **.app.dSYM (**为你app) //终端输出结果如下: bogon:symbolicateCrash wugl$ dwarfdump --uuid keke3.app.dSYM/ UUID: 427C6848-EFF8-3F66-A29E-C0DE376FBDD7 (armv7) keke3.app.dSYM/Contents/Resources/DWARF/keke
// 查看 .app文件的uuid cd **.app(可以看到.app目录中有一个**文件) dwarfdump --uuid ** (**为你app) // 终端输出结果如下: bogon:keke.app wugl$ dwarfdump --uuid keke UUID: 7B32A9C2-ABCA-3656-84D0-714100E092CB (arm64) keke
2、符号表有什么作用
在Xcode开发调试App时,一旦遇到崩溃问题,开发者可以直接使用Xcode的调试器定位分析。
但如果App发布上线,开发者不可能进行调试,只能通过分析系统记录的崩溃日志来定位问题,在这份崩溃日志文件中,会指出App出错的函数内存地址,而这些函数地址是可以在.dSYM文件中找到具体的文件名、函数名和行号信息的,这正是符号表的重要作用所在,也是为什么要进行符号表进行管理,并纪录这是哪个版本的符号表。
Xcode的Organizer查看崩溃日志时,也自动根据本地存储的.dSYM文件进行了符号化的操作。
并且,崩溃日志也有UUID信息,这个UUID和对应的.dSYM文件是一致的,即只有当三者的UUID一致时,才可以正确的把函数地址符号化。
3、符号表怎么获取
只要获取到.ipa包,就可以获取到对应的.app和.dSYM文件。
对于我们平时开发调试中,想要获取.dSYM文件,可能会遇到下面问题。
xcode release打包版本,Xcode项目默认的配置是会在编译后生成.dSYM,开发者无需额外修改配置。但是有时候,xcode build版本无法生成 dSYM 文件,那么怎么解决呢?
1、打开 xcode-BuildSettings;
2、找到EBUG_INFORMATION_FORMAT这一项;
3、通过查看项目的配置文件,我们可以发现只有 Release 模式配置了 dwarf-with-dsym;
4、我们需要做的是配置 build模式也是dwarf-with-dsym。
通过下图步骤可以获取.app和.dSYM文件
4、符号表怎么使用?
符号表的作用是把崩溃中的函数地址解析为函数名等信息。如果开发者能够获取到崩溃的函数地址信息,就可以利用符号表分析出具体的出错位置。
1、大部分情况下,开发者能获取到的都是错误地址堆栈,需要利用符号表进一步符号化才能分析定位问题。
2、部分情况下,开发者也可以利用backtrace看到符号化堆栈,可以大概定位出错的函数、但却不知道具体的位置。通过利用符号表信息,也是可以进一步得到具体的出错位置的。
那么我们怎么通过符号表来对crash文件进行符号化呢?
四、如何使用symbolicatecrash分析崩溃日志
symbolicatecrash是一个将堆栈地址符号化的脚本。
使用Xcode自带的symbolicatecrash工具来将.Crash和.dSYM文件进行符号化,就可以得到详细崩溃的信息。
通过Mac自带的命令行工具解析Crash文件需要具备三个文件:
1、symbolicatecrash,Xcode自带的崩溃分析工具,使用这个工具可以更精确的定位崩溃所在的位置,将0x开头的地址替换为响应的代码和具体行数。
2、我们打包时产生的dSYM文件。
3、崩溃时产生的Crash文件,例如:*.crash。
解析崩溃信息的时候,首先在桌面上建立一个Crash文件夹,然后将.Crash、.dSYM、symbolicatecrash放在这个文件夹中,这样进入这个文件夹下,直接一行命令就解决了。
./symbolicatecrash XX.crash XX.app.dSYM > xx.sym.crash // 如果输入.dSYM参数,将只解析系统库对应的符号
1、找到symbolicatecrash
find /Applications/Xcode.app -name symbolicatecrash -type f
稍等一会就会有路径输出,这个路径就是symbolicatecrash的路径。用命令将symbolicatecrash拷贝到桌面的crash文件夹里面,与.app和.app.dSYM放一起。
cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash /Users/你的电脑名称/Desktop/crash
2、执行symbolicatecrash
// 打开终端用命令切换到桌面的crash目录下: cd /Users/你的电脑名称/Desktop/crash // 执行命令 ./symbolicatecrash ./xx.crash ./xx.app.dSYM > xx.dsym.crash // 这时候终端有可能会出现: Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69. // 输入命令: export DEVELOPER_DIR="/Applications/XCode.app/Contents/Developer" // 将终端完成以后,在crash文件夹里面会多出一个文件Control_symbol.crash:这个就是最终的文件,可以查看bug所在的位置。