.debug_info 节是 ELF (可执行与可链接格式) 文件中存储 DWARF 调试信息的核心节区,它包含了程序的调试信息条目,为调试器提供了源代码与机器代码之间的精确映射关系,是进行源代码级调试的基石。
.debug_info节详解
.debug_info 节,即调试信息节,是 ELF 文件中存储 DWARF (带属性记录格式的调试信息) 调试信息的主要节区。它以编译单元为基本组织单位,每个单元都包含一棵完整的调试信息树。
1. DWARF格式概述
.debug_info 节采用 DWARF 格式来存储调试信息,其核心特征包括:
- 树形结构:调试信息以树形结构组织,每个节点称为一个调试信息条目。节点之间通过父子、兄弟关系连接。
- 编译单元:每个源代码文件通常对应一个或多个编译单元,它是调试信息在
.debug_info 节中的顶层组织单位。
2. 调试信息条目
.debug_info 节的核心是调试信息条目:
// DIE的基本结构
typedef struct {
// 标签(Tag)标识条目类型
// 属性列表(Attributes)
} DebugInfoEntry;
常见的DIE标签类型:
- DW_TAG_compile_unit:编译单元,表示一个源文件的调试信息。
- DW_TAG_subprogram:子程序,表示函数或方法。
- DW_TAG_variable:变量,表示局部或全局变量。
- DW_TAG_base_type:基本类型,如 int、char 等。
- DW_TAG_structure_type:结构体类型。
- DW_TAG_pointer_type:指针类型。
3. 数据结构
.debug_info 节内部通过特定的数据结构进行组织:
// 编译单元头部
typedef struct {
Elf32_Word length; // 编译单元长度
Elf32_Half version; // DWARF版本
Elf32_Word abbrev_offset; // 缩写表偏移
Elf32_Byte address_size; // 地址大小
} DWARF_CU_Header;
// 调试信息条目(DIE)
typedef struct {
Elf32_Word abbrev_code; // 缩写码
// 属性值列表(根据缩写表确定)
} DWARF_DIE;
4. 与其它调试节的关系
.debug_info 节并非孤立存在,它与 ELF 文件中的其他调试节紧密协作:
- .debug_abbrev 节:存储 DIE 的缩写信息,定义了 DIE 的标签和属性格式。
- .debug_str 节:集中存储调试信息中用到的字符串,DIE 中的字符串属性通过偏移引用此节。
- .debug_line 节:存储源代码行号信息,与
.debug_info 中的函数和变量条目关联。
- .debug_loc 节:存储变量的位置描述信息,详细说明变量在不同代码段的存储位置(如在寄存器或栈中)。
5. 编译单元结构
一个编译单元在 .debug_info 节中的典型结构如下:
- 编译单元头部:包含该单元的基本信息,并指向
.debug_abbrev 节的偏移以解析后续 DIE。
- 根 DIE:标签为
DW_TAG_compile_unit,包含源文件名、编译目录、编译器版本等全局信息。
- 子 DIEs:在根 DIE 之下,按树形结构组织着描述函数、变量、类型等实体的子 DIE。
6. 属性类型
DIE 通过属性来描述实体的具体细节,常见属性包括:
- DW_AT_name:名称属性,如函数名、变量名、类型名。
- DW_AT_type:类型属性,指向另一个描述类型的 DIE。
- DW_AT_location:位置属性,通过表达式描述变量在内存或寄存器中的存储位置。
- DW_AT_low_pc / DW_AT_high_pc:地址范围属性,描述函数代码在内存中的起始和结束地址。
- DW_AT_decl_file / DW_AT_decl_line:声明位置属性,描述实体在源代码中的文件和行号。
7. 实际应用示例
我们可以使用 readelf 工具来查看 .debug_info 节的具体内容:
# 查看.debug_info节信息
$ readelf -S program | grep debug_info
[28] .debug_info PROGBITS 00000000 000aa8 00016b 00 0 0 1
# 查看.debug_info详细内容
$ readelf --debug-dump=info program
Contents of the .debug_info section:
Compilation Unit @ offset 0x0:
Length: 0x167 (32-bit)
Version: 4
Abbrev Offset: 0x0
Pointer Size: 8
<0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
<c> DW_AT_producer : (indirect string, offset: 0x0): GNU C11 9.3.0 -mtune=generic -march=x86-64 -g
<10> DW_AT_language : 12 (ANSI C99)
<11> DW_AT_name : (indirect string, offset: 0x44): main.c
<15> DW_AT_comp_dir : (indirect string, offset: 0x4c): /home/user/project
<19> DW_AT_low_pc : 0x1129
<21> DW_AT_high_pc : 0x1145
<29> DW_AT_stmt_list : 0x0
<1><31>: Abbrev Number: 2 (DW_TAG_subprogram)
<32> DW_AT_external : 1
<33> DW_AT_name : (indirect string, offset: 0x5f): main
<37> DW_AT_decl_file : 1
<38> DW_AT_decl_line : 8
<39> DW_AT_type : <0x7a>
<3d> DW_AT_low_pc : 0x1129
<45> DW_AT_high_pc : 0x1145
<4d> DW_AT_frame_base : 1 byte block: 56 (DW_OP_reg6)
<4f> DW_AT_GNU_all_call_sites: 1
<50> DW_AT_sibling : <0x7a>
从输出可以看到一个编译单元(来自 main.c)及其内部的 main 函数的详细信息,包括地址范围、声明行号等。
8. 存储的调试信息内容
.debug_info 节承载了丰富的调试信息,主要包括:
- 源文件信息:源文件名、完整路径、编译目录以及编译器版本信息。
- 函数信息:函数名称、在内存中的起始地址和大小、参数列表及其类型、局部变量信息。
- 变量信息:全局变量和局部变量的名称、类型,以及在不同程序点上的存储位置。
- 类型信息:从基本类型(int, char)到复杂的复合类型(结构体、联合体、数组)和派生类型(指针、引用)的定义。
9. 调试器如何使用
调试器(如 GDB)依赖 .debug_info 节工作:
- 信息解析:加载可执行文件时,解析
.debug_info 节中的编译单元和 DIE 树,建立源代码符号(函数名、变量名)到机器代码地址的映射表。
- 符号查找:当用户输入
break main 时,调试器查找标签为 DW_TAG_subprogram 且 DW_AT_name 为 “main” 的 DIE,从中获取 DW_AT_low_pc 地址并设置断点。
- 类型信息查询:当用户要求打印一个复杂结构体变量时,调试器通过变量的
DW_AT_type 属性找到类型定义 DIE,从而知道该结构的成员布局并正确显示。
10. 优化与剥离
由于调试信息会显著增加文件体积,因此存在相关的优化和剥离操作:
只移除调试信息(.debug_*节),保留普通符号表
$ strip --strip-debug program
### 总结
.debug_info 节作为 DWARF 调试信息生态的核心,通过编译单元和调试信息条目构成的树形结构,为调试器建立了从源代码到机器指令的完整映射。它使得设置断点、查看变量、回溯调用栈等高级调试功能成为可能,是现代软件开发与 [逆向工程](https://yunpan.plus/f/17-1) 中不可或缺的基础设施。理解其结构,对于深层次调试、性能分析或安全研究都至关重要。如果你想深入了解编译、调试等计算机系统知识,可以访问 [云栈社区](https://yunpan.plus) 探索更多相关内容。