GDB调试学习

GDB是命令行下调试C++程序的好工具,在Linux上跑程序就不可避免地与它打交道。

GDB调试的一般流程

程序编译时带上-g选项

1
g++ test.cpp -o test -g

启动gdb对可执行程序进行调试

启动gdb来调试程序有两种思路:

  • 利用gdb启动程序
  • 利用gdb调试运行中的程序

方式一:利用gdb启动程序

1
gdb ./file_name

或者先运行gdb命令,进入gdb调试程序内,然后利用file命令启动程序

1
file ./file_name

方式二:利用gdb调试运行中的程序

1
gdb -p 进程号 

或者先运行gdb命令,进入gdb调试程序内,然后利用attach命令依附到进程

1
attach 进程号

设置断点等单步调试

  • start 程序运行至主函数main入口处暂停(相当于在main函数打了一个断点)

  • run 程序开始运行直到遇到一个断点,如果没有断点则一直运行至结束

  • break/b + 文件路:行数 在文件的第x行打断点

  • next/n 执行单行代码

    • n 10 # 执行10行代码
  • [enter] 回车键执行上次运行的命令

  • continue/c 运行到下一个断点

  • list /l 显示当前附近的代码

    • list file:line 查看指定文件行数附近的代码
  • print/p 变量 打印变量的值

  • backtrace/bt 查看当前的调用栈以及当前的文件和行

  • finish 执行完当前的函数

  • until 执行完当前的循环

好用的进阶知识点

gdb启动程序时传参数

很多时候,我们的程序启动需要带上命令行参数,此时可利用–args选项带上参数

1
gdb --args ./program arg1 arg2

打印相关

(1)格式控制

print可以指定显示的格式,支持的变量显示格式有:

  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。

比如:用16进制显示(var)值: (gdb) print /x var

(2)地址/解引用

  • 打印变量的地址 (print &var)

  • 打印地址的数据值 (print *address)

  • 打印数组 print *a@10 显示10个元素,无论a是double或者是int的都会正确地显示10个元素。

(3)GDB打印要点

  • 在对地址做强制转换时,括号全都加:(类型)(地址)
  • 后续想要对转换后的变量操作时,再加一层大括号 ,以视为整体操作:((类型)(地址))-> 成员

宏相关

  • info macro+宏名 查看宏的定义
  • macro expand+宏 可以展开宏的表达情况

其他

  • watchpoint 监听一个变量,改变时通知
  • info line *<地址> 获取某个地址对应的源代码行号

调试崩溃的程序

程序崩溃时会由操作系统生成core dump文件,里面包含了程序崩溃时的内存内容、寄存器状态和其他相关信息,借此我们可以调试分析程序崩溃的原因。

core文件设置

(1)默认情况下,core文件的大小可能被设置为0,这意味着系统不会为崩溃的进程生成core文件。

1
2
ulimit -a # 查看core文件的大小设置
ulimit -c unlimited #设置core大小无限

以上针对的是当前shell会话,如果要永久开启core文件的生成,需要编辑/etc/security/limits.conf文件(针对使用PAM的Linux系统)。在该文件中,为需要生成core文件的用户添加或修改一行配置,如* soft core unlimited,这里的*代表所有用户,也可以指定具体用户名。

(2)设置core文件的路径和名称

1
2
# 将core文件保存到/corefile目录下,并以core-命令名-进程号-时间戳的格式命名。
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

ore文件的生成路径和名称可以通过修改/proc/sys/kernel/core_pattern文件来设置。这个文件包含了一个字符串,用于指定core文件的命名格式和存储位置。

直接修改/proc/sys/kernel/core_pattern文件的效果是临时的,重启后会失效。为了永久更改,可以在系统启动时通过sysctl配置或使用其他系统管理工具进行设置。

core加载调试

使用命令 gdb [exec file] [core file] 加载core 文件进行调试。

使用bt(backtrace)命令查看当前的调用栈。这将列出从程序入口到崩溃点的所有函数调用序列。

1
2
3
4
(gdb) bt
#0 function1 (arg1=1, arg2=2) at file.c:10
#1 function2 (arg=3) at file.c:20
#2 0x00000000004006b4 in main () at main.c:5

使用frame命令加上栈帧编号来切换到该栈帧。

1
2
(gdb) frame 1
#1 0x000055555555465a in function2 (arg=3) at file.c:20

在切换到特定栈帧后,你可以使用info locals命令来查看该栈帧中的所有局部变量。此外,info args命令可以查看当前函数的参数。

1
2
3
4
5
(gdb) info locals
arg = 3
local_var = 13
(gdb) info args
arg = 3

多线程调试

gdb还可以调试多线程的程序

  • 查看可切换调试的线程:info threads

  • 切换调试的线程:thread 线程id

  • 只运行当前线程:set scheduler-locking on

  • 指定某线程执行某gdb命令:thread apply 线程id gdb_cmd

  • 运行全部的线程:set scheduler-locking off

  • 全部的线程执行某gdb命令:thread apply all gdb_cmd

调试死锁的程序

有些时候我们的程序会死锁,这时候该怎么调试程序呢?答案是保存现场,然后慢慢进行调试。

  1. gdb attach pid # 依附到hang的进程上

  2. set logging on # 将gdb的会话输出保存

  3. thread apply all bt # 打印所有线程的堆栈

  4. generate-core-file # 输出core文件 或者 gcore 进程号

  5. 保存程序产生的日志

作者

Desirer

发布于

2024-08-08

更新于

2024-08-25

许可协议