App崩溃现场取变量名和其实际值对应关系(不只是寄存器)

2020-10-14  本文已影响0人  一意孤行的程序猿

一、背景

前段时间群里有同学提说希望能在App的崩溃日志中得到崩溃现场当前方法中各个变量名和其当前值(而不是只有寄存器),于是去调研了一下.dSYM文件格式,发现理论上是可行的。

二、方案

1. .dSYM 文件基本概念

.dSYM文件是Xcode在编译iOS工程过程中产生的符号文件,一般用于崩溃日志解析——将崩溃栈中的指令地址转换为实际代码文件及其对应行号。

以下命令可以显示.dSYM文件中各个段的大小:

$ size -m  xxx.dSYM/Contents/Resources/DWARF/xxx  

我们感兴趣的是__DWARF段中的__debug_info节。

2. __debug_info 数据

__debug_info节中存放了各个函数的起始、结束地址及函数中各局部变量的变量名、类型、内存地址(相对于fp或其他寄存器)信息。

以一个简单的测试方法为例:

- (void)myFunction:(int) arg {
    int local = arg + 5;
    int i;

    for (i = 0; i < local; ++i)
        printf("i = %d\n", i);
}

编译出.dSYM文件后,运行以下命令可以导出__debug_info信息:

$ dwarfdump --debug-info ./testDwarf.app.dSYM/Contents/Resources/DWARF/testDwarf

其中与-[ViewController myFunction:]方法相关的部分如下:

0x0004005f:     TAG_subprogram [122] *
                 AT_low_pc( 0x0000000100006760 ) //方法代码起始地址
                 AT_high_pc( 0x00000074 )        //方法代码长度
                 AT_frame_base( reg29 )          //指明此方法的frame base是x29(也就是fp),后面会用到
                 AT_object_pointer( {0x00040078} )
                 AT_name( "-[ViewController myFunction:]" )    //当前测试方法名
                 AT_decl_file( "/Users/jz/bsl/Tests/testDwarf/testDwarf/ViewController.m" )    //文件路径
                 AT_decl_line( 22 )    //行号
                 AT_prototyped( true )

0x00040078:         TAG_formal_parameter [123]  
                     AT_location( fbreg -8 )
                     AT_name( "self" )
                     AT_type( {0x000400bb} ( const ViewController* ) )
                     AT_artificial( true )

0x00040084:         TAG_formal_parameter [123]  
                     AT_location( fbreg -16 )
                     AT_name( "_cmd" )
                     AT_type( {0x000400c5} ( SEL ) )
                     AT_artificial( true )

0x00040090:         TAG_formal_parameter [124]  
                     AT_location( fbreg -20 )        //AT_location字段表明此变量(参数 arg)的内存地址在当前函数的 AT_frame_base 偏移 -20 处,myFunction函数的AT_frame_base 为 x29,则参数arg的实际存放地址为 $x29 - 20
                     AT_name( "arg" )                //参数 arg 变量名
                     AT_decl_file( "/Users/jz/bsl/Tests/testDwarf/testDwarf/ViewController.m" )
                     AT_decl_line( 22 )
                     AT_type( {0x000400d8} ( int ) ) //具体类型信息,见下个代码片断

0x0004009e:         TAG_variable [125]  
                     AT_location( breg31 +24 )       //局部变量 local 的存放位置为 breg31 + 24 == x31 + 24,其中:x31也就是sp
                     AT_name( "local" )              //局部变量local
                     AT_decl_file( "/Users/jz/bsl/Tests/testDwarf/testDwarf/ViewController.m" )
                     AT_decl_line( 23 )
                     AT_type( {0x000400d8} ( int ) ) //具体类型信息,见下个代码片断

0x000400ac:         TAG_variable [125]  
                     AT_location( breg31 +20 )
                     AT_name( "i" )
                     AT_decl_file( "/Users/jz/bsl/Tests/testDwarf/testDwarf/ViewController.m" )
                     AT_decl_line( 24 )
                     AT_type( {0x000400d8} ( int ) )

//arg和local的具体类型信息都指向 0x000400d8
0x000400d8:     TAG_base_type [5]  
                 AT_name( "int" )
                 AT_encoding( DW_ATE_signed )
                 AT_byte_size( 0x04 )

其中重点关注以下字段(详见上面代码片断中的注释):

3. 数据验证

下面验证一下实际的汇编指令是否与上面的__debug_info中的字段数据相吻合。

三、结论

综上可知,通过分析.dSYM文件中的__DWARF__debug_info节中的具体信息,能够在运行时(特别是崩溃时)得到方法内变量名对应的实际存放位置(内存地址),根据需要dump出来相应内存的内容最后放到崩溃日志中即可实现原始需求。

注:因为涉及符号文件解析,可能有两个方案来实现:

  • App中带上符号文件,崩溃时实时解析
  • 将整个栈区内容dump下来,发到服务器上做具体解析 应该都只能用在内测版上。

注:此文只做了基本方案调研,工程化上还有很多需要考虑的点,可能还得实现或改造一个DWARF解析器,不在本文讨论范围之内。

参考资料

推荐👇:

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:789143298 ,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

申请即送:

上一篇 下一篇

猜你喜欢

热点阅读