一个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)
功能实现
源码级断点(行)
流程:
- 获取函数对应的地址
- 遍历编译单元,判断是否和输入的文件名一致
- 获取行表遍历
- 得到行对应的地址
- 为地址设置断点
实现对地址断点:将对应位置的低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;
}
函数断点
- 找到函数入口后,对应的位置并不对应函数的位置, 而是先进行初始化操作,例如:
- 将寄存器的值放入堆栈中
- 设置栈指针
- 分配局部变量的空间
- ...
- 可以在
.debug_line部分可以找到对应的行号 - 此时记录下一行的地址, 即为函数地址
读取变量内容
例如读取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寄存器中专门存储对应的基址
- 获取栈指针地址(rbp指针-8)
- 获取函数返回位置
- 在返回位置设置断点(判断该位置是否已经有断点)
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
- 获取当前指令行
- 循环判断是否仍然在原来的指令行
- 仍然在同一行进行指令集步进
- 打印当前行(此时行已经发生改变)
- 获取当前偏移地址
- 根据偏移地址获取当前行
- 打印源码
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处设置断点
解决步进先进入链接文件(待解决)
开始调试时先跳过链接文件 进入目标文件指令范围
- 回看段地址:()见
pc计数器出现问题:(no)章节maps文件 - 确认第一行代码开始的位置.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
- 源代码
#include <iostream>
int main () {
std::cout << "Hello world";
}
5行代码这么出现了这么多
对5 6 7 8 9的调试
- 找到main的入口:设置断点(方法见基础调试)
- 输入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位置下断点
逻辑:
- 遍历所有编译单元
- 判断输入和编译单元是否匹配
- 获取该编译单元的行
- 判断该行是否适合作为断点&&==要求的行
- 设置断点
代码实现:
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中如何进行多线程调试

浙公网安备 33010602011771号