在上篇文章《手動解析CrashLog之——方法篇》裡介紹了手動解析CrashLog的方法,接下來再說說dwarfdump、atos等解析工具是如何從符號表文件中獲取到崩潰位置信息的。一切還得從.dSYM符號表文件開始說起。
一、.dSYM文件的生成
符號表文件.dSYM實際上是從Mach-O文件中抽取調試信息而得到的文件目錄,實際用於保存調試信息的問價是DWARF,其出身可以從蘋果員工的文章《Apple’s “Lazy” DWARF Scheme》了解一二。
1、Xcode自動生成
Xcode會在編譯工程或者歸檔時自動為我們生成.dSYM文件,當然我們也可以通過更改Xcode的若干項Build Settings來阻止它那麼干。
2、手動生成
另一種方式是通過命令行從Mach-O文件中手工提取,比如:
$ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil /Users/wangzz/Library/Developer/Xcode/DerivedData/YourApp-cqvijavqbptjyhbwewgpdmzbmwzk/Build/Products/Debug-iphonesimulator/YourApp.app/YourApp -o YourApp.dSYM
該方式通過Xcode提供的工具dsymutil,從項目編譯結果.app目錄下的Mach-O文件中提取出調試符號表文件。實際上Xcode也是通過這種方式來生成符號表文件。
二、DWARF簡介
DWARF(DebuggingWith Arbitrary Record Formats),是ELF和Mach-O等文件格式中用來存儲和處理調試信息的標准格式,.dSYM中真正保存符號表數據的是DWARF文件。DWARF中不同的數據都保存在相應的section(節)中,ELF文件裡所有的section名稱都以".debug_"開頭,如下表所示:
| Section Name | Contents | | -------------------- | ------------------------------------------------ | | .debug_abbrev | Abbreviations used in the .debug_info section | | .debug_aranges | A mapping between memory address and compilation | | .debug_frame | Call Frame Information | | .debug_info | The core DWARF data containing DIEs | | .debug_line | Line Number Program | | .debug_loc | Macro descriptions | | .debug_macinfo | A lookup table for global objects and functions | | .debug_pubnames | A lookup table for global objects and functions | | .debug_pubtypes | A lookup table for global types | | .debug_ranges | Address ranges referenced by DIEs | | .debug_str | String table used by .debug_info |
Mach-O中關於section的命名和ELF稍有區別,把名稱前的.換成了_,例如.debug_info變成了_debug_info。
三、section信息提取
保存在DAWARF中的信息是高度壓縮的,可以通過dwarfdump命令從中提取出可讀信息。前文所述的那些section中,定位CrashLog只需要用到.debug_info和.debug_line。由於解析出來的數據量較大,為了方便查看,就將其保存在文本中。兩個section的數據提取方式如下:
.debug_info
$ dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF > info-e.txt
.debug_line
$ dwarfdump -e --debug-line YourPath/YourApp.dSYM/Contents/Resources/DWARF > line-e.txt
命令中的-e可以增加解析結果的可讀性;其它section的提取方式類似,詳情請參考dwarfdump命令幫助信息。
四、解析崩潰地址
1、計算崩潰地址對應符號表中的地址
在上篇文章中,介紹了如何根據崩潰地址計算得到對應符號表中的地址,並得到了最終數值:0x52846,接下來我們就通過這個值來介紹dwarfdump、atos等工具是如何解析崩潰日志的。
2、解析過程
.debug_info
.debug_info中最基本的描述單元為DIE(Debug Information Entry),詳情請參考DWARF官方網站,首先我們要根據符號表崩潰地址0x52846從.debug_info中取出包含這個地址的DIE單元。為了簡單起見,直接貼出了從info-e.txt中取出的對應DIE,其部分內容如下:
0x00062112: function [99] * low pc( 0x000502e0 ) high pc( 0x00053730 ) frame base( r7 ) object pointer( {0x0006212a} ) name( "-[OBDFirstConnectViewController showOilPricePickerView]" ) decl file( "/YourSourcePath/OBDFirstConnectViewController.m" ) decl line( 870 ) prototyped( 0x01 ) APPLE instruction set architecture( 0x01 )
可以看出,該DIE包含是方法-[OBDFirstConnectViewController showOilPricePickerView]的內容,其地址范圍是0x000502e0–0x00053730,我們的目標地址0x52846正是在這個范圍內,所以可以判定崩潰發生在該方法的某一行中。
需要指出的是,上面這段DIE是我為了介紹方便直接貼出來的,實際應用的時候需要通過搜索算法找出包含目標符號表崩潰地址(這裡是0x52846)的DIE。
從上述DIE中我們可以獲取到這些信息:
崩潰所在源碼文件:/YourSourcePath/OBDFirstConnectViewController.m 發生崩潰的方法:-[OBDFirstConnectViewController showOilPricePickerView] 發生崩潰的方法在源文件中的行號:870
. debug_line
截止目前,我們可以獲取到發生了崩潰的方法的相關信息,但要想確定崩潰發生的具體行號,還需要.debug_line的幫助。
.debug_line以一個方法為基本塊,急了該方法中每一行對應的符號表地址。通過.debug_info得知崩潰發生的方法地址范圍是0x000502e0–0x00053730,通過起始地址0x000502e0在解析. debug_line得到的line-e.txt中直接搜索即可得到崩潰所在方法的. debug_line數據,其中部分內容如下:
0x00000000000502e0 870 /YourSourcePath/OBDFirstConnectViewController.m 0x00000000000502e0 0 0x00000000000502f0 872 0x000000000005033c 873 0x0000000000050374 874 0x000000000005039e 875 0x00000000000503c8 876 ... 0x0000000000052812 880 0x000000000005283e 881 0x0000000000052846 882 0x00000000000528c8 883 ...
. debug_line段的第一行內容標識了該方法的起始符號表地址,行號及方法所在文件路徑,通過之前得到的崩潰地址0x52846即可得知崩潰發生在882行。
至此我們已經根據崩潰地址解析出了崩潰發生位置的詳細信息:
崩潰所在源碼文件:/YourSourcePath/OBDFirstConnectViewController.m 發生崩潰的方法:-[OBDFirstConnectViewController showOilPricePickerView] 發生崩潰的方法在源文件中的行號:870 崩潰發生在源文件中得行號:882
以上內容為本人工作學習中所得,如有理解錯誤之處,還請指出!
五、參考文檔
Apple’s “Lazy” DWARF Scheme
《Introduction to the DWARF Debugging Format》