gdb调试工具的基本原理和使用
基础概念
GDB(GNU Debugger)是GNU项目下的一个强大的调试工具,用于调试各种程序,特别是那些用C、C++和其他编程语言(如go)编写的程序。
DB常用于以下几种场景:
- 调试崩溃:当程序崩溃时,可以使用GDB来检查崩溃点,获取调用栈信息,找出导致崩溃的原因。这个时候就需要事先生成了一个coredump文件
- 调试逻辑错误:通过设置断点和单步执行,可以逐步检查程序的逻辑,找出逻辑错误。
- 性能调优:通过分析程序的执行路径和时间消耗,可以进行性能调优。
ELF: Linux可执行文件的格式,ELF中包含多个段,有一些是gdb调试所需要的,比如.debug_*,里面存了行号之类的
符号表: 存储每个变量或者函数的名字,不然gdb中看到的都是一堆地址,不容易读
原理
ptrace是用于实现调试功能的
当一个进程在调试的时候,调试进程会成为他的父进程,通过ptrace去控制调试过程
断点的原理
调试器在增加断点的时候,会把断点的指令替换成int 3中断,原本的指令暂存到其他地方
这个具体是由ptrace系统调用去实现的
调试coredump
coredump核心转储,是进程崩溃的时候,会将进程的状态保存在一个文件中。
开启coredump
查看coredump相关的配置
ulimit -a |
real-time non-blocking time (microseconds, -R) unlimited |
如果看到core file size为0,则表示不生成coredump; 需要将其设置为一个非0值
ulimit -c unlimited |
比如这样表示生成coredump,并且不限制生成文件的大小
配置coredump生成路径
开启coredump后,可以通过cat /proc/sys/kernel/core_pattern
查看coredump生成的默认路径
如果需要修改,可以直接执行
sudo sysctl -w kernel.core_pattern=/要指定的路径/core.%e.%p.%h.%t |
在这个模式中:
%e
:可执行文件名%p
:进程 ID%h
:主机名%t
:时间戳
上边命令重启后会失效,如果需要持久化他,需要写入/etc/sysctl.conf:
echo "kernel.core_pattern=/var/要指定的路径/core.%e.%p.%h.%t" | sudo tee -a /etc/sysctl.conf
当core_pattern以|开头的时候,表示coredump不直接生成到文件中,而是可以通过管道传给其他程序,感兴趣的读者可以自行搜索这块内容
调试coredump
如果拥有源程序,可以直接
gdb 程序路径 coredump路径 |
此时如果程序中包含符号表,就可以直接查看程序中的函数名等信息
常用命令
gdb 启动程序
gdb 程序路径 |
gdb attach 到一个进程
gdb -p 1234 |
堆栈相关
- backtrace: 打印堆栈,简写为bt
- frame xxx 切换堆栈, 简写为f
一般gdb调试的时候,bt可能是看不到glibc的堆栈的,如果需要展示他的堆栈,如果你无法看到glibc库的堆栈帧,可以尝试使用以下命令来确保GDB不会省略任何堆栈帧:
set backtrace past-main on |
#0 main () at hello.c:4 |
执行相关
- run 运行程序,简写r
- step 单步执行(会进入函数),简写s
- continue 继续执行,简写c
- next 单步执行(不会进入函数), 简写n
断点
- break, 简写为b
查看变量参数
- print xxx 打印变量的值, 简写为p
- info locals 查看本地变量
- info args 查看函数的参数
查看源码
使用 list
命令查看源码
要查看源码首先也要编译的时候带上-g选项,此选项会加上调试信息
源码不是存储在可执行文件里面的,如果要查看源码,且编译的位置跟运行的位置不一样,大概率找不到源码的
如果源代码路径发生了变化:
- 如果编译的时候使用的是相对路径,你可以使用 GDB 的
directory
命令来添加新的源代码目录。例如:
|
- 如果编译的时候使用绝对路径,可以使用**
set substitute-path
命令替换源代码路径 ,如下边代码,编译的时候是从**/build/project/src,如果在新机器上代码下载到了/home/user/project/src,就执行下边命令可以找到代码了
# 编译的时候使用的绝对路径 |
如果需要查看glibc的代码,可以下载到 /usr/src/glibc/glibc-2.39后,
directory /usr/src/glibc/glibc-2.39
指定路径就可以看到了
调试信息
在 Linux 平台上,使用 g++ -g
生成调试信息后,这些信息会被嵌入到可执行文件(或共享库)中。可执行文件通常是 ELF(Executable and Linkable Format)格式。在 ELF 文件中,调试信息主要存在于以下几个段中:
- .debug_info: 包含了多数调试信息,包括类型定义、变量、函数、行号等。是最主要的调试信息段。
- .debug_abbrev: 包含
.debug_info
段中使用的缩写表。这些是描述调试信息格式的表格,帮助调试器解析.debug_info
中的信息。 - .debug_line: 包含源码文件的行号信息。这一段用于将可执行代码中的地址映射到源代码中的行号。
- .debug_frame: 包含栈帧信息,用于在调试期间回溯调用栈。
- .debug_loc: 包含位置信息,描述了变量在程序执行期的内存位置。
- .debug_ranges: 包含代码范围信息,用于描述某些变量仅在特定代码范围内有效。
- .debug_str: 包含调试信息中使用的字符串表。
这些段都是 DWARF
(Debugging With Attributed Record Formats)调试信息格式的组成部分,DWARF 是一种用来存储调试信息的标准格式。
可以使用下列命令查询elf文件中具体段信息
objdump --dwarf=info myprogram |
去除调试信息
在某些情况下,二进制会分发到客户端,这个时候不希望暴露出去调试信息(防止对方破解啥的)
需要去除调试信息
strip
工具可以直接从二进制文件中去除调试信息。以下是一些常用的选项:
去掉所有符号信息:
strip <binary-file>
这种方法会去除所有符号信息,包括调试信息和符号表。
去掉调试信息但保留符号表:
strip --strip-debug <binary-file>
这种方法只去除调试信息,但保留符号表,便于后续的分析和调试。
去掉局部符号:
strip --strip-unneeded <binary-file>
这种方法去掉所有不必要的符号,包括局部符号,但保留必要的符号。
也可以通过objcopy分离调试信息,这样等需要调试的时候,可以快速找到调试信息,在自己的环境中测试
GNU工具链中的objcopy
工具可以用于分离调试信息。以下是一个示例:
编译程序并生成带有调试信息的可执行文件:
gcc -g -o myprogram myprogram.c
使用
objcopy
分离调试信息:objcopy --only-keep-debug myprogram myprogram.debug
objcopy --strip-debug myprogram
objcopy --add-gnu-debuglink=myprogram.debug myprogram-only-keep-debug
:将调试信息提取到一个单独的文件中(myprogram.debug
)。-strip-debug
:从可执行文件中去掉调试信息。-add-gnu-debuglink
:在可执行文件中添加一个指向调试信息文件的链接。gdb myprogra
在GDB中加载调试信息文件:
symbol-file myprogram.debug |
vscode中使用调试
调试示例配置
{ |
执行gdb命令
vscode中console执行gdb命令,要加前缀-exec
如
**-exec p aaa # 打印aaa变量的值** |
参考文档
gdb调试工具的基本原理和使用