IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    手动解析CrashLog之----原理篇

    王中周发表于 2015-08-10 15:12:49
    love 0

    在上篇文章《手动解析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文件中手工提取,比如:

    1
    
    $ /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_"开头,如下表所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    | 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
    1
    
    $ dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF > info-e.txt
    • .debug_line
    1
    
    $ 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,其部分内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    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中我们可以获取到这些信息:

    1
    2
    3
    
    崩溃所在源码文件:/YourSourcePath/OBDFirstConnectViewController.m
    发生崩溃的方法:-[OBDFirstConnectViewController showOilPricePickerView]
    发生崩溃的方法在源文件中的行号:870
    • . debug_line

    截止目前,我们可以获取到发生了崩溃的方法的相关信息,但要想确定崩溃发生的具体行号,还需要.debug_line的帮助。

    .debug_line以一个方法为基本块,急了该方法中每一行对应的符号表地址。通过.debug_info得知崩溃发生的方法地址范围是0x000502e0–0x00053730,通过起始地址0x000502e0在解析. debug_line得到的line-e.txt中直接搜索即可得到崩溃所在方法的. debug_line数据,其中部分内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    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行。

    至此我们已经根据崩溃地址解析出了崩溃发生位置的详细信息:

    1
    2
    3
    4
    
    崩溃所在源码文件:/YourSourcePath/OBDFirstConnectViewController.m
    发生崩溃的方法:-[OBDFirstConnectViewController showOilPricePickerView]
    发生崩溃的方法在源文件中的行号:870
    崩溃发生在源文件中得行号:882

    以上内容为本人工作学习中所得,如有理解错误之处,还请指出!

    五、参考文档

    • Apple’s “Lazy” DWARF Scheme
    • 《Introduction to the DWARF Debugging Format》


沪ICP备19023445号-2号
友情链接