什么是 DWARF?
DWARF(Debugging With Attributed Record Formats)是一种标准的调试信息格式,通常嵌入到二进制文件(如 ELF、Mach-O、PE 等)中。DWARF 由编译器生成,调试器使用它来提供源代码级别的调试信息,如变量名称、类型信息、源代码位置、函数调用栈等。
DWARF 格式并非特定于某一平台或架构,而是一种跨平台、跨架构的调试信息格式,广泛应用于许多操作系统和架构(如 Linux、macOS、FreeBSD、Windows 等)。
DWARF 的主要作用:
-
调试信息:DWARF 格式包含程序的调试信息,帮助调试器提供如下功能:
- 显示程序的源代码和源行号。
- 显示变量的名称、类型、作用域等信息。
- 提供函数、类、结构体等符号的映射。
-
栈跟踪:DWARF 格式能描述函数调用堆栈,帮助调试器在崩溃或断点时回溯调用栈,显示程序运行过程中函数调用的关系。
-
程序反汇编:DWARF 也可以帮助调试器显示机器代码和源代码之间的对应关系,从而进行源代码级的调试,即使是在没有源代码的情况下,也能进行低级的调试。
DWARF 文件格式的结构:
DWARF 是基于 标签/值对 的结构,数据按块进行组织,主要包括以下几个部分:
-
Debug Information Entries (DIEs):调试信息条目。每个 DIE 描述了程序中的一个符号(如变量、函数、类、类型等)。
-
Attributes:每个 DIE 由多个属性(attributes)组成,属性提供了关于符号的详细信息,例如符号的名称、类型、大小等。
-
Debug Sections:DWARF 信息存储在程序的不同段中。常见的段包括:
.debug_info
:存储主调试信息。.debug_abbrev
:存储每个 DIE 所需的简略表示信息。.debug_line
:存储源代码的行号信息。.debug_str
:存储字符串(如变量名、函数名等)。.debug_frame
:用于描述堆栈帧信息,帮助栈展开。
-
表:DWARF 还可能包含其他类型的表,例如符号表、字符串表等,用于优化查找和存储。
如何使用 DWARF 信息?
调试器(如 GDB)通过读取二进制文件中的 DWARF 信息来执行源代码级调试。例如,使用 GDB 时,可以通过 DWARF 提供的源代码和变量信息,调试程序的执行。
例如,使用 GDB 进行调试时,GDB 会读取 DWARF 格式的调试信息,允许开发者进行以下操作:
- 查看函数的调用栈。
- 显示变量的值。
- 在源代码级别单步调试。
Dwarf 示例
下面是一个简单的Dwarf示例:
首先,电脑中需要安装gcc, gcc的bin目录下一般会有objcopy, objdump这两个工具
写一个简单的C++代码,包含一个派生类,几个简单的变量
#include <stdio.h>
class CBase {
public:
int MemberA;
int MemberB;
CBase() {
MemberA = 0;
MemberB = 0;
}
CBase(int a, int b) {
MemberA = a;
MemberB = b;
}
};
class CDerived : public CBase {
public:
int MemberC;
CDerived(int a, int b, int c): CBase(a, b) {
MemberC = c;
}
};
int main() {
static CDerived d(1, 2, 3);
int u1 = 1, u2 = 2;
d.MemberC = (u1 + u2) * (d.MemberA - d.MemberB);
printf("member c value: %d", d.MemberC);
return 0;
}
用g++将它编译成exe或者elf
g++ main.cpp -o main.exe
可以用objdump命令打印它的Dwarf
objdump --dwarf=info main.exe > dump.txt
可以得到一份完整的dump文件,文件头如下所示,可以看到编译信息:
main.exe: file format pei-x86-64
Contents of the .debug_info section:
Compilation Unit @ offset 0:
Length: 0x523 (32-bit)
Version: 5
Unit Type: DW_UT_compile (1)
Abbrev Offset: 0
Pointer Size: 8
<0><c>: Abbrev Number: 16 (DW_TAG_compile_unit)
<d> DW_AT_producer : GNU C++17 14.2.0 -mtune=core2 -march=nocona -g
<3c> DW_AT_language : 33 (C++14)
<3d> DW_AT_name : (indirect line string, offset: 0): D:\Documents\TestDwarf\main.cpp
<41> DW_AT_comp_dir : (indirect line string, offset: 0x20): D:\Documents\TestDwarf
<45> DW_AT_ranges : 0xc
<49> DW_AT_low_pc : 0
<51> DW_AT_stmt_list : 0
对于基类,可以在dwarf信息看到类的定义,及其成员和地址偏移量
<1><1d0>: Abbrev Number: 19 (DW_TAG_class_type)
<1d1> DW_AT_name : CBase
<1d7> DW_AT_byte_size : 8
<1d8> DW_AT_decl_file : 2
<1d9> DW_AT_decl_line : 5
<1da> DW_AT_decl_column : 7
<1db> DW_AT_sibling : <0x253>
<2><1df>: Abbrev Number: 9 (DW_TAG_member)
<1e0> DW_AT_name : MemberA
<1e8> DW_AT_decl_file : 2
<1e8> DW_AT_decl_line : 8
<1e9> DW_AT_decl_column : 9
<1e9> DW_AT_type : <0xe2>
<1ed> DW_AT_data_member_location: 0
<1ee> DW_AT_accessibility: 1 (public)
<2><1ee>: Abbrev Number: 9 (DW_TAG_member)
<1ef> DW_AT_name : MemberB
<1f7> DW_AT_decl_file : 2
<1f7> DW_AT_decl_line : 10
<1f8> DW_AT_decl_column : 9
<1f8> DW_AT_type : <0xe2>
<1fc> DW_AT_data_member_location: 4
对于派生类,可以看到继承信息,派生类成员的地址偏移量会在基类后面
<1><25d>: Abbrev Number: 22 (DW_TAG_class_type)
<25e> DW_AT_name : (indirect string, offset: 0): CDerived
<262> DW_AT_byte_size : 12
<263> DW_AT_decl_file : 2
<264> DW_AT_decl_line : 25
<265> DW_AT_decl_column : 7
<266> DW_AT_sibling : <0x2b6>
<2><26a>: Abbrev Number: 23 (DW_TAG_inheritance)
<26b> DW_AT_type : <0x1d0>
<26f> DW_AT_data_member_location: 0
<270> DW_AT_accessibility: 1 (public)
<2><271>: Abbrev Number: 9 (DW_TAG_member)
<272> DW_AT_name : MemberC
<27a> DW_AT_decl_file : 2
<27a> DW_AT_decl_line : 29
<27b> DW_AT_decl_column : 9
<27b> DW_AT_type : <0xe2>
<27f> DW_AT_data_member_location: 8
<280> DW_AT_accessibility: 1 (public)
对于变量,可以看到分配的地址,调试时通过地址+类型信息,可以得到变量的值
通过decl信息,则可以得到源代码的位置
<2><381>: Abbrev Number: 11 (DW_TAG_variable)
<382> DW_AT_name : d
<384> DW_AT_decl_file : 2
<384> DW_AT_decl_line : 38
<385> DW_AT_decl_column : 21
<386> DW_AT_type : <0x25d>
<38a> DW_AT_location : 9 byte block: 3 30 c0 0 40 1 0 0 0 (DW_OP_addr: 14000c030)
<2><394>: Abbrev Number: 11 (DW_TAG_variable)
<395> DW_AT_name : u1
<398> DW_AT_decl_file : 2
<398> DW_AT_decl_line : 41
<399> DW_AT_decl_column : 9
<39a> DW_AT_type : <0xe2>
<39e> DW_AT_location : 2 byte block: 91 6c (DW_OP_fbreg: -20)
<2><3a1>: Abbrev Number: 11 (DW_TAG_variable)
<3a2> DW_AT_name : u2
<3a5> DW_AT_decl_file : 2
<3a5> DW_AT_decl_line : 41
<3a6> DW_AT_decl_column : 17
<3a7> DW_AT_type : <0xe2>
<3ab> DW_AT_location : 2 byte block: 91 68 (DW_OP_fbreg: -24)
对更详细的dwarf信息解释,可以看官方文档
Dwarf 解析示例
某些语言的库有dwarf解析的相关sdk,如python有pyelftools
用来解析Dwarf
示例代码
from elftools.elf.elffile import ELFFile
from elftools.dwarf.dwarf import DWARFError
def parse_dwarf(filename):
# 打开 ELF 文件
with open(filename, 'rb') as f:
elf = ELFFile(f)
# 查找 DWARF 部分
if not elf.has_dwarf_info():
print("No DWARF information found.")
return
dwarf_info = elf.get_dwarf_info()
# 遍历 DWARF 信息
for cu in dwarf_info.iter_CUs(): # 遍历编译单元(Compilation Unit)
print(f"Compilation Unit: {cu.get_name()}")
# 遍历调试信息条目(DIE)
for die in cu.iter_DIEs():
try:
# 打印 DIE 的类型和名称
if die.tag == 'DW_TAG_class_type':
class_name = die.attributes.get('DW_AT_name')
print(f"Class found: {class_name}")
# 打印类的成员变量(DW_TAG_member)
for member in die.iter_children():
if member.tag == 'DW_TAG_member':
member_name = member.attributes.get('DW_AT_name')
print(f" Member: {member_name}")
except DWARFError as e:
print(f"Error processing DIE: {e}")
if __name__ == "__main__":
# 解析 DWARF 信息,假设可执行文件名为 program
parse_dwarf('program')
运行程序后,输出如下所示:
标签:decl,name,DWARF,member,TAG,DW,调试,Dwarf From: https://www.cnblogs.com/Asp1rant/p/18637238Compilation Unit: /path/to/program
Class found: CBase
Member: MemberA
Member: MemberB
Class found: CDerived
Member: MemberC