DWARF
是一个用于在可执行程序和其源代码之间进行关联的调试文件格式。当开发者使用调试编译选项(例如,使用gcc时的-g
标志)编译程序时,编译器会生成这种格式的调试信息。这些信息在后续的调试过程中非常有用,例如,使用gdb
(GNU调试器)。
以下是DWARF
的一些主要特点和相关的详细信息:
历史:DWARF
起初是为了满足UNIX系统上的高效、紧凑和跨平台的调试需求而设计的。自那时起,它已经经历了多个版本,每个版本都增加了新的特性。
版本:从DWARF 1
到DWARF 5
,每个版本都引入了新的特性和改进,以支持新的编程语言特性、编译器优化等。
数据结构:DWARF
信息由一系列的记录组成,这些记录描述了源代码的结构、变量、数据类型等。它们以所谓的"Debugging Information Entries" (DIEs)的形式存储。
查看工具:有一些工具可以查看和处理DWARF
调试信息。例如,readelf
(与binutils
套件一起提供)可以使用-wi
选项来查看DWARF
信息。
优势:
DWARF
是一个可扩展的格式,可以支持多种不同的架构和操作系统。DWARF
的设计使其尽可能紧凑,避免浪费存储空间。DWARF
可以描述各种编程语言构造和复杂的数据类型。与其他格式的比较:除了DWARF
,还有其他的调试格式,例如stabs
和PE/COFF
。但DWARF
因其丰富的特性和跨平台支持而受到许多现代系统的青睐。
总之,DWARF
是一个强大的调试信息格式,允许开发者在程序的执行过程中访问源代码级别的详细信息。对于开发者或者正在学习调试技术的读者来说,了解DWARF
和如何使用它将非常有益。
下面,我们用一个简单的C语言程序,展示它如何与DWARF调试信息交互。
假设我们有一个简单的C程序example.c
:
#include
int add(int a, int b) {
return a + b;
}
int main() {
int sum = add(5, 7);
printf("The sum is: %d\n", sum);
return 0;
}
我们使用GCC编译器编译这个程序,并使用-g
选项来生成DWARF调试信息:
$ gcc -g -o example example.c
现在,可以使用readelf
命令来查看生成的DWARF信息。例如,查看DWARF头部信息:
$ readelf --debug-dump=info example
这会列出关于源代码结构的很多信息,比如变量、函数等,并与其在源代码中的位置进行关联。
为了进一步调试程序,可以使用gdb
:
$ gdb ./example
在GDB中,可以进行各种操作,如设置断点、查看变量值等。所有这些操作都是基于DWARF调试信息的。
例如,可以在add
函数上设置断点:
(gdb) b add
然后运行程序:
(gdb) run
程序将在add
函数处暂停执行,此时,可以查看和修改变量值、单步执行等。
总的来说,DWARF调试信息提供了程序的详细视图,使得像gdb
这样的调试器可以与源代码进行交互。
下面,我们来详细了解DWARF调试信息。
$ readelf --debug-dump=info example
Contents of the .debug_info section:
Compilation Unit @ offset 0x0:
Length: 0xf4 (32-bit)
Version: 5
Unit Type: DW_UT_compile (1)
Abbrev Offset: 0x0
Pointer Size: 8
<0><c>: Abbrev Number: 3 (DW_TAG_compile_unit)
<d> DW_AT_producer : (indirect string, offset: 0x0): GNU C17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection
<11> DW_AT_language : 29 (C11)
<12> DW_AT_name : (indirect line string, offset: 0x29): example.c
<16> DW_AT_comp_dir : (indirect line string, offset: 0x0): /home/majn/python_project/socket_project
<1a> DW_AT_low_pc : 0x1149
<22> DW_AT_high_pc : 0x56
<2a> DW_AT_stmt_list : 0x0
<1><2e>: Abbrev Number: 1 (DW_TAG_base_type)
<2f> DW_AT_byte_size : 8
<30> DW_AT_encoding : 7 (unsigned)
<31> DW_AT_name : (indirect string, offset: 0x8e): long unsigned int
<1><35>: Abbrev Number: 1 (DW_TAG_base_type)
<36> DW_AT_byte_size : 4
<37> DW_AT_encoding : 7 (unsigned)
<38> DW_AT_name : (indirect string, offset: 0x93): unsigned int
<1><3c>: Abbrev Number: 1 (DW_TAG_base_type)
<3d> DW_AT_byte_size : 1
<3e> DW_AT_encoding : 8 (unsigned char)
<3f> DW_AT_name : (indirect string, offset: 0xa0): unsigned char
<1><43>: Abbrev Number: 1 (DW_TAG_base_type)
<44> DW_AT_byte_size : 2
<45> DW_AT_encoding : 7 (unsigned)
<46> DW_AT_name : (indirect string, offset: 0xbc): short unsigned int
<1><4a>: Abbrev Number: 1 (DW_TAG_base_type)
<4b> DW_AT_byte_size : 1
<4c> DW_AT_encoding : 6 (signed char)
<4d> DW_AT_name : (indirect string, offset: 0xa2): signed char
<1><51>: Abbrev Number: 1 (DW_TAG_base_type)
<52> DW_AT_byte_size : 2
<53> DW_AT_encoding : 5 (signed)
<54> DW_AT_name : (indirect string, offset: 0xd6): short int
<1><58>: Abbrev Number: 4 (DW_TAG_base_type)
<59> DW_AT_byte_size : 4
<5a> DW_AT_encoding : 5 (signed)
<5b> DW_AT_name : int
<1><5f>: Abbrev Number: 1 (DW_TAG_base_type)
<60> DW_AT_byte_size : 8
<61> DW_AT_encoding : 5 (signed)
<62> DW_AT_name : (indirect string, offset: 0xb3): long int
<1><66>: Abbrev Number: 1 (DW_TAG_base_type)
<67> DW_AT_byte_size : 1
<68> DW_AT_encoding : 6 (signed char)
<69> DW_AT_name : (indirect string, offset: 0xa9): char
<1><6d>: Abbrev Number: 5 (DW_TAG_const_type)
<6e> DW_AT_type : <0x66>
<1><72>: Abbrev Number: 6 (DW_TAG_subprogram)
<73> DW_AT_external : 1
<73> DW_AT_name : (indirect string, offset: 0xcf): printf
<77> DW_AT_decl_file : 2
<78> DW_AT_decl_line : 356
<7a> DW_AT_decl_column : 12
<7b> DW_AT_prototyped : 1
<7b> DW_AT_type : <0x58>
<7f> DW_AT_declaration : 1
<7f> DW_AT_sibling : <0x8a>
<2><83>: Abbrev Number: 7 (DW_TAG_formal_parameter)
<84> DW_AT_type : <0x8a>
<2><88>: Abbrev Number: 8 (DW_TAG_unspecified_parameters)
<2><89>: Abbrev Number: 0
<1><8a>: Abbrev Number: 9 (DW_TAG_pointer_type)
<8b> DW_AT_byte_size : 8
<8c> DW_AT_type : <0x6d>
<1><90>: Abbrev Number: 10 (DW_TAG_subprogram)
<91> DW_AT_external : 1
<91> DW_AT_name : (indirect string, offset: 0xae): main
<95> DW_AT_decl_file : 1
<96> DW_AT_decl_line : 7
<97> DW_AT_decl_column : 5
<98> DW_AT_type : <0x58>
<9c> DW_AT_low_pc : 0x1161
<a4> DW_AT_high_pc : 0x3e
<ac> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
<ae> DW_AT_call_all_tail_calls: 1
<ae> DW_AT_sibling : <0xc2>
<2><b2>: Abbrev Number: 11 (DW_TAG_variable)
<b3> DW_AT_name : sum
<b7> DW_AT_decl_file : 1
<b8> DW_AT_decl_line : 8
<b9> DW_AT_decl_column : 9
<ba> DW_AT_type : <0x58>
<be> DW_AT_location : 2 byte block: 91 6c (DW_OP_fbreg: -20)
<2><c1>: Abbrev Number: 0
<1><c2>: Abbrev Number: 12 (DW_TAG_subprogram)
<c3> DW_AT_external : 1
<c3> DW_AT_name : add
<c7> DW_AT_decl_file : 1
<c8> DW_AT_decl_line : 3
<c9> DW_AT_decl_column : 5
<ca> DW_AT_prototyped : 1
<ca> DW_AT_type : <0x58>
<ce> DW_AT_low_pc : 0x1149
<d6> DW_AT_high_pc : 0x18
<de> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
<e0> DW_AT_call_all_calls: 1
<2><e0>: Abbrev Number: 2 (DW_TAG_formal_parameter)
<e1> DW_AT_name : a
<e3> DW_AT_decl_file : 1
<e3> DW_AT_decl_line : 3
<e3> DW_AT_decl_column : 13
<e4> DW_AT_type : <0x58>
<e8> DW_AT_location : 2 byte block: 91 6c (DW_OP_fbreg: -20)
<2><eb>: Abbrev Number: 2 (DW_TAG_formal_parameter)
<ec> DW_AT_name : b
<ee> DW_AT_decl_file : 1
<ee> DW_AT_decl_line : 3
<ee> DW_AT_decl_column : 20
<ef> DW_AT_type : <0x58>
<f3> DW_AT_location : 2 byte block: 91 68 (DW_OP_fbreg: -24)
<2><f6>: Abbrev Number: 0
<1><f7>: Abbrev Number: 0
这是一个.debug_info
节的内容,该节是由readelf
从ELF文件中提取的。这个节包含了程序的DWARF调试信息。以下是这些输出信息的简要说明:
Compilation Unit: 表示源文件的一个编译单位。这里是example.c
。
DW_TAG_base_type: 这是基础类型的定义,例如unsigned int
,unsigned char
等。
DW_TAG_subprogram: 代表一个函数或子程序。
DW_TAG_formal_parameter: 表示函数的一个形式参数。
DW_TAG_pointer_type: 表示指针类型。
DW_TAG_const_type: 表示常量类型。
DW_TAG_unspecified_parameters: 表示函数可能有不确定数量的参数。
DW_TAG_variable: 表示一个变量。
上述内容只是一个粗略的说明,DWARF格式包含了大量的复杂信息,用于在不运行程序的情况下,对其进行详细的分析和调试。
在DWARF调试信息中,DW_AT_type: <0x58>
表示一个引用或指针,它指向另一个DWARF条目。具体来说,这是一个偏移量,它从当前编译单元的开始处计算,指向描述该类型的DWARF条目。
在给定的输出中,DW_AT_type: <0x58>
意味着该属性引用了偏移量为0x58
的条目以获取其类型信息。
为了理解这代表什么,我们应该查找偏移量为0x58
的条目。在提供的输出中,可以看到以下条目:
<1><58>: Abbrev Number: 4 (DW_TAG_base_type)
<59> DW_AT_byte_size : 4
<5a> DW_AT_encoding : 5 (signed)
<5b> DW_AT_name : int
这意味着DW_AT_type: <0x58>
引用的是一个基础类型,它是一个带符号的整数int
,大小为4字节。
DW_AT_stmt_list
是一个属性,用于在DWARF调试信息中引用一个行号信息表的偏移量。这个属性关联了源代码的行号和生成的机器代码之间的映射,使得调试工具能够准确地确定执行的源代码行。
具体来说,当我们在调试器中设置断点或在运行时遇到错误时,DW_AT_stmt_list
使得调试器可以准确地显示源代码中的相关行。
值得注意的是,DW_AT_stmt_list
的值是一个偏移量,指向 .debug_line
节区中的一个位置,该位置包含与编译单元关联的行号程序。
这允许开发者和调试工具从机器代码位置回溯到源代码位置,从而可以进行更有效的调试。
Abbrev Number
在 DWARF 调试信息中是一个非常重要的概念,用于表示一个 “abbreviation” 的唯一标识符。它与一个特定的 “abbreviation” 对象关联,该对象定义了一个特定的 DIE(Debugging Information Entry)的结构和属性。简单地说,它是一个用于描述 DIE 结构的模板。
让我们详细解析一下:
DIE (Debugging Information Entry): 在 DWARF 中,调试信息由许多的 DIEs 组成。每个 DIE 描述了编译单位中的某个实体,如变量、类型、函数等。每个 DIE 都有一个 “tag”(如 DW_TAG_base_type
),用于描述其表示的实体类型。
Abbreviations: 由于调试信息可能会很大,DWARF 使用一种缩写机制来减少所需的空间。这种机制通过定义一组 “abbreviations” 来工作,每个 “abbreviation” 描述了一种特定的 DIE 结构(包括其标签和属性)。这些 “abbreviations” 存储在 .debug_abbrev
节区中。
Abbrev Number: 每个 “abbreviation” 都有一个唯一的编号,称为 Abbrev Number
。这允许 .debug_info
节区中的 DIEs 通过简单地引用这个编号来描述其结构,而不是重复每个属性和标签。
因此,当我们在 .debug_info
节区中看到 Abbrev Number: 1
,这意味着可以查找 .debug_abbrev
节区中的编号为 1 的 “abbreviation”,从而知道该 DIE 的结构和属性。
在DWARF调试信息中,DW_AT_low_pc
和 DW_AT_high_pc
是两个非常重要的属性,它们定义了一个代码范围的开始和结束地址。
DW_AT_low_pc: 这是代码段的起始地址。在上面给出的例子中,DW_AT_low_pc: 0x1149
表示相关的代码段(可能是一个函数或其他代码块)从地址 0x1149
开始。
DW_AT_high_pc: 在早期的DWARF版本中,这是代码段的结束地址。但在后来的版本中(尤其是DWARF4之后),这的含义发生了变化,它代表与DW_AT_low_pc
的偏移量,而不是实际的结束地址。在上面给出的例子中,DW_AT_high_pc: 0x56
很可能表示代码段的长度是 0x56
字节。因此,真正的结束地址将是 0x1149 + 0x56
。
为了确定 DW_AT_high_pc
的确切含义(是否是结束地址还是偏移量),需要查看DWARF版本以及可能存在的其他属性,如DW_AT_ranges
。但在大多数情况下,考虑到新版本的DWARF,可以假设DW_AT_high_pc
表示从DW_AT_low_pc
的偏移量。
在DWARF调试信息中,DW_AT_decl_file
属性表示源文件的索引,它指向文件名列表(通常存储在 .debug_line
节中的文件名表中)。
在上面的例子中,DW_AT_decl_file : 1
表示这个特定的实体(可能是一个变量、函数等)是在文件名列表中索引为1的文件中声明的。
为了找出索引1
真正代表的文件名,需要查看 .debug_line
节,并找到对应的文件名表。这个表通常会列出编译单元中用到的所有文件,每个文件都有一个唯一的索引号,从1开始。这样,DWARF信息可以通过简单地引用索引号来指代一个特定的文件,而不是多次重复文件的完整路径名,从而节省空间。