dwarf调试信息格之式入门

本文介绍了程序调试过程中使用的调试信息来源及格式,重点解析了DWARF标准及其历史发展,同时还展示了如何利用readelf工具查看编译后文件中的调试信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.youkuaiyun.com/coutcin/article/details/1547622

一个程序的完成过程一般是编码、编译、运行的过程,当然这是一个理想的过程,所有的开发几乎都不可能是一帆风顺的,总会有些意想不到的错误,这时便需要调试,良好的调试器应该每一个程序员的必备。
那么调试器使用的调试信息是从哪里来的呢?答案简单的很,是从编译后的文件中来的(注意这里编译的时候要使用特定的编译选项,如VC使用debug模式,GCC使用”-g”)。在编译的时候,编译器会从源文件中收集大量的信息,例如变量名、变量类型、变量所在行号、函数名、函数参数、函数的地址范围、行号和地址的对应关系等等,然后按照一种特定的格式写入到编译后的文件中。调试的时候,调试器便从文件中读取并解析这些信息,以产生人们可读性比较强的信息。简单的说,调试信息就是在机器码和对应的源代码之间建立一座桥梁,大大方便和提高了调试程序的能力。
调试信息一般都是按照什么样的格式存放的呢?主要有下面几种:stabs,COFF,PE-COFF,OMF,IEEE-695和DWARF。其中DWARF在Linux中被普遍使用,我们主要分析它。

DWARF的全称是"Debugging With Attributed Record Formats",遵从GNU FDL授权。现在已经有dwarf1,dwarf2,dwarf3三个版本。

Dwarf最初被贝尔实验室设计用来供Unix System V的sdb调试器使用,并且在1989年被Unix国际化部门的PLSIG (Programming Languages Special Interest Group)标准化成为dwarf1.0。但是dwarf1有着很多明显的缺点,于是PLSIG继续开发,改正了缺点,并加入了对C++等语言的支持,并在1990年正式公布了dwarf2的标准草案。但是稍后由于一些原因,PLSIG被解散,dwarf的开发陷入到多个并不合作的组织中间,造成dwarf2的一些实现细节要取决于特定的编译器。这种情况一直持续到1999年,开发工作受到了来自实现对HP/Inter IA-64架构提供较好支持的推动,成立了dwarf委员会,dwarf的原作者担任负责人,

开始了dwarf3的开发,并于2006年1月份推出dwarf3.0,同时为了解决分歧,dwarf委员会加入了自由标准组织,在自由标准组织与来自Linux基金会的OSDL(Open Source Development Labs)合并后,dwarf重返独立状态并创建了自己的网站:dwarfstd.org

       这三个版本中,dwarf2对dwarf1的改变很大,dwarf3大多是对dwarf2的扩充。
       现在dwarf已经是一种独立的标准,可以支持C、C++、JAVA、Fortran等语言。
       在了解了dwarf的历史之后,来看一下如何查看dwarf所包含的调试信息内容,并在下一篇文章中介绍这些内容的具体意思。查看内容的工具常用的有四种:
1. readelf
GNU提供的二进制工具,功能很多,并不限于读dwarf信息
2. gdb
这个就不用多说了吧,^_^
3. drawfdump
是一个被打包在libdwarf内的程序
4. libdwarf
是一个封装好的C库API,用来读取dwarf信息
       在这里我们主要使用readelf工具。
       先写一个简单的C程序,如下:
      

int  add( int int );

int  main()
{
    
int i, j;
    
    
for(i = 0; i < 100; i += 5, j = i * 5)
        add(i, j);
    
    
return 0;
}


int  add( int  a,  int  b)
{
    
return a + b;
}

 

       然后使用gcc –g hello.c –o hello编译。生成hello文件。

Hello文件是elf格式的,elf一般由多个节(section)组成,不熟悉的可以看前面两篇关于 elf文件格式的文章。调试信息被包含在某几个节中,如果是用dwarf2格式编译的,这些节的名字一般是以.debug开头,如.debug_info,.debug_line,.debug_frame等,如果是用dwarf1格式编译的,这些节的名字一般是.debug,.line等。现在的编译器默认大多数是dwarf2格式编译,当然可以通过gcc的编译选项改变。
现在来看hello文件都包含了哪些调试信息。
首先来看都包含了哪些调试节,使用readelf –S hello命令,产生如下输出(已删一些无关内容):

[Nr] Name              Type            Addr     Off    Size  

 [ 0]                    NULL            00000000 000000 000000
 [ 1] .text              PROGBITS        00008000 008000 0006c4

 [ 2] .ARM.exidx        ARM_EXIDX       000086c4 0086c4 000008

 [ 3] .data              PROGBITS        000086d0 0086d0 000520
 [ 4] .bss               NOBITS          00008bf0 008bf0 000020
 [ 5] .debug_aranges     PROGBITS        00000000 008bf0 000020
 [ 6] .debug_pubnames    PROGBITS        00000000 008c10 000023
 [ 7] .debug_info        PROGBITS        00000000 008c33 0000cc
 [ 8] .debug_abbrev      PROGBITS        00000000 008cff 00006b
 [ 9] .debug_line        PROGBITS        00000000 008d6a 00003e
 [10] .debug_frame       PROGBITS        00000000 008da8 000188
 [11] .debug_loc         PROGBITS        00000000 008f30 000054

 [12] .ARM.attributes   ARM_ATTRIBUTES 00000000 008f84 000010

 [13] .comment           PROGBITS        00000000 008f94 000032
  [14] .shstrtab          STRTAB          00000000 008fc6 0000ad
 [15] .symtab            SYMTAB          00000000 00931c 000780

 [16] .strtab           STRTAB          00000000 009a9c 000416

可见一共包含了17个节,其中7个调试信息的节。
在来看一下各个调试信息节包含的内容,使用readelf –w* hello命令,*是调试节名的第一个字母,如-wi就是查看.debug_info节的内容,-wl就是查看.debug_line节的内容。
对于一个调试文件,.debug_info和.debug_line节是必须有的,其他的不见得。同时也可以自己写链接脚本实现对所有节(不局限于调试节)的控制,如指定每个节的基址等。
.debug_info基本包含了一个源文件内部的大部分信息,如函数、参数、变量、类型等等,我们看一下它的输出:
The section .debug_info contains:
 
 Compilation Unit @ offset 0x0:
   Length:        200
   Version:       2
   Abbrev Offset: 0
   Pointer Size: 4

 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)

     DW_AT_stmt_list   : 0     
     DW_AT_high_pc     : 0x8248 
     DW_AT_low_pc      : 0x81ac
     DW_AT_producer    : GNU C 4.1.1

     DW_AT_language    : 1    (ANSI C)

     DW_AT_name        : hello.c 

     DW_AT_comp_dir    : C:/Program Files/CodeSourcery/Sourcery G++/bin  

 <1><5c>: Abbrev Number: 2 (DW_TAG_subprogram)
     DW_AT_sibling     : <92>      
     DW_AT_external    : 1     
     DW_AT_name        : main   
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 5     
     DW_AT_type        : <92>    
     DW_AT_low_pc      : 0x81ac
     DW_AT_high_pc     : 0x8214 

     DW_AT_frame_base : 0    (location list)

 <2><79>: Abbrev Number: 3 (DW_TAG_variable)

     DW_AT_name        : i  
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 6     
     DW_AT_type        : <92>    
     DW_AT_location    : 2 byte block: 91 68       (DW_OP_fbreg: -24)

 <2><85>: Abbrev Number: 3 (DW_TAG_variable)

     DW_AT_name        : j  
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 6     
     DW_AT_type        : <92>    

     DW_AT_location    : 2 byte block: 91 6c       (DW_OP_fbreg: -20)

 <1><92>: Abbrev Number: 4 (DW_TAG_base_type)

     DW_AT_name        : int      
     DW_AT_byte_size   : 4    
     DW_AT_encoding    : 5    (signed)

 <1><99>: Abbrev Number: 5 (DW_TAG_subprogram)

     DW_AT_external    : 1     
     DW_AT_name        : add     
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 15   
     DW_AT_prototyped : 1    
     DW_AT_type        : <92>    
     DW_AT_low_pc      : 0x8214
     DW_AT_high_pc     : 0x8248 
     DW_AT_frame_base : 0x2a      (location list)

 <2><b2>: Abbrev Number: 6 (DW_TAG_formal_parameter)

     DW_AT_name        : a 
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 14   
     DW_AT_type        : <92>    

     DW_AT_location    : 2 byte block: 91 6c       (DW_OP_fbreg: -20)

 <2><be>: Abbrev Number: 6 (DW_TAG_formal_parameter)

     DW_AT_name        : b 
     DW_AT_decl_file   : 1      
     DW_AT_decl_line   : 14   
     DW_AT_type        : <92>    
     DW_AT_location    : 2 byte block: 91 68       (DW_OP_fbreg: -24)

.debug_line包含了所有地址和源文件行的对应信息,内容如下:
Dump of debug contents of section .debug_line:
 
 Length:                       58
 DWARF Version:                2
 Prologue Length:               30
 Minimum Instruction Length:     2
 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 0x81ac

 Special opcode 9: advance Address by 0 to 0x81ac and Line by 4 to 5

 Special opcode 120: advance Address by 16 to 0x81bc and Line by 3 to 8

 Special opcode 90: advance Address by 12 to 0x81c8 and Line by 1 to 9

 Special opcode 88: advance Address by 12 to 0x81d4 and Line by -1 to 8

 Advance PC by constant 34 to 0x81f6

 Special opcode 78: advance Address by 10 to 0x8200 and Line by 3 to 11

 Special opcode 34: advance Address by 4 to 0x8204 and Line by 1 to 12

 Special opcode 120: advance Address by 16 to 0x8214 and Line by 3 to 15

 Special opcode 174: advance Address by 24 to 0x822c and Line by 1 to 16

 Special opcode 90: advance Address by 12 to 0x8238 and Line by 1 to 17

 Advance PC by 16 to 0x8248
 Extended opcode 1: End of Sequence

其他的信息内容不在一一列举,有兴趣的可以自己查看。在下篇文章中分析这些输出究竟何意。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值