欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS问题分析技巧-crash日志符号化

程序员文章站 2022-07-08 18:22:03
iOS问题分析技巧-crash日志符号化 iOS开发需要不停发版本,开发者要面临线上各种版本的奔溃日志(crash log),解决奔溃问题是移动开发者最日常的工作之一. 在实际...

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工具,所以开发者才可以直接看到符号化的错误日志。)

iOS问题分析技巧-crash日志符号化

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文件
iOS问题分析技巧-crash日志符号化
iOS问题分析技巧-crash日志符号化

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所在的位置。