iOS crash log 解析 symbol address = stack address

概述:

为什么 crash log 内 Exception Backtrace 部分的地址(stack address)不能从 dsym 文件中查出对应的代码?

因为 ASLR(Address space layout randomization),因为 ASLR 引入了一个 slide (偏移) 。

 symbol address = stack address - slide;

slide 可以在运行时 由 API 获取到 

[objc] view plain copy
  1. dyld_get_image_vmaddr_slide()  

也可以根据运行时的 binary image 和   ELF 文件的 load command 计算的到。

 slide = (运行时)load address - (链接时)load address;

注意,如果你没有在运行时用 api 获取slide,那么 binary image 就必须要收集,否则你无法从dsym 文件中解析出符号。

 

 

这是一个 iOS crash log 文件,为了简洁删除了部分不需要的内容

[objc] view plain copy
  1. Incident Identifier: 975CF16A-5259-4DD1-BFDA-D1B155EF5BF0  
  2. CrashReporter Key:   562a7cefe034ac086cae453c61278cdd9a4b3288  
  3. Hardware Model:      iPad4,1  
  4. Process:             MedicalRecordsFolder [382]  
  5. Path:                /var/mobile/Applications/05C398CE-21E9-43C2-967F-26DD0A327932/MedicalRecordsFolder.app/MedicalRecordsFolder  
  6. Identifier:          com.xingshulin.MedicalRecordIOS  
  7. Version:             1 (4.14.0)  
  8. Code Type:           ARM-64 (Native)  
  9. Parent Process:      launchd [1]  
  10.   
  11.   
  12. Date/Time:           2015-12-03 19:14:59.921 +0800  
  13. OS Version:          iOS 7.1.2 (11D257)  
  14. Report Version:      104  
  15.   
  16.   
  17.   
  18.   
  19. Exception Type:  EXC_CRASH (SIGABRT)  
  20. Exception Codes: 0x0000000000000000, 0x0000000000000000  
  21. Triggered by Thread:  0  
  22.   
  23.   
  24. Last Exception Backtrace:  
  25. 0   CoreFoundation               0x189127100 __exceptionPreprocess + 132  
  26. 1   libobjc.A.dylib              0x1959e01fc objc_exception_throw + 60  
  27. 2   CoreFoundation               0x189127040 +[NSException raise:format:] + 128  
  28. 3   MedicalRecordsFolder         0x100a8666c 0x10003c000 + 10790508  
  29. 4   libsystem_platform.dylib     0x19614bb0c _sigtramp + 56  
  30. 5   MedicalRecordsFolder         0x1006ef160x10003c000 + 7024996  
  31. 6   MedicalRecordsFolder         0x1006e8580x10003c000 + 6997376  
  32. 7   MedicalRecordsFolder         0x1006e8010x10003c000 + 6995988  
  33. 8   MedicalRecordsFolder         0x1006e7c90x10003c000 + 6995092  
  34. 9   MedicalRecordsFolder         0x1006f2460x10003c000 + 7038048  
  35. 10  libdispatch.dylib            0x195fb8014 _dispatch_call_block_and_release + 24  
  36. 11  libdispatch.dylib            0x195fb7fd4 _dispatch_client_callout + 16  
  37. 12  libdispatch.dylib            0x195fbe4a8 _dispatch_queue_drain + 640  
  38. 13  libdispatch.dylib            0x195fba4c0 _dispatch_queue_invoke + 68  
  39. 14  libdispatch.dylib            0x195fbf0f4 _dispatch_root_queue_drain + 104  
  40. 15  libdispatch.dylib            0x195fbf4fc _dispatch_worker_thread2 + 76  
  41. 16  libsystem_pthread.dylib      0x19614d6bc _pthread_wqthread + 356  
  42. 17  libsystem_pthread.dylib      0x19614d54c start_wqthread + 4  
  43.   
  44.   
  45. Thread 0 Crashed:  
  46. 0   libsystem_kernel.dylib        0x00000001960ce58c __pthread_kill + 8  
  47. 1   libsystem_c.dylib             0x0000000196062804 abort + 108  
  48. 2   libc++abi.dylib               0x0000000195288990 abort_message + 84  
  49. 3   libc++abi.dylib               0x00000001952a5c28 default_terminate_handler() + 296  
  50. 4   libobjc.A.dylib               0x00000001959e04d0 _objc_terminate() + 124  
  51. 5   libc++abi.dylib               0x00000001952a3164 std::__terminate(void (*)()) + 12  
  52. 6   libc++abi.dylib               0x00000001952a2d38 __cxa_rethrow + 140  
  53. 7   libobjc.A.dylib               0x00000001959e03a4 objc_exception_rethrow + 40  
  54. 8   CoreFoundation                0x0000000189025e48 CFRunLoopRunSpecific + 572  
  55. 9   GraphicsServices              0x000000018ecb5c08 GSEventRunModal + 164  
  56. 10  UIKit                         0x000000018c156fc0 UIApplicationMain + 1152  
  57. 11  MedicalRecordsFolder          0x000000010018fc70x10003c000 + 1391728  
  58. 12  libdyld.dylib                 0x0000000195fd3a9c start + 0  
  59.   
  60.   
  61. Thread 1:  
  62. 0   libsystem_kernel.dylib        0x00000001960b5aa8 kevent64 + 8  
  63. 1   libdispatch.dylib             0x0000000195fb9998 _dispatch_mgr_thread + 48  
  64. ....  
  65.   
  66.   
  67. Thread 0 crashed with ARM Thread State (64-bit):  
  68.     x0: 0x0000000000000000   x1: 0x0000000000000000   x2: 0x0000000000000000   x3: 0xffffffffffffffff  
  69.     x4: 0x0000000000003060   x5: 0x000000016fdc3530   x6: 0x000000000000006e   x7: 0x0000000000000640  
  70.     x8: 0x0000000008000000   x9: 0x0000000004000000  x10: 0x0000000098efe6f7  x11: 0x0000000198efde94  
  71.    x12: 0x000000000000006f  x13: 0x0000000000000000  x14: 0x0000000000000000  x15: 0x000000019607bdcb  
  72.    x16: 0x0000000000000148  x17: 0x004b96d3524ed02c  x18: 0x0000000000000000  x19: 0x0000000000000006  
  73.    x20: 0x0000000198f112a0  x21: 0x434c4e47432b2b00  x22: 0x434c4e47432b2b00  x23: 0x0000000000000001  
  74.    x24: 0x00000001701578c0  x25: 0x0000000000000001  x26: 0x0000000170002ea0  x27: 0x00000001963e1410  
  75.    x28: 0x0000000000000000  fp: 0x000000016fdc34b0   lr: 0x000000019615116c  
  76.     sp: 0x000000016fdc3490   pc: 0x00000001960ce58c cpsr: 0x00000000  
  77.   
  78.   
  79.   
  80.   
  81. Binary Images:  
  82. 0x10003c000 - 0x100f7bfff MedicalRecordsFolder arm64  <b5ae3570a013386688c7007ee2e73978> /var/mobile/Applications/05C398CE-21E9-43C2-967F-26DD0A327932/MedicalRecordsFolder.app/MedicalRecordsFolder  
  83. 0x12007c000 - 0x1200a3fff dyld arm64  <628da833271c3f9bb8d44c34060f55e0> /usr/lib/dyld  
  84. .......  

现在来指出其中比较重要的部分

uuid信息

 

[objc] view plain copy
  1. Incident Identifier: 975CF16A-5259-4DD1-BFDA-D1B155EF5BF0  

这行指出文件的 uuid ,根据次 uuid 可确定 dsym 文件是否匹配

确定方法如下,后面会打印出该 dsym 文件内所有 架构的 uuid ,看一下 是否包含 就知道了

 

 

[objc] view plain copy
  1. dwarfdump --uuid MedicalRecordsFolder.app.dSYM/  

  arch信息不解释

[objc] view plain copy
  1. Code Type:           ARM-64 (Native)  

下面是 异常信息,异常线程 为 thread 0

[objc] view plain copy
  1. Exception Type:  EXC_CRASH (SIGABRT)  
  2. Exception Codes: 0x0000000000000000, 0x0000000000000000  
  3. Triggered by Thread:  0  

 

 

接下来看 抛出异常的线程的函数调用栈信息 

左侧
第一列,调用顺序 
第二列,对应函数所属的 binary image 
第三列,stack address  
第四列,地址的符号+偏移的表示法,实质内容跟第三列一样(此列不理解也无影响)

 

[objc] view plain copy
  1. Last Exception Backtrace:  
  2. 0   CoreFoundation                0x189127100 __exceptionPreprocess + 132  
  3. 1   libobjc.A.dylib               0x1959e01fc objc_exception_throw + 60  
  4. 2   CoreFoundation                0x189127040 +[NSException raise:format:] + 128  
  5. 3   MedicalRecordsFolder          0x100a8666c 0x10003c000 + 10790508  
  6. 4   libsystem_platform.dylib      0x19614bb0c _sigtramp + 56  
  7. 5   MedicalRecordsFolder          0x1006ef160x10003c000 + 7024996  
  8. 6   MedicalRecordsFolder          0x1006e8580x10003c000 + 6997376  
  9. 7   MedicalRecordsFolder          0x1006e8010x10003c000 + 6995988  
  10. 8   MedicalRecordsFolder          0x1006e7c90x10003c000 + 6995092  
  11. 9   MedicalRecordsFolder          0x1006f2460x10003c000 + 7038048  
  12. 10  libdispatch.dylib             0x195fb8014 _dispatch_call_block_and_release + 24  
  13. 11  libdispatch.dylib             0x195fb7fd4 _dispatch_client_callout + 16  
  14. 12  libdispatch.dylib             0x195fbe4a8 _dispatch_queue_drain + 640  
  15. 13  libdispatch.dylib             0x195fba4c0 _dispatch_queue_invoke + 68  
  16. 14  libdispatch.dylib             0x195fbf0f4 _dispatch_root_queue_drain + 104  
  17. 15  libdispatch.dylib             0x195fbf4fc _dispatch_worker_thread2 + 76  
  18. 16  libsystem_pthread.dylib       0x19614d6bc _pthread_wqthread + 356  
  19. 17  libsystem_pthread.dylib       0x19614d54c start_wqthread + 4  

我们从 binary image 这列里面可以看出 好多都是 动态库调用,动态库也就是说 这是 sdk 里面的东西,即使出了bug 你也修复不了,所以我们需要关心的就只有

第 3、5、6、7、8、9、这些行 ,只有这行行对应的代码 才是你自己的 创作(我工程名就是MedicalRecordsFolder)

是 函数调用顺序是 从下往上,也就是说 第 3 行对应的函数才是出问题时 正在执行的代码片段。

所以 从 dsym 中找到 第三行对应的 符号信息 才可能定位到 问题代码。

第三行第三列 

0x100a8666c ,这是 stack address ,注意是 stack address,如果系统没有 ASLR 的话,用这个 stack address 就能在dsym 中找到对应符号信息,但是事实 iOS是有 ASLR 的 。

ASLR 技术Address space layout randomization,ASLR通过将系统可执行程序随机装载到内存里,从而防止缓冲区溢出攻击 

由于 ASLR 的缘故,导致 程序crash后生成的crash log 中的  stack address 与 对应的 symbol address 不一致,有一个偏移量 slide,slide是程序装在时随机生成的随机数。

引入新的概念:

stack address 

: 程序运行时线程栈中 所有 函数调用的地址

symble address 

: dsym文件中函数符号对应的地址,用此地址 在 dsym 文件中可以 查出对应的 符号信息。 

无 ASLR 机制时  stack address 等于symble address 。

 

定义 slide

在ASLR机制下每次启动APP 装在之前 ,会在连接时指定的 进程空间上 加上一个随意的 偏移量,这个偏移量就是 slide。

 很简单  symble address = stack address -slide;

但是这个 slide 每次 启动 程序都不同,如何 知道 当时启动时 slide 的值呢 ? 带着疑问继续吧

Load Command

一个 iOS 程序编译链接完之后,生成一个 ELF 二进制文件(也就是程序运行时的映射文件),该文件的详细格式不再赘述,这里只强调一个 segment  _TEXT

下面是 使用 otool 工具查看到的  MedicalRecordsFolder(我的demo程序)的 加载命令 。

 

[objc] view plain copy
  1. $otool -l MedicalRecordsFolder.app/MedicalRecordsFolder   
  2. MedicalRecordsFolder.app/MedicalRecordsFolder:  
  3. Load command 0  
  4.       cmd LC_SEGMENT_64  
  5.   cmdsize 72  
  6.   segname __PAGEZERO  
  7.    vmaddr 0x0000000000000000  
  8.    vmsize 0x0000000100000000  
  9.   fileoff 0  
  10.  filesize 0  
  11.   maxprot 0x00000000  
  12.  initprot 0x00000000  
  13.    nsects 0  
  14.     flags 0x0  
  15. Load command 1  
  16.       cmd LC_SEGMENT_64  
  17.   cmdsize 792  
  18.   segname __TEXT  
  19.    vmaddr 0x0000000100000000  
  20.    vmsize 0x000000000000c000  
  21.   fileoff 0  
  22.  filesize 49152  
  23.   maxprot 0x00000005  
  24.  initprot 0x00000005  
  25.    nsects 9  
  26.     flags 0x0  
  27.   
  28.   
  29. ……  
  30.   
  31.   
  32. Load command 2  
  33.       cmd LC_SEGMENT_64  
  34.   cmdsize 1352  
  35.   segname __DATA  
  36.    vmaddr 0x000000010000c000  
  37.    vmsize 0x0000000000004000  
  38.   fileoff 49152  
  39.  filesize 16384  
  40.   maxprot 0x00000003  
  41.  initprot 0x00000003  
  42.    nsects 16  
  43.     flags 0x0  
  44.   
  45.   
  46. ……  
  47.   
  48.   
  49. Load command 3  
  50.       cmd LC_SEGMENT_64  
  51.   cmdsize 72  
  52.   segname __LINKEDIT  
  53.    vmaddr 0x0000000100010000  
  54.    vmsize 0x000000000000c000  
  55.   fileoff 65536  
  56.  filesize 35056  
  57.   maxprot 0x00000001  
  58.  initprot 0x00000001  
  59.    nsects 0  
  60.     flags 0x0  

_TEXT 段的加载命令如下,可知到映射文件中segment _TEXT 对应的  虚拟地址空间从 0x0000000100000000 开始 。

[objc] view plain copy
  1. segname __TEXT  
  2.    vmaddr 0x0000000100000000  
  3.    vmsize 0x000000000000c000  
  4.   fileoff 0  
  5.  filesize 49152  



segname __TEXT  就是代码段,也就是说所有的二进制指令

没有 ASLR机制时:

加载时 装载器会将此 ELF 文件的 前 49152 (offset 0 ,filesize 49152)个字节(因为 offset 0 ,filesize 49152)映射到 进程空间以  0x0000000100000000 开始的一块虚拟内存空间里. 

有ASLR 机制时:

加载时 装载器会将此 ELF 文件的 前 49152 (offset 0 ,filesize 49152)个字节(因为 offset 0 ,filesize 49152)映射到 进程空间以  0x0000000100000000 (+slide)开始的一块虚拟内存空间里. 

所以 : 如果没有 ASLR 机制,那么运行时的内存布局 就和  Load command 中指定的布局一致,也就意味着stack address和 symbol address 一致

有 ASLR 的情况也不复杂,只是 加了一个 随意的偏移量 slide 

binary image

程序运行时 的  映射 信息,

[objc] view plain copy
  1. Binary Images:  
  2. 0x10003c000 - 0x100f7bfff MedicalRecordsFolder arm64  <b5ae3570a013386688c7007ee2e73978> /var/.../MedicalRecordsFolder  
  3. 0x12007c000 - 0x1200a3fff dyld arm64  <628da833271c3f9bb8d44c34060f55e0> /usr/lib/dyld  

左侧

第一列,虚拟地址空间区块

第二列,映射文件 名

第三列,uuid吧,还不知道,以后再补上

第四列,映射文件路径

计算 slide 和 symbol address

第一行可以看出 进程空间的 0x10003c000 - 0x100f7bfff 这个区域 在运行时被映射为 MedicalRecordsFolder 内的内容,也就是我们的 ELF 文件。

注意这个 区域起始地址 为  0x10003c000 

而我们在 Load Command 中看到的却是  0x0000000100000000

 

[objc] view plain copy
  1. segname __TEXT  
  2.    vmaddr 0x0000000100000000  
  3.    vmsize 0x000000000000c000  

显而易见: 

slide =  0x10003c000 - 0x100000000 = 0x3c000;

symbol address = stack address - slide;

stack address 在crash log 中已经找到了。

用的到的symbol地址去 dsym 文件中 查询,命令如下

 

[objc] view plain copy
  1. $dwarfdump --arch arm64 --lookup 0x00123 MedicalRecordsFolder.app.dSYM/  

就可以定位下来具体的 代码 函数名,所处的文件,行 等信息了

读取 slide 的 API

这个 slide 的计算还是挺 恶心的 ,要 查看 binary image 的到 load address ,还要查看  对用 ELF 中 _TEXT 的 Load Command 虚拟空间范围.

如果 自己写一个 模块 来 收集 NSException 的话 ,大可不必这么繁琐,因为 程序 运行时 有 api 是可以 直接获取这个 binary image 对应的  slide 值的 。

如下:

 

[objc] view plain copy
  1. #import <mach-o/dyld.h>  
  2. void calculate(void) {  
  3.     for (uint32_t i = 0; i < _dyld_image_count(); i++) {  
  4.         if (_dyld_get_image_header(i)->filetype == MH_EXECUTE) {  
  5.              long slide = _dyld_get_image_vmaddr_slide(i);  
  6.             break;  
  7.         }  
  8.     }  
  9. }  


 这样 就可以 将 stack address 直接 减去 slide 之后 再 上传到自己的  服务端,岂不是 很完美。

 

原文地址:https://www.cnblogs.com/feng9exe/p/7988360.html