iOS crash log 解析 symbol address = stack address - slide 運行時獲取slide的api 利用dwarfdump從dsym文件中得到symbol


概述:

為什么 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 之后 再 上傳到自己的  服務端,豈不是 很完美。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM