Ads Top

DWARF格式对于debug信息的支持

http://tsecer.blog.163.com/blog/static/150181720118395251117/

1、总体思想:
由于一般的大型软件中包含了非常多的调试信息,所以如何将调试信息在dwarf中进行压缩是一个符号系统的重要问题。这种压缩体现在各个方面,例如,其中使用的证书压缩方法,就是一个数是通过特殊的字节压缩方式而不是通常意义上的ASCII内码;大量使用“状态机”增量表示状态的变化,例如,当行号和汇编指令的对应关系转换,栈帧中寄存器和变量的变化都是用的增量迭代的表示方法。
2、.debug_info和.debug_abbrev
这两个节是天生在一起的两个节,它们是一个“实例和类型”的关系,也就是info节中的内容是abbrev节中的一个结构的实例。在abbrev节中声明了很多中不同的Dwarf类型组合(我们可以想象为C语言中的结构声明,而这些类型都是DWARF格式约定好的类型),然后在info节的每一项都声明自己使用的是abbrev节中的那个类型,也就是说明自己是那个结构的实例。这样两者结合就可以得到系统中的所有类型声明信息
现在依然是一个例子实例来说明这个问题
Contents of the .debug_info section:
  Compilation Unit @ offset 0x0:
   Length:        0x88 (32-bit)
   Version:       3
   Abbrev Offset: 0
   Pointer Size:
  4 一个标准节头信息
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit) 这里声明了自己是abbrev节中第一个类型的一个实例,我们可以交叉到abbrev节的第一个类型声明看一下其中关于这个结构类型的声明,通过这个类型的声明,我们可以知道这里存放的字节流如何划分,它们的长度,代表的意义等逻辑信息
    < c>   DW_AT_producer    : (indirect string, offset: 0x5c): GNU C 4.4.2 20091027 (Red Hat 4.4.2-7)
    <10>   DW_AT_language    : 1 (ANSI C)
    <11>   DW_AT_name        : (indirect string, offset: 0x0): Hello.c     <15>   DW_AT_comp_dir    : (indirect string, offset: 0x8):/home/tsecer/gdb7.2/Src/Obj/gdb 如果编译时使用的是绝对路径,那么这个dir项就不存在,如果是相对路径,那么这个编译路径是存在的    <19>   DW_AT_low_pc      : 0x80483b4    代码段的起始地址    <1d>   DW_AT_high_pc     : 0x80483d0    代码段的结束地址    <21>   DW_AT_stmt_list   : 0x0
 <1><25>: Abbrev Number: 2 (DW_TAG_base_type)
    <26>   DW_AT_byte_size   : 4
    <27>   DW_AT_encoding    : 7 (unsigned)
    <28>   DW_AT_name        : (indirect string, offset: 0x3c): unsigned int
 <1><2c>: Abbrev Number: 2 (DW_TAG_base_type可以看到此处有很多的都是基本类型,也就是我们通常所说的内置类型,例如char int short 等结构,这些是编译器内置识别的一些结构,它们使用的格式都是相同的,都是abbrev节中的第二项
    <2d>   DW_AT_byte_size   : 1
    <2e>   DW_AT_encoding    : 8 (unsigned char)
    <2f>   DW_AT_name        : (indirect string, offset: 0x49): unsigned char
 <1><33>: Abbrev Number: 2 (DW_TAG_base_type)
    <34>   DW_AT_byte_size   : 2
    <35>   DW_AT_encoding    : 7 (unsigned)
    <36>   DW_AT_name        : (indirect string, offset: 0x83): short unsigned int
 <1><3a>: Abbrev Number: 2 (DW_TAG_base_type)
    <3b>   DW_AT_byte_size   : 4
    <3c>   DW_AT_encoding    : 7 (unsigned)
    <3d>   DW_AT_name        : (indirect string, offset: 0x37): long unsigned int
 <1><41>: Abbrev Number: 2 (DW_TAG_base_type)
    <42>   DW_AT_byte_size   : 1
    <43>   DW_AT_encoding    : 6 (signed char)
    <44>   DW_AT_name        : (indirect string, offset: 0x4b): signed char
 <1><48>: Abbrev Number: 2 (DW_TAG_base_type)
    <49>   DW_AT_byte_size   : 2
    <4a>   DW_AT_encoding    : 5 (signed)
    <4b>   DW_AT_name        : (indirect string, offset: 0x28): short int
 <1><4f>: Abbrev Number: 3 (DW_TAG_base_type)
    <50>   DW_AT_byte_size   : 4
    <51>   DW_AT_encoding    : 5 (signed)
    <52>   DW_AT_name        : int
 <1><56>: Abbrev Number: 2 (DW_TAG_base_type)
    <57>   DW_AT_byte_size   : 8
    <58>   DW_AT_encoding    : 5 (signed)
    <59>   DW_AT_name        : (indirect string, offset: 0x96): long long int
 <1><5d>: Abbrev Number: 2 (DW_TAG_base_type)
    <5e>   DW_AT_byte_size   : 8
    <5f>   DW_AT_encoding    : 7 (unsigned)
    <60>   DW_AT_name        : (indirect string, offset: 0x32): long long unsigned int
 <1><64>: Abbrev Number: 2 (DW_TAG_base_type)
    <65>   DW_AT_byte_size   : 4
    <66>   DW_AT_encoding    : 5 (signed)
    <67>   DW_AT_name        : (indirect string, offset: 0x9b): long int
 <1><6b>: Abbrev Number: 4 (DW_TAG_base_type)
    <6c>   DW_AT_byte_size   : 4
    <6d>   DW_AT_encoding    : 7 (unsigned)
 <1><6e>: Abbrev Number: 2 (DW_TAG_base_type)
    <6f>   DW_AT_byte_size   : 1
    <70>   DW_AT_encoding    : 6 (signed char)
    <71>   DW_AT_name        : (indirect string, offset: 0x52): char
 <1><75>: Abbrev Number: 5 (DW_TAG_subprogram这个是前面说的第一个项的子结构,因为它的前面有一个<1>,就表示他是第一项的一个子项,同样前面的所有的项都是第一项的子项。表示子项的方式就是在abbrev中会说明它是否有子项。例如,abbrev的第一项的就有“has children”标志,所以接下来的所有项都是它的子项,直到遇到一个00项为止。以此类推,从而形成一个树形结构
    <76>   DW_AT_external    : 1
    <77>   DW_AT_name        : (indirect string, offset: 0x57): main
    <7b>   DW_AT_decl_file   : 1
    <7c>   DW_AT_decl_line   : 2
    <7d>   DW_AT_type        : <0x4f>
    <81>   DW_AT_low_pc      : 0x80483b4
    <85>   DW_AT_high_pc     : 0x80483d0
    <89>   DW_AT_frame_base  : 1 byte block: 9c  (DW_OP_call_frame_cfa)
Contents of the .debug_abbrev section:
  Number TAG
   1      DW_TAG_compile_unit    [has children]
    DW_AT_producer     DW_FORM_strp
    DW_AT_language     DW_FORM_data1
    DW_AT_name         DW_FORM_strp
    DW_AT_comp_dir     DW_FORM_strp
    DW_AT_low_pc       DW_FORM_addr
    DW_AT_high_pc      DW_FORM_addr
    DW_AT_stmt_list    DW_FORM_data4
   2      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
    DW_AT_name         DW_FORM_strp
   3      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
    DW_AT_name         DW_FORM_string
   4      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
   5      DW_TAG_subprogram    [no children]
    DW_AT_external     DW_FORM_flag
    DW_AT_name         DW_FORM_strp
    DW_AT_decl_file    DW_FORM_data1
    DW_AT_decl_line    DW_FORM_data1
    DW_AT_type         DW_FORM_ref4
    DW_AT_low_pc       DW_FORM_addr
    DW_AT_high_pc      DW_FORM_addr
    DW_AT_frame_base   DW_FORM_block1
3、.debug_frame格式
该节主要是为了表示函数栈帧的关系。也就是当执行一个函数的时候,这个函数中的各个寄存器的存放位置及变化情况,栈帧的计算方法的变化情况等。这些是编译器最为清楚的布局,所以最好让便一起来完成这个操作,从而不必通过指令码来进行猜测。该节的开始可以有若干个CIE(Common Information Entry),顾名思义,这些项主要是用来保存一些通用的信息,这些信息可以被接下来的FDE(Frame Description Entry)引用,从而提高存储的压缩效率。
Contents of the .debug_frame section:
00000000 00000010 ffffffff CIE
  Version:               1
  Augmentation:          ""
  Code alignment factor: 1
  Data alignment factor: -4
  Return address column: 8
  DW_CFA_def_cfa: r4 (esp) ofs 4 这里定义了CFA(Canonical Frame Address)的计算方法,也就是计算出当前的堆栈基准位置的计算方法,之后的大部分变量都将会通过这个CFA偏移来表示。这个指令有两个操作数,一个是寄存器,一个是偏移量,还有一种表示就是直接通过寄存器定义,DW_CFA_DEF_CFA_REGISTER,这种定义CFA的时候只需要一个操作数,就是寄存器编号
  DW_CFA_offset: r8 (eip) at cfa-4 这里定义一个寄存器的存放位置或者说计算方法,就是上面定义的CFA偏移4个字节。表示EIP寄存器在cfa-4的位置存放
  DW_CFA_nop
  DW_CFA_nop
00000014 0000001c 00000000 FDE cie=00000000 pc=080483b4..080483d0
  DW_CFA_advance_loc: 1 to 080483b5
  DW_CFA_def_cfa_offset: 8 表示当前的CFA值向下偏移8个字节,但是CFA使用的寄存器中的值并没有变化
  DW_CFA_advance_loc: 2 to 080483b7
  DW_CFA_offset: r5 (ebp) at cfa-8
  DW_CFA_def_cfa_register: r5 (ebp)
  DW_CFA_advance_loc: 24 to 080483cf
  DW_CFA_restore: r5 (ebp)
  DW_CFA_def_cfa: r4 (esp) ofs 4 这些DW_CFA_XXX的意义以及它们使用的操作数都已经在DWARF格式中规定,大家不要猜测,也不要自己发挥
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
下面是汇编代码
080483b4 <main>:
 80483b4: 55                    push   %ebp
 80483b5: 89 e5                 mov    %esp,%ebp
 80483b7: 83 e4 f0              and    $0xfffffff0,%esp
 80483ba: 83 ec 10              sub    $0x10,%esp
 80483bd: c7 04 24 94 84 04 08  movl   $0x8048494,(%esp)
 80483c4: e8 27 ff ff ff        call   80482f0 <puts@plt>
 80483c9: b8 00 00 00 00        mov    $0x0,%eax
 80483ce: c9                    leave 
 80483cf: c3                    ret   
可以看到,在80483b5指令之后,堆栈的栈顶指针向下偏移了8个字节(call自动push的EIP加上开始的时候push的EBP),但是EBP的值尚未变化
4、.debug_line
这里包含的是行号和机器指令之间的映射关系,这也是实现源代码级调试的重要依据。用户在源代码中设置断点,我们就必须能够通过这个“源文件+行号”的信息来在制定的程序中打断点。它同样使用了一个状态机增量表示这个变化,并且在每个项中同样是通过《行号增加+地址增加》的格式来表示它们映射关系的变化。这里比较复杂一些,所以说的可能多一些。
它基本的思想就是通过一个字节来表示一个<行号变化,地质变化>pair关系,也就是DWARF中说明的special Opcode,这些special Opcode只有一个字节;这个字节中同时还要去掉两外两种操作类型:standard opcode和extended opcode,
standard 是DWARF自己保留的若干个操作符,它们的意义、格式、操作数的个数及大小都是规定好的。但是它们的个数可能之后还会增加,所以每个.debug_line节中都要说明自己使用了多少个标准操作符,这个就是节中的op_base的意义,也就是说,这个节之下的所有操作符都是标准操作符。这样一个低版本的调试器如果不识别某个格式也不会出问题。
extended 类型是以0开始的一个字节,它接下来是一个字节表示自己的长度,这样扩展性更强。具体的意义则由扩展类型确定,当然,dwarf也保留了标准的扩展操作符。
special 类型就是一个字节中剩下的数值了。它是一个和line_base和line_range相关的变量。它的根本目的是为了用一个字节表示大部分<行号偏移、指令偏移对>。它使用的方法就是使用不同的基数来表示,其中商为一个行号偏移,而余数为一个指令偏移。从而对一个字节的地址空间进行划分
Raw dump of debug contents of section .debug_line:
  Offset:                      0x0
  Length:                      52
  DWARF Version:               2
  Prologue Length:             30
  Minimum Instruction Length:  1
  Initial value of 'is_stmt':  1
  Line Base:                   -5
  Line Range:                  14
  Opcode Base:                 13
 Opcodes:
  Opcode 1 has 0 args
  Opcode 2 has 1 args
  Opcode 3 has 1 args
  Opcode 4 has 1 args
  Opcode 5 has 1 args
  Opcode 6 has 0 args
  Opcode 7 has 0 args
  Opcode 8 has 0 args
  Opcode 9 has 1 args
  Opcode 10 has 0 args
  Opcode 11 has 0 args
  Opcode 12 has 1 args
 The Directory Table is empty.
 The File Name Table:
  Entry Dir Time Size Name
  1 0 0 0 Hello.c
 Line Number Statements:
  Extended opcode 2: set Address to 0x80483b4
  Special opcode 7: advance Address by 0 to 0x80483b4 and Line by 2 to 3
  Special opcode 132: advance Address by 9 to 0x80483bd and Line by 1 to 4
  Special opcode 174: advance Address by 12 to 0x80483c9 and Line by 1 to 5
  Special opcode 76: advance Address by 5 to 0x80483ce and Line by 1 to 6
  Advance PC by 2 to 0x80483d0
  Extended opcode 1: End of Sequence
上面对应的line number statements对应的内存值
               00 05  02 B4 83 04 08 14 91 BB 
59 02 02 00 01 01 00 00                      
00 05  02 B4 83 04 08 这里最开始的00 05说明是一个扩展命令,并且接下来的总长度为5个字节,2表示扩展操作码为2,也就是定义基准地址的命令。接下来四个字节为操作数,表示基地址为xxxxxx
0x14  大于op_base 13,所以为特殊节,20-13 = 7   7 % 14 = 0 7 + (-5) =2,对应这里显示的地址增加0,行号增加2的意义,其它一次类推
5、.debug_str
这个我们最不陌生,就是C语言中的字符串组,它们以零结束,放在一个单独的节是为了提高存储效率。因为字符串的长度是任意的,所以在考虑节对其的时候比较有意义。这里只是列出一个其内容,大家一看便知
Contents of the .debug_str section:
  0x00000000 48656c6c 6f2e6300 2f686f6d 652f7473 Hello.c./home/ts
  0x00000010 65636572 2f676462 372e322f 5372632f ecer/gdb7.2/Src/
  0x00000020 4f626a2f 67646200 73686f72 7420696e Obj/gdb.short in
  0x00000030 74006c6f 6e67206c 6f6e6720 756e7369 t.long long unsi
  0x00000040 676e6564 20696e74 00756e73 69676e65 gned int.unsigne
  0x00000050 64206368 6172006d 61696e00 474e5520 d char.main.GNU
  0x00000060 4320342e 342e3220 32303039 31303237 C 4.4.2 20091027
  0x00000070 20285265 64204861 7420342e 342e322d  (Red Hat 4.4.2-
  0x00000080 37290073 686f7274 20756e73 69676e65 7).short unsigne
  0x00000090 6420696e 74006c6f 6e67206c 6f6e6720 d int.long long
  0x000000a0 696e7400      

沒有留言:

技術提供:Blogger.