返回「计算机、信息技术与工程」

ELF文件-DWARF

ELF文件 DWARF DWARF 是一种用于调试的标准化格式,全称为 " Debugging With Attributed Record Formats "。帮助调试器从编译后的二进制文件中恢复源代码级别的信息,比如变量名称、类型、函数调用栈等。

更多
Markdown 结构化数据
本文目录 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 的寄存器指针,通常是 rsprbp

虽然 CIE 中定义了基本的 CFA 计算规则,但每个函数的 FDE 可能会根据其特定的栈帧布局来调整 CFA 的计算。例如:

  • 函数 funcA 可能有更多的局部变量或保存了更多的寄存器,因此它可能需要调整更大的栈空间。
  • 函数 funcB 的栈帧可能相对简单,CFA 计算的偏移量可能更小。

从CFI中可以获取的主要信息

栈帧布局

可以利用CFI的信息, 确定每个栈帧的布局,包括栈指针、帧指针和返回地址的存储位置。

寄存器状态

寄存器保存和恢复信息:通过 DW_CFA_offsetDW_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 信息恢复上一个栈帧的寄存器状态和返回地址。
  • 重复过程:继续回溯,直到到达栈的顶端。
  1. 初始化上下文:在崩溃时或调试时,首先获取当前的上下文状态,包括当前的栈指针(rsp)、程序计数器(rip)等寄存器状态。
  2. 获取 CFA:根据 DWARF 中的 CFI 信息,利用指令如 DW_CFA_def_cfa 来计算当前栈帧的 CFA
  3. 恢复寄存器状态
    • 从栈中恢复保存的寄存器信息(如 rbprip),以确定上一个栈帧的地址。
    • 通过 DW_CFA_offset 指令找到每个寄存器的位置,并从栈中恢复它们的值。
  4. 回溯到上一个栈帧
    • 利用恢复的返回地址寄存器(如 rip),确定上一个函数的程序计数器(PC)。
    • 根据新的 PC 继续从 DWARF 信息中获取该栈帧的 CFI,更新 CFA 和寄存器状态。
  5. 逐帧回溯:重复上述过程,直到到达主函数或回溯到栈的最底部。