解決崩潰問題是移動應用開發者最日常的工作之一。如果是開發過程中遇到的崩潰,可以根據重現步驟調試,但線上版本就無能為力了。好在目前已經有很多不錯的第三方CrashLog搜集平台(如友盟、Crashlytics等)為我們做好了解析工作,甚至在Xcode7裡蘋果也跟進了解析線上版本崩潰日志的功能,為開發者減輕了不少負擔。盡管通常已經不需要我們手工處理CrashLog,了解CrashLog的還原原理和方法還是有必要的。
一、.dSYM
.dSYM(debugging SYMbols)又稱為調試符號表,是蘋果為了方便調試和定位問題而使用的一種調試方案,本質上使用的是起源於貝爾實驗室的DWARF(Debugging With Attributed Record Formats),其在.xcarchive目錄中的層次結構為:
.xcarchive --dSYMs |--Your.app.dSYM |--Contents |--Resources |--DWARF
關於DWARF的具體內容以後有機會再說。我們能解析CrashLog全靠.dSYM文件,解析方式見後文。
二、確定符號表和崩潰日志的一致性
有了符號表文件,有了崩潰日志文件,在解析之前一定要確保二者的對應關系,否則就算按照下述步驟解析出內容也肯定是不准確的。二者的對應關系可以通過UUID來確定。
1、從崩潰日志中獲取UUID
崩潰日志比較靠下的位置有個Binary Images模塊,其第一行內容如下:
Binary Images: 0xa2000 - 0x541fff Your armv7 /var/mobile/Containers/Bundle/Application/645D3184-4C20-4161-924B-BDE170FA64CC/Your.app/Your
從中可以看到關於你應用的若干信息:
代碼段的起終地址為:0xa2000 – 0x541fff
運行你應用的CPU指令集為:armv7
應用的UUID為:a5c8d3cfda65396689e4370bf3a0ac64(不區分大小寫)
2、從符號表中獲取UUID
執行以下命令從符號表中提取UUID:
dwarfdump --uuid Your.app.dSYM
或者:
dwarfdump --uuid Your.app.dSYM/Contents/Resources/DWARF/Your
執行結果為:
UUID: A5C8D3CF-DA65-3966-89E4-370BF3A0AC64 (armv7) Your.app.dSYM/Contents/Resources/DWARF/Your
由此得到armv7指令集的UUID為:A5C8D3CF-DA65-3966-89E4-370BF3A0AC64(如果你的二進制文件支持多個指令集,這裡會列出每個指令集對應符號表的UUID),通過和崩潰日志中的對比發現二者一致,才可進行進一步的解析操作。
三、計算崩潰符號表地址
以下面的崩潰堆棧為例:
Thread 0: 0 libobjc.A.dylib 0x33f10f60 0x33efe000 + 77664 1 Foundation 0x273526ac 0x2734a000 + 34476 2 Foundation 0x27355c3e 0x2734a000 + 48190 3 UIKit 0x29ef9d1c 0x29bbc000 + 3398940 4 UIKit 0x29ef9c9a 0x29bbc000 + 3398810 5 UIKit 0x29ef954c 0x29bbc000 + 3396940 6 UIKit 0x29c3a16a 0x29bbc000 + 516458 7 UIKit 0x29e4b8e6 0x29bbc000 + 2685158 8 UIKit 0x29c3a128 0x29bbc000 + 516392 9 Your 0x000f0846 0xa2000 + 321606 10 UIKit 0x29e90fb2 0x29bbc000 + 2969522 11 UIKit 0x29e91076 0x29bbc000 + 2969718 12 UIKit 0x29e867cc 0x29bbc000 + 2926540 13 UIKit 0x29c9e8ea 0x29bbc000 + 927978 14 UIKit 0x29bc8a6a 0x29bbc000 + 51818 15 QuartzCore 0x295f0a08 0x295e4000 + 51720 16 QuartzCore 0x295ec3e0 0x295e4000 + 33760 17 QuartzCore 0x295ec268 0x295e4000 + 33384 18 QuartzCore 0x295ebc4c 0x295e4000 + 31820 19 QuartzCore 0x295eba50 0x295e4000 + 31312 20 QuartzCore 0x295e5928 0x295e4000 + 6440 21 CoreFoundation 0x266d0d92 0x26604000 + 839058 22 CoreFoundation 0x266ce44e 0x26604000 + 828494 23 CoreFoundation 0x266ce856 0x26604000 + 829526 24 CoreFoundation 0x2661c3bc 0x26604000 + 99260 25 CoreFoundation 0x2661c1ce 0x26604000 + 98766 26 GraphicsServices 0x2da1a0a4 0x2da11000 + 37028 27 UIKit 0x29c2a7ac 0x29bbc000 + 452524 28 Your 0x0024643a 0xa2000 + 1721402 29 libdyld.dylib 0x34484aac 0x34483000 + 6828
1、 符號表堆棧地址計算方式
要想利用符號表解析出崩潰對應位置,需要計算出符號表中對應的崩潰堆棧地址。而從上述堆棧中第9行可以看到,應用崩潰發生在運行時地址0x000f0846,該進程的運行時起始地址是0xa2000,崩潰處距離進程起始地址的偏移量為十進制的321606(對應十六進制為0x4E846)。三者對應關系:
0x000f0846 = 0xa2000 + 0x4E846
對應的公式為:
運行時堆棧地址 = 運行時起始地址 + 偏移量
崩潰堆棧中的起始地址和崩潰地址均為運行時地址,根據虛擬內存偏移量不變原理,只要提供了符號表TEXT段的起始地址,再加上偏移量(這裡為0x4E846)就能得到符號表中的堆棧地址,即:
符號表堆棧地址 = 符號表起始地址 + 偏移量
2、獲取符號表中的TEXT段起始地址
符號表中TEXT段的起始地址可以通過以下命令獲得:
$otool -l Your.app.dSYM/Contents/Resources/DWARF/Your
運行結果中的片段如下:
Load command 3 cmd LC_SEGMENT cmdsize 736 segname __TEXT vmaddr 0x00004000 vmsize 0x00700000 fileoff 0 filesize 0 maxprot 0x00000005 initprot 0x00000005 nsects 10 flags 0x0
其中的vmaddr 0x00004000字段即為TEXT段的起始地址。
3、計算符號表地址
由公式:
符號表堆棧地址 = 符號表起始地址 + 偏移量
可得:
0x52846 = 0x4E846 + 0x4000
即符號表中的崩潰地址為0x52846,接下來就可以根據這個地址解析出崩潰位置了。
四、崩潰信息還原
有了符號表的崩潰地址,有以下幾種方式解析崩潰信息:
1、dwarfdump
命令如下:
$dwarfdump --arch armv7 Your.app.dSYM --lookup 0x52846 | grep 'Line table'
需要注意的是:
這裡的armv7是運行設備的CPU指令集,而不是二進制文件的指令集
比如armv7指令集的二進制文件運行在arm64指令集的設備上,這個地方應該寫arm64。
—lookup後面跟的一定是經過准確計算的符號表中的崩潰地址
使用dwarfdump解析的結果較雜亂,因此使用grep命令抓取其中關鍵點展示出來
運行結果如下:
Line table dir : '/data/.../Src/OBDConnectSetting/Controller' Line table file: 'OBDFirstConnectViewController.m' line 882, column 5 with start address 0x000000000052768
其中第一行是編譯時文件目錄,第二行包含了崩潰發生的文件名稱以及文件中具體行號等信息,有了這些信息就能准確定位崩潰原因啦。
2、atos
atos是另一種更加簡潔的崩潰日志解析方法,使用方式如下:
$atos -o LuBao -arch armv7 0x52846
其執行結果如下:
-[OBDFirstConnectViewController showOilPricePickerView] (in Your) (OBDFirstConnectViewController.m:882)
相對dwarfdump命令的解析結果,更加簡潔直觀的指出了崩潰發生的位置。
3、無需符號表崩潰地址的解析方式
實際上,atos還提供了另外一種無需計算崩潰地址對應的符號表地址的方式,命令格式如下:
$atos -o Your.app.dSYM/Contents/Resources/DWARF/Your -arch armv7 -l 0xa2000 0x000f0846
其中-l選項指定了二進制文件在運行時的起始地址0xa2000(獲取方式見Binary Images相關內容),後面跟的是崩潰發生的運行時地址0x000f0846,解析結果和使用計算得到的符號表中崩潰地址一致:
-[OBDFirstConnectViewController showOilPricePickerView] (in Your) (OBDFirstConnectViewController.m:882)
五、參考文檔
How to Match a Crash Report to a Build
CrashReporter
Understanding and Analyzing iOS Application Crash Reports
atos and dwarfdump won’t symbolicate my address