iOS crash log手动解析

关于iOS的崩溃分析网上有很多文章,有利用xcode来进行符号化的,有利用symbolicatecrash工具来进行符号化的,但经常会遇到无法符号化,或者部分符号化的情况,那该如何处理呢,今天就来好好分析一下吧

背景知识

先了解几个概念:

  • 符号文件:存储了函数内存地址方法名称及其实现文件等信息的映射关系。
  • DSYM文件:应用程序打包编译后生成的符号文件。里面只有我们应用程序的符号信息。
  • ASLR机制:应用程序代码块被加载到内存时会主动加上一个随机的偏移量,防止缓冲区溢出攻击。全称Address space layout randomization

认识崩溃日志

这是一个ios crash log文件,省略部分内部:

{"app_name":"AppCrashTest","timestamp":"2020-08-24 11:17:29.11 +0800","app_version":"8.2.2.4","slice_uuid":"4452a677-4a33-301f-88cb-b44e9f744062","adam_id":0,"build_version":"1","bundleID":"com.mccm.test","share_with_app_devs":false,"is_first_party":false,"bug_type":"109","os_version":"iPhone OS 12.4 (16G77)","incident_id":"50DCADC2-896D-4D0B-889C-0DF39AFDBBA0","name":"AppCrashTest"}
Incident Identifier: 50DCADC2-896D-4D0B-889C-0DF39AFDBBA0
CrashReporter Key:   6afda7a3846fb33ba936d48b9cbbfabf2903c90b
Hardware Model:      iPhone9,1
Process:             AppCrashTest [4277]
Path:                /private/var/containers/Bundle/Application/210704D2-AA1F-468F-9840-679AB05FB371/AppCrashTest.app/AppCrashTest
Identifier:          com.mccm.test
Version:             1 (8.2.2.4)
Code Type:           ARM-64 (Native)
Role:                Non UI
Parent Process:      launchd [1]
Coalition:           com.mccm.test [1099]


Date/Time:           2020-08-24 11:17:28.9696 +0800
Launch Time:         2020-08-24 11:10:44.1941 +0800
OS Version:          iPhone OS 12.4 (16G77)
Baseband Version:    5.70.01
Report Version:      104

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

Application Specific Information:
abort() called

Last Exception Backtrace:
(0x196cde98c 0x195eb79f8 0x196bf8098 0x1976bc088 0x1c391ec78 0x1c39364f4 0x1c39365fc 0x1054f4504 0x104c1cc24 0x196c4fa28 0x196c4f9f4 0x196c4eee8 0x196c4eb94 0x196bc8474 0x196c4e644 0x1976376f4 0x104ae571c 0x104ae41f0 0x19671ca38 0x19671d7d4 0x1966cb008 0x196c7032c 0x196c6b264 0x196c6a7c0 0x198e6b79c 0x1c3735c38 0x104e17adc 0x19672e8e0)

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x000000019687b0dc 0x196858000 + 143580
1   libsystem_pthread.dylib         0x00000001968f4094 0x1968f2000 + 8340
2   libsystem_c.dylib               0x00000001967d3ea8 0x196779000 + 372392
3   libc++abi.dylib                 0x0000000195ea0788 0x195e9f000 + 6024
4   libc++abi.dylib                 0x0000000195eac858 0x195e9f000 + 55384
5   libc++abi.dylib                 0x0000000195eac8c4 0x195e9f000 + 55492
6   libdispatch.dylib               0x000000019671d7e8 0x1966bd000 + 395240
7   libdispatch.dylib               0x00000001966cb008 0x1966bd000 + 57352
8   CoreFoundation                  0x0000000196c7032c 0x196bc6000 + 697132
9   CoreFoundation                  0x0000000196c6b264 0x196bc6000 + 676452
10  CoreFoundation                  0x0000000196c6a7c0 0x196bc6000 + 673728
11  GraphicsServices                0x0000000198e6b79c 0x198e61000 + 42908
12  UIKitCore                       0x00000001c3735c38 0x1c2e79000 + 9161784
13  AppCrashTest                    0x0000000104e17adc 0x104910000 + 5274332
14  libdyld.dylib                   0x000000019672e8e0 0x19672d000 + 6368

......

Thread 3:
0   libsystem_kernel.dylib          0x000000019687aee4 0x196858000 + 143076
1   libsystem_pthread.dylib         0x00000001968f5cf8 0x1968f2000 + 15608
2   libc++.1.dylib                  0x0000000195e51128 0x195e49000 + 33064
3   AppCrashTest                    0x0000000105a5cc58 0x104910000 + 18140248
4   AppCrashTest                    0x0000000105b9d3e0 0x104910000 + 19452896
5   AppCrashTest                    0x0000000105b9d1a0 0x104910000 + 19452320
6   AppCrashTest                    0x0000000105b9d200 0x104910000 + 19452416
7   AppCrashTest                    0x0000000105b873fc 0x104910000 + 19362812
8   AppCrashTest                    0x0000000105b87c94 0x104910000 + 19365012
9   libsystem_pthread.dylib         0x00000001968fd2c0 0x1968f2000 + 45760
10  libsystem_pthread.dylib         0x00000001968fd220 0x1968f2000 + 45600
11  libsystem_pthread.dylib         0x0000000196900cdc 0x1968f2000 + 60636

Binary Images:
0x104910000 - 0x10742bfff AppCrashTest arm64  <4452a6774a33301f88cbb44e9f744062> /var/containers/Bundle/Application/210704D2-AA1F-468F-9840-679AB05FB371/AppCrashTest.app/AppCrashTest
0x108310000 - 0x10840bfff Charts arm64  <0eae557f017e31ceade3a43c5a644b92> /var/containers/Bundle/Application/210704D2-AA1F-468F-9840-679AB05FB371/AppCrashTest.app/Frameworks/Charts.framework/Charts
0x1085c0000 - 0x108617fff dyld arm64   <06f3d9add3a233cea57df42b73686817> /usr/lib/dyld
......
线程堆栈解读

第一列:调用序号
第二列:调用方法所属的binary image信息
第三列:调用方法的内存地址
第四列:调用方法所属的image加载首地址+偏移量,和第三列值一样,可以不用关心

Binary Images解读,以AppCrashTest为例
  • 0x1048d0000 - 0x1073ebfff :image被加载到内存后的地址段
  • AppCrashTest : image信息描述
  • arm64 : 构架信息
  • 4452a6774a33301f88cbb44e9f744062 : 该image的唯一id,该id必须和符号文件的uuid匹配上,否则无法进行符号化,libSystem.B.dylib和AppCrashTest都一样。

获取符号文件的uuid: dwarfdump --uuid 符号文件路径

符号化过程分析

符号化的过程其实非常简单,就是拿crash文件中堆栈信息中的方法内存地址到相应image的符号文件中找映射的方法描述信息
例如我们想符号化这行日志:

AppCrashTest 0x0000000104e17adc 0x104910000 + 5274332

就拿0x0000000104e17adc这个地址到AppCrashTest这个image对应的符号文件中去找方法描述信息。
想符号化这行日志:

CoreFoundation 0x0000000196c7032c 0x196bc6000 + 697132

就拿0x0000000196c7032c这个地址到CoreFoundation这个image对应的符号文件中去找方法描述信息。

所以整体crash文件符号化过程就是,逐行读取堆栈信息,然后获取第一行的image信息内存地址,然后到其相应的符号文件中查找方法名即可。

Last Exception Backtrace是如何符号化的

仔细观察Binary Images列表,你会发现image被加载到内存后的地址段基本是连续的,并且不会有重叠。所以Last Exception Backtrace中的每个内存地址一定处于某个image地址段中,遍历找到其所在的地址段就能找到其所属image信息,然后到对应的符号文件中查找方法描述信息即可。

手动解析

上面的说法只是基于我们的猜测,到底是不是这样的,还需要通过实践来证明。xcode给我提供了一个工具来进行符号化:

xcrun atos -arch  -o /Contents/Resources/DWARF/ -l 

-arch 所属架构
-o 符号文件地址
-l image被加载到内存的起始地址

其中-l的值为该行日志所属image被加载到内存中的起始地址,我们来解析一下thread 0中第13行:

13  AppCrashTest 0x0000000104e17adc 0x104910000 + 5274332

先输入

//注意AppCrashTest 这个image的初始地址为0x104910000
crash % xcrun atos -arch arm64 -o ./AppCrashTest.app.dSYM/Contents/Resources/DWARF/AppCrashTest -l 0x104910000

回车后输入方法的内存地址0x0000000104e17adc,打印如下:

main (in AppCrashTest) (main.m:13)

再试一下thread 3中这几行:

3   AppCrashTest                    0x0000000105a5cc58 0x104910000 + 18140248
4   AppCrashTest                    0x0000000105b9d3e0 0x104910000 + 19452896
5   AppCrashTest                    0x0000000105b9d1a0 0x104910000 + 19452320
6   AppCrashTest                    0x0000000105b9d200 0x104910000 + 19452416
7   AppCrashTest                    0x0000000105b873fc 0x104910000 + 19362812
8   AppCrashTest                    0x0000000105b87c94 0x104910000 + 19365012

//回车后输入相应的地址
0x0000000105a5cc58
std::__1::cv_status std::__1::condition_variable::wait_until > >(std::__1::unique_lock&, std::__1::chrono::time_point > > const&) (in AppCrashTest) + 124
0x0000000105b9d3e0
std::__1::cv_status std::__1::condition_variable_any::wait_until, std::__1::chrono::steady_clock, std::__1::chrono::duration > >(std::__1::unique_lock&, std::__1::chrono::time_point > > const&) (in AppCrashTest) + 104
0x0000000105b9d1a0
TXCCondition::wait(std::__1::unique_lock&, long) (in AppCrashTest) + 88
0x0000000105b9d200
TXCCondition::wait(long) (in AppCrashTest) + 60
0x0000000105b873fc
__async_log_thread() (in AppCrashTest) + 176
0x0000000105b87c94
void* std::__1::__thread_proxy >, void (*)()> >(void*) (in AppCrashTest) + 44

按此步骤所有属于AppCrashTest image的日志信息都能被符号化出来,那些系统方法的调用该如何符号化呢?

符号化系统库

我们应用程序对应的image模块能够通过atos符号化,是因为我们有DSYM文件,那系统符号文件如何获取呢?有多种方式,关于系统库的符号文件获取大家可以参考iOS Crash分析必备:符号化系统库方法

由于这个符号文件的uuid要和我们本次分析的crash文件中对应imageuuid一致才能进行符号化,而市面上的ios手机和操作系统实太多,所以相应的系统符号文件也是非常多的。不过还好网上已经有人收集了历史版本的符号文件,链接在这里网上收集的部分系统库符号文件。

如果你用过友盟或者bugly,你会发现在它们崩溃分析中有些系统库的日志也是无法符号化的,那是因为它们对于苹果历史版本的符号文件也收集不全

所幸OS Version: iPhone OS 12.4 (16G77)在我本机的/Users/xxx/Library/Developer/Xcode/iOS DeviceSupport/目录下有,接下来我们分析一下thread 0中这几行日志:

0   libsystem_kernel.dylib          0x000000019687b0dc 0x196858000 + 143580
1   libsystem_pthread.dylib         0x00000001968f4094 0x1968f2000 + 8340
2   libsystem_c.dylib               0x00000001967d3ea8 0x196779000 + 372392
3   libc++abi.dylib                 0x0000000195ea0788 0x195e9f000 + 6024
4   libc++abi.dylib                 0x0000000195eac858 0x195e9f000 + 55384
5   libc++abi.dylib                 0x0000000195eac8c4 0x195e9f000 + 55492
6   libdispatch.dylib               0x000000019671d7e8 0x1966bd000 + 395240
7   libdispatch.dylib               0x00000001966cb008 0x1966bd000 + 57352

第0行libsystem_kernel:

dyf@dyfdeMacBook-Pro crash % xcrun atos -arch arm64 -o '/Users/dyf/Library/Developer/Xcode/iOS DeviceSupport/12.4 (16G77)/Symbols/usr/lib/system/libsystem_kernel.dylib' -l 0x196858000
0x000000019687b0dc
__pthread_kill (in libsystem_kernel.dylib) + 8

第1行libsystem_pthread:

dyf@dyfdeMacBook-Pro crash % xcrun atos -arch arm64 -o '/Users/dyf/Library/Developer/Xcode/iOS DeviceSupport/12.4 (16G77)/Symbols/usr/lib/system/libsystem_pthread.dylib' -l 0x1968f2000
0x00000001968f4094
pthread_kill$VARIANT$mp (in libsystem_pthread.dylib) + 380

第6-7行libdispatch:

dyf@dyfdeMacBook-Pro crash % xcrun atos -arch arm64 -o '/Users/dyf/Library/Developer/Xcode/iOS DeviceSupport/12.4 (16G77)/Symbols/usr/lib/system/libdispatch.dylib' -l 0x1966bd000
0x000000019671d7e8
_dispatch_client_callout (in libdispatch.dylib) + 36
0x00000001966cb008
_dispatch_main_queue_callback_4CF$VARIANT$mp (in libdispatch.dylib) + 1068

所以只要能找到相应版本的系统符号文件我们就能对系统库进行符号化。

关于Slide偏移

前面我们提到ASLR机制,我们的应用程序被加载时会先生成一个随机值,然后所有的image被加载到内存都会加上这个值,这个随机值就是Slide偏移量

在分析Slide之前我们先认识一个新的概念Load Command,它是应用程序编译完成生成的可执行文件的描述信息。可以通过otool命令查看,还是用上面的AppCrashTest应用来举例:

otool -l AppCrashTest.app/AppCrashTest

...
//注意,这里只看arm64的
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 1192
  segname __TEXT
   vmaddr 0x0000000100000000
   vmsize 0x0000000002b1c000
  fileoff 0
 filesize 45203456
  maxprot 0x00000005
 initprot 0x00000005
   nsects 14
    flags 0x0
... 

信息很多,但我们重点关注的是__TEXT段,这是代码加载段,vmaddr为其起始地址,filesize为整个代码数据占用内存大小。如果没有ASLR机制程序被加载时,其代码数据被加载到内存应该是从0x100000000开始占用45203456个字节。我们再来看一下crash文件中真实的AppCrashTest程序被加载时内存段:

0x104910000 - 0x10742bfff AppCrashTest arm64  <4452a6774a33301f88cbb44e9f744062> /var/containers/Bundle/Application/210704D2-AA1F-468F-9840-679AB05FB371/AppCrashTest.app/AppCrashTest

0x10742bfff - 0x104910000 + 1 = 45203456 

运行过程中AppCrashTest的代码是从0x10742bfff开始占用45203456字节,直到0x104910000结束。

所以从Slide = 0x104910000 - 0x100000000 = 0x4910000。整个应用程序的内存地址偏移了0x4910000个字节。

如何证明呢?

任然需要利用atos,查看atos命令的帮助说明:

dyf@dyfdeMacBook-Pro crash % atos --help
Usage: atos [-p pid] [-o executable] [-f file] [-s slide | -l loadAddress] [-arch architecture] [-printHeader] [-fullPath] [address ...]

        --fullPath  show full path to source file

我们可以看到-l-s是二选一,那这次我们来使用-s对thread 0中的这行日志进行解析:

AppCrashTest 0x0000000104e17adc 0x104910000 + 5274332

输入命令如下:

xcrun atos -arch arm64 -o AppCrashTest.app.dSYM/Contents/Resources/DWARF/AppCrashTest -s 0x4910000

回车后输入0x0000000104e17adc,打印如下:

main (in AppCrashTest) (main.m:13)

依然能够解析出来。可以看到-s这种方式来进行符号化比较麻烦,所以大部分情况下用-l即可。

注意-s这种方式只能解析我们应用程序的符号信息,系统库的是无法解析的。对于系统库的符号文件来说是没有slide这个概念的,系统库的__TEXT的地址段和运行时被加载到内存后的地址段是一致的,这就是动态库的设计思路,为了节约内存,所有的系统库在内存中都只需要加载一次。
通过对比同一个版本的两们崩溃日志也会发现,我们应用程序的image地址段在两次crash文件中不一样,但系统库的地址段都是一样的。

你可能感兴趣的:(iOS crash log手动解析)