GDB
GDB 能够允许查看程序执行时的内部情况,或者在程序出现崩溃的瞬间正在做什么。
GDB 可以做四种主要的功能来帮助捕获错误:
- 启动程序,指定可能影响其行为的变量或条件
- 使程序在指定条件或者位置停止
- 检查程序停止时发生了什么
- 更改程序中的内容,以便可以尝试更正当前已了解到的错误,然后继续调试程序,了解下一个错误
使用 GDB 的条件
想要使用 GDB 进行调试程序,需要在编译程序时加入 -g 选项,让编译的程序中保留 调试符号信息。此外,最好在编译时同时加上 -O0,-O
能够设置编译器对我们程序的优化选项,分别由 0 - 4 五个级别,而 -O0
为让编译器不对程序做出优化,已能够准确定位错误点。
启动 GDB
启动 GDB 调试程序主要有以下三种方式
gdb filename
: 直接调试目标程序gdb attach pid
: 附加进程gdb filename corename
: 调试 core 文件
退出GDB
退出 GDB 调试命令行界面比较简单,输入 quit
或简写 q
,以及 ctrl + d
均可退出。
接下来依次介绍这三种启动方式。现在先准备一个测试程序:
// HelloGdb.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello GDB\n");
int i = 0;
while(1) {
i++;
if (i == 100) {
i = 0;
}
}
return 0;
}
然后进行编译:
gcc -g -O0 HelloGdb.c -o HelloGdb
直接启动
gdb ./HelloGdb
附加进程
如果是在进程已经存在情况下,我们想在不重启该程序的情况下对其进行调试,则可以使用附加进程调试的方式。
获取进程PID
使用ps
命令获取 HelloGdb 进程的 PID:
ps -ef | grep HelloGdb
# 输出如下
# h47 5130 3602 0 15:35 pts/0 00:00:00 ./HelloGdb
# h47 5249 5146 0 15:36 pts/1 00:00:00 grep --color=auto HelloGdb
可见,HelloGdb 的进程 PID 为 5130
gdb附加进程PID
接下来使用 GDB 附加进程,需要以 root 权限执行。
sudo gdb attach 5130
当用 sudo gdb attach PID
成功附加上目标进程后,调试器会将程序暂停下来,此时可以进行查看代码、添加断点等 GDB 操作,然后可以使用 continue
让程序继续运行。
当调试完程序想结束此次调试时,想单独退出GDB而不对当前进程产生影响,让其可以继续运行,可以在 GDB 命令行输入 detach
命令实现,这样进程就可以继续运行了。再使用 quit
退出GDB即可。
调试 core 文件
在实际工作中,常能见到程序在跑机测试中,运行一段时间后会偶现崩溃,这种崩溃难以及时追踪,于是可以调试 core 文件的方式追踪问题。
只要程序在崩溃时就会有 core 文件产生,就可以使用这个 core 文件来定位原因。
可以使用 ulimit -c
命令查看是否开启程序崩溃产生 core 文件的机制(Linux 系统默认不开启)。
ulimit -c
# 返回
# 0
执行以下步骤修改设置可生成 corefile :
# 打开 /etc/profile 文件
sudo vi /etc/profile
# 在该文件末新增下面语句,并保存退出
ulimit -c unlimited
# 使修改立即生效
source /etc/profile
# 查看修改是否生效
ulimit -c
执行以下步骤,指定生成 corefile 的路径:
- 在
/etc/sysctl.conf
写入 corefile 文件生成的目录, 并创建该目录。
kernel.core_pattern=/home/h47/core_dump/core-%e-%p-%t
mkdir /home/h47/core_dump
/home/h47/core_dump/ 对应指定存放的路径
core-%e-%p-%t 对应文件格式
格式控制参数表如下:
参数 | 说明 |
---|---|
%% | 相当于% |
%p | pid 值 |
%u | uid 值 |
%g | gid 值 |
%s | 导致dump的信号的数字 |
%t | dump的时间 |
%e | 执行文件名称 |
%h | hostname |
- 执行生效
sudo sysctl -p /etc/sysctl.conf
- 查看是否生效
cat /proc/sys/kernel/core_pattern
不指定生成路径,则会在执行文件的同级目录下生成 corefile
修改测试范例
我们还需要修改一下测试范例,让其在运行时崩溃:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("Hello GDB\n");
int i = 0;
while(1) {
i++;
// 让程序5s后崩溃
if (i == 5) {
printf("G\n");
*(char *)0 = 0;
i = 0;
}
sleep(1);
}
return 0;
}
执行程序崩溃后,能在设置的路径看到生成了 corefile,接下来使用以下命令进行调试:
gdb ./HelloGdb /home/h47/core_dump/core-HelloGdb-2851-1710927155
./HelloGdb 是可执行文件名称
/home/h47/core_dump/xxxxx 是刚刚生成的 corefile 的绝对路径
接着输入 bt
查看堆栈。
常用命令
命令名称 | 命令缩写 | 命令说明 |
---|---|---|
run | r | 运行程序 |
continue | c | 让暂停的程序继续运行 |
next | n | 单步跳过,运行到下一行 |
step | s | 但不进入,进入调用的函数内部 |
until | u | 运行到指定行 |
finish | fi | 单步跳出,跳出当前函数,到上一层函数调用处 |
return | 跳出当前函数并返回指定值,到上一层函数调用处 | |
jump | j | 将当前程序执行流跳转到指定行或地址 |
p | 打印变量或寄存器值 | |
backtrace | bt | 查看当前线程的调用堆栈 |
frame | f | 切换到当前调用线程的指定堆栈,具体堆栈通过堆栈序号指定 |
thread | thread | 切换到指定线程 |
break | b | 添加断点 |
tbreak | tb | 添加临时断点 |
delete | del | 删除断点 |
enable | 启用某个断点 | |
disable | 禁用某个断点 | |
watch | 监视某一个变量或内存地址的值是否发生变化 | |
list | l | 显示源码 |
info | 查看断点/线程等信息 | |
ptype | 查看变量类型 | |
disassemble | dis | 查看汇编代码 |
set args | 设置程序启动命令行参数 | |
show args | 查看设置的命令行参数 |