ELF文件-DWARF
ELF文件 DWARF DWARF 是一种用于调试的标准化格式,全称为 " Debugging With Attributed Record Formats "。帮助调试器从编译后的二进制文件中恢复源代码级别的信息,比如变量名称、类型、函数调用栈等。
本文目录 12 个章节
ELF文件-DWARF
DWARF 是一种用于调试的标准化格式,全称为 "Debugging With Attributed Record Formats"。帮助调试器从编译后的二进制文件中恢复源代码级别的信息,比如变量名称、类型、函数调用栈等。
libunwind 在实现栈回溯时依赖 DWARF 来解析栈帧信息和寄存器状态。
DWARF 是存储在 ELF 文件中的一个(或多个)section,它与其他诸如可执行代码段(.text 段)、符号表(.symtab 段)等并列存在。
DWARF 文件的内容包括:
- 源代码中的函数和变量信息:每个函数或变量在源代码中的名称、数据类型、作用域等。
- 程序的控制流和栈信息:包括函数调用的栈帧结构、寄存器信息,方便回溯调用栈。
- 数据布局信息:帮助调试器理解如何解析复杂的数据结构。
DWARF的内容
Compilation Unit (CU):
- 是 DWARF 文件的基本单元,每个编译单元描述了从某个源文件生成的调试信息。它包含了该源文件中的所有符号和类型定义。
Debug Information Entry (DIE):
- DIE 是 DWARF 中的基本数据结构,每个 DIE 描述一个调试信息项,如一个变量、一个类型定义或一个函数。DIE 是分层结构的,父 DIE 可以包含多个子 DIE。
Call Frame Information (CFI):
- CFI 是 DWARF 中的栈帧信息,用来描述函数调用过程中寄存器和栈帧如何变化。CFI 用于栈回溯,它记录了关于程序中每个函数如何设置栈帧、保存寄存器以及在函数返回时如何恢复这些寄存器的信息。
Location Expressions:
- DWARF 使用位置表达式(Location Expressions)来描述变量在内存或寄存器中的位置。通过解析这些表达式,调试器可以找到程序执行期间变量存储的位置。
Line Number Information:
- DWARF 还包含源代码行号信息,帮助调试器在二进制文件和源代码之间进行映射。每个函数和变量都可以映射到源代码的具体行号,便于调试时快速定位。
每个 编译单元(Compilation Unit,CU) 代表一个源文件。
每个 CU 包含多个 调试信息条目(Debug Information Entry, DIE) ,这些条目描述了该文件中的变量、类型、函数等。
DWARF就是elf文件中的一系列段的类型,对elf的理解可以参见: ELF文件
常见的 DWARF 段包括:
.debug_info:存储程序的调试信息,如变量、类型、函数等。.debug_line:用于将二进制代码的地址映射回源代码行号,方便调试时进行源代码级别的栈回溯。.debug_frame或.eh_frame:包含函数调用时的栈帧信息(CFI),用于栈回溯,帮助恢复各个函数的寄存器状态和调用关系。.debug_abbrev:存储调试信息的缩写,减少冗余数据,提高 DWARF 信息的存储效率。.debug_str:包含 DWARF 调试信息中使用的字符串,如变量名、类型名等。.debug_pubnames和.debug_pubtypes:用于记录公共符号名称和类型信息。
Call Frame Information (CFI)
CFI(Call Frame Information,调用帧信息) 是用来描述函数调用过程中寄存器和栈帧变化的。CFI中有以下两种内容:
- CIE (Common Information Entry):描述函数调用栈的通用信息,例如栈帧的基本结构和栈指针如何变化。
- FDE (Frame Description Entry):描述一个具体函数的栈帧布局,包括哪些寄存器被保存、如何恢复这些寄存器的值等。
.eh_frame段:这是异常处理使用的栈帧信息表,记录了函数的栈帧布局和如何恢复寄存器的规则。即使去除了调试信息,.eh_frame段通常仍然会被保留,因为它对于栈回溯和异常处理(如 C++ 的异常机制)是必需的。
如果在编译时明确使用了选项去除
.eh_frame段(例如通过-fno-asynchronous-unwind-tables),则libunwind将无法使用 CFI 信息进行栈回溯。这种情况下,libunwind只能依赖帧指针或栈指针进行回溯,这会导致栈回溯的能力大大受限,特别是在使用了大量优化(如内联、帧指针消除)时,回溯可能不准确甚至失败。
在编译时,以下编译选项会影响 libunwind 进行栈回溯的能力:
-g:启用调试信息,生成.debug_*段,但这对于libunwind来说并非必需。-fno-omit-frame-pointer:保留帧指针,确保libunwind可以通过帧指针回溯栈帧。-fno-asynchronous-unwind-tables:去除.eh_frame段,禁止生成 CFI 信息,可能会导致栈回溯失败。CFA (Canonical Frame Address)是栈帧的规范化基址,通过 DWARF 提供的
DW_CFA_def_cfa指令定义。CFA 通常用于定位函数的局部变量、函数参数和返回地址等栈帧信息。
当调试信息中的属性(例如 DW_AT_frame_base)标记为 DW_OP_call_frame_cfa,它的含义是当前函数的栈帧基址可以通过 CFA 来获得, "使用当前的 CFA 作为栈帧的基址"。
CIE(Common Information Entry)
CIE 提供了多个函数共享的通用栈帧信息,定义了如何计算栈帧基址(CFA)及如何处理返回地址。
- Version: DWARF 版本号。
- Augmentation: 增强字段,描述了附加信息,例如返回地址寄存器等。
- Code alignment factor: 程序计数器的对齐因子,通常与指令大小相关。
- Data alignment factor: 数据对齐因子,影响栈指针如何变化。
- Return address column: 指明返回地址所在的寄存器位置。
- DW_CFA_def_cfa: 定义栈帧的基准地址(CFA),用于定位局部变量、保存的寄存器等。它通常由栈指针寄存器(如
rsp)加上某个偏移量计算得出。
FDE(Frame Description Entry)
FDE 描述了特定函数的栈帧布局,主要通过指令来更新 CFA 和寄存器的状态。
- pc range: 函数的指令范围(程序计数器)。
- DW_CFA_def_cfa_offset: 指定 CFA 偏移量,通常表示栈上分配的空间大小。
- DW_CFA_offset: 保存某个寄存器的值到栈上,偏移量相对于 CFA。
- DW_CFA_advance_loc: 程序计数器前进,用于表示指令执行的推进。
- DW_CFA_def_cfa_register: 更新 CFA 的寄存器指针,通常是
rsp或rbp。
虽然 CIE 中定义了基本的 CFA 计算规则,但每个函数的 FDE 可能会根据其特定的栈帧布局来调整 CFA 的计算。例如:
- 函数
funcA可能有更多的局部变量或保存了更多的寄存器,因此它可能需要调整更大的栈空间。- 函数
funcB的栈帧可能相对简单,CFA 计算的偏移量可能更小。
从CFI中可以获取的主要信息
栈帧布局
可以利用CFI的信息, 确定每个栈帧的布局,包括栈指针、帧指针和返回地址的存储位置。
寄存器状态
寄存器保存和恢复信息:通过 DW_CFA_offset 和 DW_CFA_restore 等指令,DWARF 描述了寄存器的保存和恢复过程。根据这些指令,读取栈中的寄存器值,以恢复函数返回时的寄存器状态。
程序与调试信息示例
程序源码
示例程序:
#include <stdio.h>
void funcA(int a) {
int x = a * 2; // 局部变量
printf("funcA: x = %d\n", x);
// 模拟崩溃点
*((volatile int *)0) = 0;
}
void funcB() {
int b = 10; // 局部变量
funcA(b); // 调用 funcA
}
int main() {
funcB(); // 调用 funcB
return 0;
}
该程序可以使用gdb执行和调试。运行时,会出现崩溃信号:
(gdb) run
Starting program: /home/[redacted-user]/example
funcA: x = 20
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554678 in funcA (a=10) at example.c:7
7 *((volatile int *)0) = 0;
gdb可以输出崩溃的调用栈回溯结果:
(gdb) backtrace
#0 0x0000555555554678 in funcA (a=10) at example.c:7
#1 0x000055555555469a in funcB () at example.c:12
#2 0x00005555555546ab in main () at example.c:16
汇编代码
objdump -d ./example
./example: 文件格式 elf64-x86-64
Disassembly of section .init:
00000000000004f0 <_init>:
4f0: 48 83 ec 08 sub $0x8,%rsp
4f4: 48 8b 05 ed 0a 20 00 mov 0x200aed(%rip),%rax # 200fe8 <__gmon_start__>
4fb: 48 85 c0 test %rax,%rax
4fe: 74 02 je 502 <_init+0x12>
500: ff d0 callq *%rax
502: 48 83 c4 08 add $0x8,%rsp
506: c3 retq
Disassembly of section .plt:
0000000000000510 <.plt>:
510: ff 35 aa 0a 20 00 pushq 0x200aaa(%rip) # 200fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
516: ff 25 ac 0a 20 00 jmpq *0x200aac(%rip) # 200fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
51c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000000520 :
520: ff 25 aa 0a 20 00 jmpq *0x200aaa(%rip) # 200fd0
526: 68 00 00 00 00 pushq $0x0
52b: e9 e0 ff ff ff jmpq 510 <.plt>
Disassembly of section .plt.got:
0000000000000530 <__cxa_finalize@plt>:
530: ff 25 c2 0a 20 00 jmpq *0x200ac2(%rip) # 200ff8 <__cxa_finalize@GLIBC_2.2.5>
536: 66 90 xchg %ax,%ax
Disassembly of section .text:
0000000000000540 <_start>:
540: 31 ed xor %ebp,%ebp
542: 49 89 d1 mov %rdx,%r9
545: 5e pop %rsi
546: 48 89 e2 mov %rsp,%rdx
549: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
54d: 50 push %rax
54e: 54 push %rsp
54f: 4c 8d 05 da 01 00 00 lea 0x1da(%rip),%r8 # 730 <__libc_csu_fini>
556: 48 8d 0d 63 01 00 00 lea 0x163(%rip),%rcx # 6c0 <__libc_csu_init>
55d: 48 8d 3d 39 01 00 00 lea 0x139(%rip),%rdi # 69d <main>
564: ff 15 76 0a 20 00 callq *0x200a76(%rip) # 200fe0 <__libc_start_main@GLIBC_2.2.5>
56a: f4 hlt
56b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000000570 <deregister_tm_clones>:
570: 48 8d 3d 99 0a 20 00 lea 0x200a99(%rip),%rdi # 201010 <__TMC_END__>
577: 55 push %rbp
578: 48 8d 05 91 0a 20 00 lea 0x200a91(%rip),%rax # 201010 <__TMC_END__>
57f: 48 39 f8 cmp %rdi,%rax
582: 48 89 e5 mov %rsp,%rbp
585: 74 19 je 5a0 <deregister_tm_clones+0x30>
587: 48 8b 05 4a 0a 20 00 mov 0x200a4a(%rip),%rax # 200fd8 <_ITM_deregisterTMCloneTable>
58e: 48 85 c0 test %rax,%rax
591: 74 0d je 5a0 <deregister_tm_clones+0x30>
593: 5d pop %rbp
594: ff e0 jmpq *%rax
596: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
59d: 00 00 00
5a0: 5d pop %rbp
5a1: c3 retq
5a2: 0f 1f 40 00 nopl 0x0(%rax)
5a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
5ad: 00 00 00
00000000000005b0 <register_tm_clones>:
5b0: 48 8d 3d 59 0a 20 00 lea 0x200a59(%rip),%rdi # 201010 <__TMC_END__>
5b7: 48 8d 35 52 0a 20 00 lea 0x200a52(%rip),%rsi # 201010 <__TMC_END__>
5be: 55 push %rbp
5bf: 48 29 fe sub %rdi,%rsi
5c2: 48 89 e5 mov %rsp,%rbp
5c5: 48 c1 fe 03 sar $0x3,%rsi
5c9: 48 89 f0 mov %rsi,%rax
5cc: 48 c1 e8 3f shr $0x3f,%rax
5d0: 48 01 c6 add %rax,%rsi
5d3: 48 d1 fe sar %rsi
5d6: 74 18 je 5f0 <register_tm_clones+0x40>
5d8: 48 8b 05 11 0a 20 00 mov 0x200a11(%rip),%rax # 200ff0 <_ITM_registerTMCloneTable>
5df: 48 85 c0 test %rax,%rax
5e2: 74 0c je 5f0 <register_tm_clones+0x40>
5e4: 5d pop %rbp
5e5: ff e0 jmpq *%rax
5e7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
5ee: 00 00
5f0: 5d pop %rbp
5f1: c3 retq
5f2: 0f 1f 40 00 nopl 0x0(%rax)
5f6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
5fd: 00 00 00
0000000000000600 <__do_global_dtors_aux>:
600: 80 3d 09 0a 20 00 00 cmpb $0x0,0x200a09(%rip) # 201010 <__TMC_END__>
607: 75 2f jne 638 <__do_global_dtors_aux+0x38>
609: 48 83 3d e7 09 20 00 cmpq $0x0,0x2009e7(%rip) # 200ff8 <__cxa_finalize@GLIBC_2.2.5>
610: 00
611: 55 push %rbp
612: 48 89 e5 mov %rsp,%rbp
615: 74 0c je 623 <__do_global_dtors_aux+0x23>
617: 48 8b 3d ea 09 20 00 mov 0x2009ea(%rip),%rdi # 201008 <__dso_handle>
61e: e8 0d ff ff ff callq 530 <__cxa_finalize@plt>
623: e8 48 ff ff ff callq 570 <deregister_tm_clones>
628: c6 05 e1 09 20 00 01 movb $0x1,0x2009e1(%rip) # 201010 <__TMC_END__>
62f: 5d pop %rbp
630: c3 retq
631: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
638: f3 c3 repz retq
63a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000000640 <frame_dummy>:
640: 55 push %rbp
641: 48 89 e5 mov %rsp,%rbp
644: 5d pop %rbp
645: e9 66 ff ff ff jmpq 5b0 <register_tm_clones>
000000000000064a <funcA>:
64a: 55 push %rbp
64b: 48 89 e5 mov %rsp,%rbp
64e: 48 83 ec 20 sub $0x20,%rsp
652: 89 7d ec mov %edi,-0x14(%rbp)
655: 8b 45 ec mov -0x14(%rbp),%eax
658: 01 c0 add %eax,%eax
65a: 89 45 fc mov %eax,-0x4(%rbp)
65d: 8b 45 fc mov -0x4(%rbp),%eax
660: 89 c6 mov %eax,%esi
662: 48 8d 3d db 00 00 00 lea 0xdb(%rip),%rdi # 744 <_IO_stdin_used+0x4>
669: b8 00 00 00 00 mov $0x0,%eax
66e: e8 ad fe ff ff callq 520
673: b8 00 00 00 00 mov $0x0,%eax
678: c7 00 00 00 00 00 movl $0x0,(%rax)
67e: 90 nop
67f: c9 leaveq
680: c3 retq
0000000000000681 <funcB>:
681: 55 push %rbp
682: 48 89 e5 mov %rsp,%rbp
685: 48 83 ec 10 sub $0x10,%rsp
689: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
690: 8b 45 fc mov -0x4(%rbp),%eax
693: 89 c7 mov %eax,%edi
695: e8 b0 ff ff ff callq 64a <funcA>
69a: 90 nop
69b: c9 leaveq
69c: c3 retq
000000000000069d <main>:
69d: 55 push %rbp
69e: 48 89 e5 mov %rsp,%rbp
6a1: b8 00 00 00 00 mov $0x0,%eax
6a6: e8 d6 ff ff ff callq 681 <funcB>
6ab: b8 00 00 00 00 mov $0x0,%eax
6b0: 5d pop %rbp
6b1: c3 retq
6b2: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
6b9: 00 00 00
6bc: 0f 1f 40 00 nopl 0x0(%rax)
00000000000006c0 <__libc_csu_init>:
6c0: 41 57 push %r15
6c2: 41 56 push %r14
6c4: 49 89 d7 mov %rdx,%r15
6c7: 41 55 push %r13
6c9: 41 54 push %r12
6cb: 4c 8d 25 e6 06 20 00 lea 0x2006e6(%rip),%r12 # 200db8 <__frame_dummy_init_array_entry>
6d2: 55 push %rbp
6d3: 48 8d 2d e6 06 20 00 lea 0x2006e6(%rip),%rbp # 200dc0 <__init_array_end>
6da: 53 push %rbx
6db: 41 89 fd mov %edi,%r13d
6de: 49 89 f6 mov %rsi,%r14
6e1: 4c 29 e5 sub %r12,%rbp
6e4: 48 83 ec 08 sub $0x8,%rsp
6e8: 48 c1 fd 03 sar $0x3,%rbp
6ec: e8 ff fd ff ff callq 4f0 <_init>
6f1: 48 85 ed test %rbp,%rbp
6f4: 74 20 je 716 <__libc_csu_init+0x56>
6f6: 31 db xor %ebx,%ebx
6f8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
6ff: 00
700: 4c 89 fa mov %r15,%rdx
703: 4c 89 f6 mov %r14,%rsi
706: 44 89 ef mov %r13d,%edi
709: 41 ff 14 dc callq *(%r12,%rbx,8)
70d: 48 83 c3 01 add $0x1,%rbx
711: 48 39 dd cmp %rbx,%rbp
714: 75 ea jne 700 <__libc_csu_init+0x40>
716: 48 83 c4 08 add $0x8,%rsp
71a: 5b pop %rbx
71b: 5d pop %rbp
71c: 41 5c pop %r12
71e: 41 5d pop %r13
720: 41 5e pop %r14
722: 41 5f pop %r15
724: c3 retq
725: 90 nop
726: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
72d: 00 00 00
0000000000000730 <__libc_csu_fini>:
730: f3 c3 repz retq
Disassembly of section .fini:
0000000000000734 <_fini>:
734: 48 83 ec 08 sub $0x8,%rsp
738: 48 83 c4 08 add $0x8,%rsp
73c: c3 retq
debug_frame
该程序的debug_frame或eh_frame内容如下.
CIE 可以是全局公共的, 被共享. FDE相当于继承或者共享CIE的内容
uwa@wzp-B365-HD3:~$ readelf --debug-dump=frames ./example
.eh_frame 节的内容:
00000000 0000000000000014 00000000 CIE
Version: 1
Augmentation: "zR"
Code alignment factor: 1
Data alignment factor: -8
Return address column: 16
Augmentation data: 1b
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_offset: r16 (rip) at cfa-8
DW_CFA_undefined: r16 (rip)
00000018 0000000000000014 0000001c FDE cie=00000000 pc=0000000000000540..000000000000056b
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
00000030 0000000000000014 00000000 CIE
Version: 1
Augmentation: "zR"
Code alignment factor: 1
Data alignment factor: -8
Return address column: 16
Augmentation data: 1b
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_offset: r16 (rip) at cfa-8
DW_CFA_nop
DW_CFA_nop
00000048 0000000000000024 0000001c FDE cie=00000030 pc=0000000000000510..0000000000000530
DW_CFA_def_cfa_offset: 16
DW_CFA_advance_loc: 6 to 0000000000000516
DW_CFA_def_cfa_offset: 24
DW_CFA_advance_loc: 10 to 0000000000000520
DW_CFA_def_cfa_expression (DW_OP_breg7 (rsp): 8; DW_OP_breg16 (rip): 0; DW_OP_lit15; DW_OP_and; DW_OP_lit11; DW_OP_ge; DW_OP_lit3; DW_OP_shl; DW_OP_plus)
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
00000070 0000000000000014 00000044 FDE cie=00000030 pc=0000000000000530..0000000000000538
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
00000088 000000000000001c 0000005c FDE cie=00000030 pc=000000000000064a..0000000000000681
DW_CFA_advance_loc: 1 to 000000000000064b
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r6 (rbp) at cfa-16
DW_CFA_advance_loc: 3 to 000000000000064e
DW_CFA_def_cfa_register: r6 (rbp)
DW_CFA_advance_loc: 50 to 0000000000000680
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
000000a8 000000000000001c 0000007c FDE cie=00000030 pc=0000000000000681..000000000000069d
DW_CFA_advance_loc: 1 to 0000000000000682
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r6 (rbp) at cfa-16
DW_CFA_advance_loc: 3 to 0000000000000685
DW_CFA_def_cfa_register: r6 (rbp)
DW_CFA_advance_loc: 23 to 000000000000069c
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
000000c8 000000000000001c 0000009c FDE cie=00000030 pc=000000000000069d..00000000000006b2
DW_CFA_advance_loc: 1 to 000000000000069e
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r6 (rbp) at cfa-16
DW_CFA_advance_loc: 3 to 00000000000006a1
DW_CFA_def_cfa_register: r6 (rbp)
DW_CFA_advance_loc: 16 to 00000000000006b1
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
000000e8 0000000000000044 000000bc FDE cie=00000030 pc=00000000000006c0..0000000000000725
DW_CFA_advance_loc: 2 to 00000000000006c2
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r15 (r15) at cfa-16
DW_CFA_advance_loc: 2 to 00000000000006c4
DW_CFA_def_cfa_offset: 24
DW_CFA_offset: r14 (r14) at cfa-24
DW_CFA_advance_loc: 5 to 00000000000006c9
DW_CFA_def_cfa_offset: 32
DW_CFA_offset: r13 (r13) at cfa-32
DW_CFA_advance_loc: 2 to 00000000000006cb
DW_CFA_def_cfa_offset: 40
DW_CFA_offset: r12 (r12) at cfa-40
DW_CFA_advance_loc: 8 to 00000000000006d3
DW_CFA_def_cfa_offset: 48
DW_CFA_offset: r6 (rbp) at cfa-48
DW_CFA_advance_loc: 8 to 00000000000006db
DW_CFA_def_cfa_offset: 56
DW_CFA_offset: r3 (rbx) at cfa-56
DW_CFA_advance_loc: 13 to 00000000000006e8
DW_CFA_def_cfa_offset: 64
DW_CFA_advance_loc: 50 to 000000000000071a
DW_CFA_def_cfa_offset: 56
DW_CFA_advance_loc: 1 to 000000000000071b
DW_CFA_def_cfa_offset: 48
DW_CFA_advance_loc: 1 to 000000000000071c
DW_CFA_def_cfa_offset: 40
DW_CFA_advance_loc: 2 to 000000000000071e
DW_CFA_def_cfa_offset: 32
DW_CFA_advance_loc: 2 to 0000000000000720
DW_CFA_def_cfa_offset: 24
DW_CFA_advance_loc: 2 to 0000000000000722
DW_CFA_def_cfa_offset: 16
DW_CFA_advance_loc: 2 to 0000000000000724
DW_CFA_def_cfa_offset: 8
DW_CFA_nop
00000130 0000000000000010 00000104 FDE cie=00000030 pc=0000000000000730..0000000000000732
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
00000144 ZERO terminator
DW_CFA_def_cfa: rsp, 16:表示 CFA 是栈指针rsp加上 16 字节的偏移。DW_CFA_offset: rip at cfa-8:表示返回地址(rip)保存于栈顶 CFA - 8 位置。
FDE 2 (函数 funcB)
000000a8 000000000000001c 0000007c FDE cie=00000030 pc=0000000000000681..000000000000069d
DW_CFA_advance_loc: 1 to 0000000000000682
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r6 (rbp) at cfa-16
DW_CFA_advance_loc: 3 to 0000000000000685
DW_CFA_def_cfa_register: r6 (rbp)
DW_CFA_advance_loc: 23 to 000000000000069c
DW_CFA_def_cfa: r7 (rsp) ofs 8
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
DW_CFA_advance_loc表示 PC(Program Counter,程序计数器)向前推进 1 个字节,到地址0x682。这通常意味着程序的执行已经进入了新的指令,可能是一个新的函数调用或栈操作。DW_CFA_def_cfa_offset表示当前 CFA 的偏移量为 16 字节。
如果当前 SP 的值为 0x1000,那么 CFA 的计算如下:
CFA = SP + 16 = 0x1000 + 0x10 = 0x1010
DW_CFA_offset: r6 (rbp) at cfa-16 描述了 rbp(帧指针寄存器) 的保存位置。
假设当前 CFA = 0x1008,那么 rbp 保存的位置为:
rbp 保存位置 = 0x1008 - 16 = 0x0FF8
借助DWARF信息进行栈回溯的基本流程
由以上信息可知, DWARF通过 .eh_frame 和 .debug_frame 段来提供调用帧信息(CFI)来描述栈帧布局。 调试工具或者回溯工具可以利用DWARF提供的CFI, 对调用栈进行回溯, 以获得造成崩溃的指令的调用链, 并且可以获取每一帧的寄存器信息.
- 获取当前 PC 和栈指针(SP):首先从当前的执行上下文中获取程序计数器(PC)和栈指针(SP)。
- 读取 CFI 信息:使用 DWARF 的 CFI 信息找到当前栈帧中保存的返回地址和寄存器位置。
- 恢复上一个栈帧:根据 CFI 信息恢复上一个栈帧的寄存器状态和返回地址。
- 重复过程:继续回溯,直到到达栈的顶端。
- 初始化上下文:在崩溃时或调试时,首先获取当前的上下文状态,包括当前的栈指针(
rsp)、程序计数器(rip)等寄存器状态。 - 获取 CFA:根据 DWARF 中的 CFI 信息,利用指令如
DW_CFA_def_cfa来计算当前栈帧的 CFA。 - 恢复寄存器状态:
- 从栈中恢复保存的寄存器信息(如
rbp、rip),以确定上一个栈帧的地址。 - 通过
DW_CFA_offset指令找到每个寄存器的位置,并从栈中恢复它们的值。
- 从栈中恢复保存的寄存器信息(如
- 回溯到上一个栈帧:
- 利用恢复的返回地址寄存器(如
rip),确定上一个函数的程序计数器(PC)。 - 根据新的 PC 继续从 DWARF 信息中获取该栈帧的 CFI,更新 CFA 和寄存器状态。
- 利用恢复的返回地址寄存器(如
- 逐帧回溯:重复上述过程,直到到达主函数或回溯到栈的最底部。