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 (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 128312
max locked memory (kbytes, -l) 4110092
max memory size (kbytes, -m) unlimited
open files (-n) 4194304
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 90000
virtual memory (kbytes, -v) unlimited
file locks (-x) 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
set backtrace past-entry on
#0  main () at hello.c:4
#1 0x00007ffff7dd01ca in __libc_start_call_main (main=main@entry=0x555555555149 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffd998) at ../sysdeps/nptl/libc_start_call_main.h:58
#2 0x00007ffff7dd028b in __libc_start_main_impl (main=0x555555555149 <main>, argc=1, argv=0x7fffffffd998, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd988) at ../csu/libc-start.c:360
#3 0x0000555555555085 in _start ()

执行相关

  • run 运行程序,简写r
  • step 单步执行(会进入函数),简写s
  • continue 继续执行,简写c
  • next 单步执行(不会进入函数), 简写n

断点

  • break, 简写为b

查看变量参数

  • print xxx 打印变量的值, 简写为p
  • info locals 查看本地变量
  • info args 查看函数的参数

查看源码

使用 list 命令查看源码

要查看源码首先也要编译的时候带上-g选项,此选项会加上调试信息

源码不是存储在可执行文件里面的,如果要查看源码,且编译的位置跟运行的位置不一样,大概率找不到源码的

如果源代码路径发生了变化:

  • 如果编译的时候使用的是相对路径,你可以使用 GDB 的 directory 命令来添加新的源代码目录。例如:

# 比如编译是使用的相对路径
# gcc -g -o build/my_program src/my_program.c src/other_file.c

# gdb中使用这样的方式指定新的代码base路径
directory 新的代码路径1 新的代码路径2
  • 如果编译的时候使用绝对路径,可以使用**set substitute-path 命令替换源代码路径 ,如下边代码,编译的时候是从**/build/project/src,如果在新机器上代码下载到了/home/user/project/src,就执行下边命令可以找到代码了
# 编译的时候使用的绝对路径
gcc -g -o /home/user/project/build/my_program /home/user/project/src/my_program.c /home/user/project/src/other_file.c
# gdb中使用这样的方式指定替换规则
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 文件中,调试信息主要存在于以下几个段中:

  1. .debug_info: 包含了多数调试信息,包括类型定义、变量、函数、行号等。是最主要的调试信息段。
  2. .debug_abbrev: 包含 .debug_info 段中使用的缩写表。这些是描述调试信息格式的表格,帮助调试器解析 .debug_info 中的信息。
  3. .debug_line: 包含源码文件的行号信息。这一段用于将可执行代码中的地址映射到源代码中的行号。
  4. .debug_frame: 包含栈帧信息,用于在调试期间回溯调用栈。
  5. .debug_loc: 包含位置信息,描述了变量在程序执行期的内存位置。
  6. .debug_ranges: 包含代码范围信息,用于描述某些变量仅在特定代码范围内有效。
  7. .debug_str: 包含调试信息中使用的字符串表。

这些段都是 DWARF(Debugging With Attributed Record Formats)调试信息格式的组成部分,DWARF 是一种用来存储调试信息的标准格式。

可以使用下列命令查询elf文件中具体段信息

objdump --dwarf=info myprogram

去除调试信息

在某些情况下,二进制会分发到客户端,这个时候不希望暴露出去调试信息(防止对方破解啥的)

需要去除调试信息

strip 工具可以直接从二进制文件中去除调试信息。以下是一些常用的选项:

  1. 去掉所有符号信息

    strip <binary-file>

    这种方法会去除所有符号信息,包括调试信息和符号表。

  2. 去掉调试信息但保留符号表

    strip --strip-debug <binary-file>

    这种方法只去除调试信息,但保留符号表,便于后续的分析和调试。

  3. 去掉局部符号

    strip --strip-unneeded <binary-file>

    这种方法去掉所有不必要的符号,包括局部符号,但保留必要的符号。

也可以通过objcopy分离调试信息,这样等需要调试的时候,可以快速找到调试信息,在自己的环境中测试

GNU工具链中的objcopy工具可以用于分离调试信息。以下是一个示例:

  1. 编译程序并生成带有调试信息的可执行文件

    gcc -g -o myprogram myprogram.c

  2. 使用 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中使用调试

调试示例配置

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/hello",
"args": [],
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerArgs": "-q -ex quit; wait() { fg >/dev/null; }; /bin/gdb -q --interpreter=mi",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},

{
"description": "Set GDB search directory",
"text": "directory /usr/src/glibc/glibc-2.39", // 设置glibc的搜素目录
"ignoreFailures": true
}
],
"sourceFileMap":{
}
}
]
}

执行gdb命令

vscode中console执行gdb命令,要加前缀-exec

**-exec p aaa # 打印aaa变量的值**

参考文档

高效c/c++调试

gdb调试工具的基本原理和使用

https://blogs.92ac.cn/2024/11/08/c++/gdb/

作者

deepwzh

发布于

2024-11-08

更新于

2025-01-16

许可协议

评论