一个GDB调试器的前导知识以及初步设计

555555554000-555555555000 r--p 00000000 08:05 679264                     /home/daligh/project/gbdSelf/minidbg-tut_setup/src/hello
555555555000-555555556000 r-xp 00001000 08:05 679264                     /home/daligh/project/gbdSelf/minidbg-tut_setup/src/hello
555555556000-555555557000 r--p 00002000 08:05 679264                     /home/daligh/project/gbdSelf/minidbg-tut_setup/src/hello
555555557000-555555559000 rw-p 00002000 08:05 679264                     /home/daligh/project/gbdSelf/minidbg-tut_setup/src/hello
7ffff7fc9000-7ffff7fcd000 r--p 00000000 00:00 0                          [vvar]
7ffff7fcd000-7ffff7fcf000 r-xp 00000000 00:00 0                          [vdso]
@                                                                                       

-------------------------代码段地址

0000000000001189 <main>:
    1189:       f3 0f 1e fa             endbr64 
    118d:       55                      push   %rbp
    118e:       48 89 e5                mov    %rsp,%rbp
    1191:       48 8d 05 6d 0e 00 00    lea    0xe6d(%rip),%rax        # 2005 <_ZStL19piecewise_construct+0x1>
    1198:       48 89 c6                mov    %rax,%rsi
    119b:       48 8d 05 9e 2e 00 00    lea    0x2e9e(%rip),%rax        # 4040 <_ZSt4cout@@GLIBCXX_3.4>
    11a2:       48 89 c7                mov    %rax,%rdi
    11a5:       e8 d6 fe ff ff          callq  1080 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
    11aa:       b8 00 00 00 00          mov    $0x0,%eax
    11af:       5d                      pop    %rbp
    11b0:       c3                      retq   

----------------------------调用之前的位置
555555554000+1191=0x555555555191

---------------------------调用位置
555555554000+11a5=0x5555555551A5

------------------------在调用指令之后位置设置断点
555555554000+11aa=5555 5555 51AA
b 0x5555555551AA

------------------------写入rip寄存器回到0x555555555191
register write rip 0x555555555191

----------------应该输出两次

-------------测试行号
0x116f+0x555555554000=0x55555555516F

基础

DWARF

**DWARF(Debugging With Attributed Record Formats(使用属性化记录格式调试))

DWARF中记录了源代码和可执行文件分关系,每个部分都有自己的信息,是一种调试文件格式,许多编译器调试器都使用它来支持源代码级调试

结构

  • 每个DIE都包含一个tag(如DW_TAG_variable,DW_TAG_pointer_type,DW_TAG_subprogram等)以及一系列的attributes。
  • 每个DIE还可以包含child DIEs,这些DIEs构成的树结构共同描述一个变量、数据类型、函数等不同的程序构造。
  • DIE中的每个attribute可以引用另一个DIE,例如一个描述变量的DIE,它会包含一个属性DW_AT_type来指向一个描述变量数据类型的DIE。

解释:

在使用dwarfdump查看的信息(部分)中

.debug_info

COMPILE_UNIT<header overall offset = 0x00000000>:
< 0><0x0000000b>  DW_TAG_compile_unit
                    DW_AT_producer              GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-***-protection -fcf-protection
                    DW_AT_language              DW_LANG_C_plus_plus
                    DW_AT_name                  test.cpp
                    DW_AT_comp_dir              /home/daligh/project/gbdSelf/minidbg-tut_setup
                    DW_AT_low_pc                0x00001129
                    DW_AT_high_pc               <offset-from-lowpc>54
                    DW_AT_stmt_list             0x00000000

LOCAL_SYMBOLS:
< 1><0x0000002d>    DW_TAG_subprogram
                      DW_AT_external              yes(1)
                      DW_AT_name                  main
                      DW_AT_decl_file             0x00000001 /home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp
                      DW_AT_decl_line             0x00000001
                      DW_AT_decl_column           0x00000005
                      DW_AT_type                  <0x00000077>
                      DW_AT_low_pc                0x00001129
                      DW_AT_high_pc               <offset-from-lowpc>54
                      DW_AT_frame_base            len 0x0001: 9c: DW_OP_call_frame_cfa
                      DW_AT_GNU_all_call_sites    yes(1)
                      DW_AT_sibling               <0x00000077>
< 2><0x0000004f>      DW_TAG_variable
                        DW_AT_name                  a
                        DW_AT_decl_file             0x00000001 /home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp
                        DW_AT_decl_line             0x00000002
                        DW_AT_decl_column           0x0000000a
                        DW_AT_type                  <0x0000007e>
                        DW_AT_location              len 0x0002: 9158: DW_OP_fbreg -40
< 2><0x0000005c>      DW_TAG_variable
                        DW_AT_name                  b
                        DW_AT_decl_file             0x00000001 /home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp
                        DW_AT_decl_line             0x00000003
                        DW_AT_decl_column           0x0000000a
                        DW_AT_type                  <0x0000007e>
                        DW_AT_location              len 0x0002: 9160: DW_OP_fbreg -32
< 2><0x00000069>      DW_TAG_variable
                        DW_AT_name                  c
                        DW_AT_decl_file             0x00000001 /home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp
                        DW_AT_decl_line             0x00000004
                        DW_AT_decl_column           0x0000000a
                        DW_AT_type                  <0x0000007e>
                        DW_AT_location              len 0x0002: 9168: DW_OP_fbreg -24
< 1><0x00000077>    DW_TAG_base_type
                      DW_AT_byte_size             0x00000004
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_name                  int
< 1><0x0000007e>    DW_TAG_base_type
                      DW_AT_byte_size             0x00000008
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_name                  long int

.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000b):

            NS new statement, BB new basic block, ET end of text sequence
            PE prologue end, EB epilogue begin
            IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x00001129  [   1,12] NS uri: "/home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp"
0x00001131  [   2,10] NS
0x00001139  [   3,10] NS
0x00001141  [   4,10] NS
0x00001150  [   5, 7] NS
0x00001158  [   6, 1] NS
0x0000115f  [   6, 1] NS ET

以下为一个DIE:包含一个tag(DW_TAG_compile_unit) 以及一些列 属性(名字中含有AT)

< 0><0x0000000b>  DW_TAG_compile_unit
                    DW_AT_producer              GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-***-protection -fcf-protection
                    DW_AT_language              DW_LANG_C_plus_plus
                    DW_AT_name                  test.cpp
                    DW_AT_comp_dir              /home/daligh/project/gbdSelf/minidbg-tut_setup
                    DW_AT_low_pc                0x00001129
                    DW_AT_high_pc               <offset-from-lowpc>54
                    DW_AT_stmt_list             0x00000000

< 0> 标记后的内容表示一个编译单元(Compile Unit),它是顶层的 DIE,然后接下来的 < 1>、< 2> 标记后的内容表示子级 DIE,描述了程序中的函数和变量等。

其中的DW_AT_type也会指向其他的DIE(以地址的形式)

上文中的源代码

int main() {
    long a = 3;
    long b = 2;
    long c = a + b;
    a = 4;
}

使用 -g 选项编译这个程序,并通过 dwarfdump 运行结果

符号级调试器需要两张非常大的表,一个是行号表Line Number Table,一个是调用栈信息表Call Frame Information Table。

ELF

ELF(可执行可链接文件):是一种在linux平台下的用于二进制文件可执行文件目标代码共享库核心转储格式文件的文件格式

类型:

  • 可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为 .o
  • 可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。
  • 共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以 .so 结尾。一般情况下,它有以下两种使用情景:
    • 链接器(Link eDitor, ld)可能会处理它和其它可重定位文件以及共享目标文件,生成另外一个目标文件。
    • 动态链接器(Dynamic Linker)将它与可执行文件以及其它共享目标组合在一起生成进程镜像。

文件格式:

ELF Header

记录了文件的全局信息,如下

使用readelf查看相关信息

eadelf -h minidbg-tut_setup/hello

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              DYN (共享目标文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x10a0
  程序头起点:          64 (bytes into file)
  Start of section headers:          31792 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         36
  Section header string table index: 35

Program Header Table:连接文件可选,可执行文件必须有

描述了进程内存和ELF的映射关系

 readelf -l minidbg-tut_setup/hello 

Elf 文件类型为 DYN (共享目标文件)
Entry point 0x10a0
There are 13 program headers, starting at offset 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000920 0x0000000000000920  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x00000000000002a5 0x00000000000002a5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x00000000000001b0 0x00000000000001b0  R      0x1000
  LOAD           0x0000000000002d88 0x0000000000003d88 0x0000000000003d88
                 0x0000000000000288 0x00000000000003d0  RW     0x1000
  DYNAMIC        0x0000000000002da0 0x0000000000003da0 0x0000000000003da0
                 0x0000000000000200 0x0000000000000200  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                 0x0000000000000054 0x0000000000000054  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002d88 0x0000000000003d88 0x0000000000003d88
                 0x0000000000000278 0x0000000000000278  R      0x1

 Section to Segment mapping:
  段节...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got

Section Header Table:区信息(可执行文件可选)

 readelf -S minidbg-tut_setup/hello
There are 36 section headers, starting at offset 0x7c30:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000318  00000318
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.propert NOTE             0000000000000338  00000338
       0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.build-i NOTE             0000000000000358  00000358
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c
       0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0
       000000000000005c  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           0000000000000400  00000400
       00000000000001f8  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           00000000000005f8  000005f8
       000000000000016b  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           0000000000000764  00000764
       000000000000002a  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          0000000000000790  00000790
       0000000000000040  0000000000000000   A       7     2     8
  [10] .rela.dyn         RELA             00000000000007d0  000007d0
       0000000000000108  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             00000000000008d8  000008d8
       0000000000000048  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020
       0000000000000040  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001060  00001060
       0000000000000010  0000000000000010  AX       0     0     16
  [15] .plt.sec          PROGBITS         0000000000001070  00001070
       0000000000000030  0000000000000010  AX       0     0     16
  [16] .text             PROGBITS         00000000000010a0  000010a0
       00000000000001f5  0000000000000000  AX       0     0     16
  [17] .fini             PROGBITS         0000000000001298  00001298
       000000000000000d  0000000000000000  AX       0     0     4
  [18] .rodata           PROGBITS         0000000000002000  00002000
       0000000000000011  0000000000000000   A       0     0     4
  [19] .eh_frame_hdr     PROGBITS         0000000000002014  00002014
       0000000000000054  0000000000000000   A       0     0     4
  [20] .eh_frame         PROGBITS         0000000000002068  00002068
       0000000000000148  0000000000000000   A       0     0     8
  [21] .init_array       INIT_ARRAY       0000000000003d88  00002d88
       0000000000000010  0000000000000008  WA       0     0     8
  [22] .fini_array       FINI_ARRAY       0000000000003d98  00002d98
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000003da0  00002da0
       0000000000000200  0000000000000010  WA       7     0     8
  [24] .got              PROGBITS         0000000000003fa0  00002fa0
       0000000000000060  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000004000  00003000
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000004040  00003010
       0000000000000118  0000000000000000  WA       0     0     64
  [27] .comment          PROGBITS         0000000000000000  00003010
       000000000000002b  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  0000303b
       0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  0000306b
       0000000000002421  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000548c
       00000000000005c1  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  00005a4d
       00000000000003ad  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  00005dfa
       0000000000001267  0000000000000001  MS       0     0     1
  [33] .symtab           SYMTAB           0000000000000000  00007068
       0000000000000750  0000000000000018          34    58     8
  [34] .strtab           STRTAB           0000000000000000  000077b8
       0000000000000319  0000000000000000           0     0     1
  [35] .shstrtab         STRTAB           0000000000000000  00007ad1
       000000000000015a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

功能实现

源码级断点(行)

流程:

  1. 获取函数对应的地址
    • 遍历编译单元,判断是否和输入的文件名一致
    • 获取行表遍历
    • 得到行对应的地址
  2. 为地址设置断点

实现对地址断点:将对应位置的低8位保存下来, 设置0xcc中断(INT3)

 void enable() {
            auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr);
            m_saved_data = static_cast<uint8_t>(data & 0xff); //save bottom byte
            uint64_t int3 = 0xcc;
            uint64_t data_with_int3 = ((data & ~0xff) | int3); //set bottom byte to 0xcc
            ptrace(PTRACE_POKEDATA, m_pid, m_addr, data_with_int3);
            m_enabled = true;
        }

函数断点

  1. 找到函数入口后,对应的位置并不对应函数的位置, 而是先进行初始化操作,例如:
    1. 将寄存器的值放入堆栈中
    2. 设置栈指针
    3. 分配局部变量的空间
    4. ...
  2. 可以在.debug_line部分可以找到对应的行号
  3. 此时记录下一行的地址, 即为函数地址

读取变量内容

例如读取a变量时 info信息:

< 2><0x0000004f>      DW_TAG_variable
                        DW_AT_name                  a
                        DW_AT_decl_file             0x00000001 /home/daligh/project/gbdSelf/minidbg-tut_setup/test.cpp
                        DW_AT_decl_line             0x00000002
                        DW_AT_decl_column           0x0000000a
                        DW_AT_type                  <0x0000007e>
                        DW_AT_location              len 0x0002: 9158: DW_OP_fbreg -40

位置基于栈底部偏移量-40, x86上的 reg6 是帧指针寄存器

还需要知道如何解释这段地址

DW_AT_type记录了变量类型:<0x0000007e> long int 像用户解释为'int64_t'

< 1><0x0000007e>    DW_TAG_base_type
                      DW_AT_name                  long int
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_byte_size             0x00000008

GCC的最新版本倾向于使用 DW_OP_call_frame_cfa ,这涉及到解析 .eh_frame ELF部分,目前没有资料

continue

考虑当前执行位置可能存在断点,应该先设置当前位置为无断点

void debugger::continue_execution() {
    // 关闭当前断点
    step_over_breakpoint();
    ptrace(PTRACE_CONT, m_pid, nullptr, nullptr);
    wait_for_signal();
}

跳出(step_out)

rbp寄存器中专门存储对应的基址

  1. 获取栈指针地址(rbp指针-8)
  2. 获取函数返回位置
  3. 在返回位置设置断点(判断该位置是否已经有断点)
void debugger::step_out() {
    auto frame_pointer = get_register_value(m_pid, reg::rbp);
    auto return_address = read_memory(frame_pointer+8);

    bool should_remove_breakpoint = false;
    if (!m_breakpoints.count(return_address)) {
        set_breakpoint_at_address(return_address);
        should_remove_breakpoint = true;
    }

    continue_execution();

    if (should_remove_breakpoint) {
        remove_breakpoint(return_address);
    }
}

step_in

  1. 获取当前指令行
  2. 循环判断是否仍然在原来的指令行
  3. 仍然在同一行进行指令集步进
  4. 打印当前行(此时行已经发生改变)
    • 获取当前偏移地址
    • 根据偏移地址获取当前行
    • 打印源码
void debugger::step_in() {
   auto line = get_line_entry_from_pc(get_offset_pc())->line;

   while (get_line_entry_from_pc(get_offset_pc())->line == line) {
      single_step_instruction_with_breakpoint_check();
   }
   auto line_entry = get_line_entry_from_pc(get_offset_pc());
   print_source(line_entry->file->path, line_entry->line);
}

uint64_t debugger::get_offset_pc() {
   return offset_load_address(get_pc());
}

实现源代码级step_over

不进入函数而是一直执行到该源代码的下一行:主要利用在调用子程序时会将下一步当前函数指令地址入栈,在入栈地址下断点即可

例如:call 子程序

section .data
    ; 定义两个整数
    num1 dd 10
    num2 dd 20

section .text
    global _start

_start:
    ; 将参数传递给函数
    mov eax, [num1]     ; 将第一个整数加载到 eax 寄存器中
    mov ebx, [num2]     ; 将第二个整数加载到 ebx 寄存器中
    call add_numbers    ; 调用 add_numbers 函数

    ; 返回值存储在 eax 寄存器中,将其传递给系统调用,以便在终端中打印
    mov ebx, eax        ; 将返回值移动到 ebx 寄存器中,用于系统调用
    mov eax, 1          ; 系统调用号 1 表示输出
    int 0x80            ; 调用系统调用

    ; 退出程序
    mov eax, 1          ; 系统调用号 1 表示退出程序
    xor ebx, ebx        ; 返回值为 0
    int 0x80            ; 调用系统调用

; 定义一个函数,将两个整数相加并返回结果
section .text
add_numbers:
    mov eax, [ebp+8]   ; 将第一个参数加载到 eax 寄存器中
    mov ebx, [ebp+12]  ; 将第二个参数加载到 ebx 寄存器中
    add eax, ebx       ; 执行加法操作,结果存储在 eax 寄存器中
    ret                ; 返回结果

子程序使用ret回到原来的函数中

ret执行的操作即从栈中获取对应的指令地址,移动栈指针

在x86系统中rbp寄存器专门存储了函数的基址

具体实现需要考虑到,当前指令可能并不是函数,

函数在循环中,如何判断进入了下一行

为函数每一行设置断点,执行后,关闭所有断点

同时存在函数作为当前函数的最后代码,应该返回上一层, 在栈指针+8处设置断点

解决步进先进入链接文件(待解决)

开始调试时先跳过链接文件 进入目标文件指令范围

  1. 回看段地址:()见pc计数器出现问题:(no)章节maps文件
  2. 确认第一行代码开始的位置.debug_line信息
.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000b):

            NS new statement, BB new basic block, ET end of text sequence
            PE prologue end, EB epilogue begin
            IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x00001189  [   3,13] NS uri: "/home/daligh/project/gbdSelf/minidbg-tut_setup/examples/hello.cpp"
0x00001191  [   4,18] NS
0x000011aa  [   5, 1] NS
0x000011b1  [   5, 1] NS
0x000011c3  [   5, 1] NS
0x000011c9  [   5, 1] DI=0x1
0x000011d2  [  74,25] NS uri: "/usr/include/c++/11/iostream"
0x00001204  [   5, 1] NS uri: "/home/daligh/project/gbdSelf/minidbg-tut_setup/examples/hello.cpp"
0x00001207  [   5, 1] NS
0x0000120f  [   5, 1] NS
0x00001220  [   5, 1] NS ET
  1. 源代码
#include <iostream>

int main () {
    std::cout << "Hello world";
}

5行代码这么出现了这么多

对5 6 7 8 9的调试

  1. 找到main的入口:设置断点(方法见基础调试)
  2. 输入s/n进行调试

对函数设置断点

在 DWARF信息中,包含 DW_AT_name信息

< 0><0x0000000b>  DW_TAG_compile_unit
                    DW_AT_producer              clang version 3.9.1 (tags/RELEASE_391/final)
                    DW_AT_language              DW_LANG_C_plus_plus
                    DW_AT_name                  /path/to/variable.cpp
                    DW_AT_stmt_list             0x00000000
                    DW_AT_comp_dir              /path/to/
                    DW_AT_low_pc                0x00400670
                    DW_AT_high_pc               0x0040069c

LOCAL_SYMBOLS:
< 1><0x0000002e>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00400670
                      DW_AT_high_pc               0x0040069c
                      DW_AT_name                  foo
                      ...
...
<14><0x000000b0>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00400700
                      DW_AT_high_pc               0x004007a0
                      DW_AT_name                  bar
                      ...

遍历所有编译单元,查找是否有符合的函数名

获取地址在源代码中的函数,为下一行设置断点

void debugger::set_breakpoint_at_function(const std::string& name) {
    for (const auto& cu : m_dwarf.compilation_units()) {
        for (const auto& die : cu.root()) {
            if (die.has(dwarf::DW_AT::name) && at_name(die) == name) {
                auto low_pc = at_low_pc(die);
                auto entry = get_line_entry_from_pc(low_pc);
                ++entry; //skip prologue
                set_breakpoint_at_address(offset_dwarf_address(entry->address));
            }
        }
    }
}

为行号设置断点

.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000b):

NS new statement, BB new basic block, ET end of text sequence
PE prologue end, EB epilogue begin
IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x004004a7  [   1, 0] NS uri: "/path/to/a.hpp"
0x004004ab  [   2, 0] NS
0x004004b2  [   3, 0] NS
0x004004b9  [   4, 0] NS
0x004004c1  [   5, 0] NS
0x004004c3  [   1, 0] NS uri: "/path/to/b.hpp"
0x004004c7  [   2, 0] NS
0x004004ce  [   3, 0] NS
0x004004d5  [   4, 0] NS
0x004004dd  [   5, 0] NS
0x004004df  [   4, 0] NS uri: "/path/to/ab.cpp"
0x004004e3  [   5, 0] NS
0x004004e8  [   6, 0] NS
0x004004ed  [   7, 0] NS
0x004004f4  [   7, 0] NS ET

例如在底行设置断点,需要在段基址+0x004004b9位置下断点

逻辑:

  1. 遍历所有编译单元
  2. 判断输入和编译单元是否匹配
  3. 获取该编译单元的行
  4. 判断该行是否适合作为断点&&==要求的行
  5. 设置断点

代码实现:

void debugger::set_breakpoint_at_source_line(const std::string& file, unsigned line) {
    for (const auto& cu : m_dwarf.compilation_units()) {
        if (is_suffix(file, at_name(cu.root()))) {
            const auto& lt = cu.get_line_table();

            for (const auto& entry : lt) {
                if (entry.is_stmt && entry.line == line) {
                    set_breakpoint_at_address(offset_dwarf_address(entry.address));
                    return;
                }
            }
        }
    }
}

My is_suffix hack is there so you can type c.cpp for a/b/c.cpp. Of course you should actually use a sensible path handling library or something; I’m lazy. The entry.is_stmt is checking that the line table entry is marked as the beginning of a statement, which is set by the compiler on the address it thinks is the best target for a breakpoint.

读取变量

目前已知elf文件中记录了相关符号信息

延申

在gdb中如何进行多线程调试

posted @ 2024-11-19 11:39  daligh  阅读(48)  评论(0)    收藏  举报