Gdb基础教程

常用命令

命令 全称 解释
l list 查看源码
b break 设置断点
r run 运行程序,在断点处停止
n next 单条语句执行(步过)
s step 单条语句执行(步入)
c continue 继续运行程序,下一个断点处停止
f finish 继续运行直到当前函数执行完成
p print 打印
q quit 退出GDB

调试段错误文件

先保证能生成 coredump 文件

转存文件可以是某个进程的,也可以是整个系统的。

可以是进程活着的时候生成的,也可以是进程或者系统崩溃的时候自动生成的。

但明显段错误文件是要进程或者系统崩溃的时候自动生成,因为段错误必然不可能让进程活着。

1
2
3
ulimit -c unlimited

sudo bash -c "echo core > /proc/sys/kernel/core_pattern "

调试基本步骤

1
2
3
gdb 可执行程序 coredump文件

bt

通过 bt 就可以看出发生段错误的调用堆栈,调用顺序从下往上看,一般去找自己实现的代码进行错误排查。

调试死锁文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutexA;
std::mutex mutexB;

void task1() {
std::lock_guard<std::mutex> lockA(mutexA);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
std::cout << "Task 1 locked mutexA" << std::endl;

// 尝试获取 mutexB,导致等待
std::lock_guard<std::mutex> lockB(mutexB);
std::cout << "Task 1 locked mutexB" << std::endl;
}

void task2() {
std::lock_guard<std::mutex> lockB(mutexB);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
std::cout << "Task 2 locked mutexB" << std::endl;

// 尝试获取 mutexA,导致等待
std::lock_guard<std::mutex> lockA(mutexA);
std::cout << "Task 2 locked mutexA" << std::endl;
}

int main() {
std::thread t1(task1);
std::thread t2(task2);

t1.join();
t2.join();

std::cout << "Finished without deadlock (unlikely)" << std::endl;
return 0;
}

死锁文件是正在运行的,要通过 gcore 命令生成 coredump 文件。

1
2
3
4
5
gcore <pid> # 生成死锁应用的coredump

info threads # 显示所有线程信息

t <n> # 切换当前显示线程

查看所有线程的堆栈,找出处于 lock wait 的线程:

死锁1.png

可以看到,线程 2 和 线程 3 是 处于 lock wait 状态,表明它们正在等待锁。当多个线程在 __lll_lock_wait 处停住时,很可能是因为它们在等待某个锁资源,出现了互相等待的情况。

切换到 这两个线程,打印我们在代码中定义的 mutex 变量(mutexA 和 mutexB)。其中 owner 属性代表的就是当前锁占有者的线程 ID(注意在 gdb 中为LWP)。

死锁2.png

thread 2 的 LWP = 260468;thread 3 的 LWP = 260469。

mutexA 的 owner = 260468,表明被 thread 2 占有;mutexB 的 owner = 260469,表明被 thread 3 占有;

结合前面的 __lll_lock_wait 信息,我们知道 thread 3 在等待 mutexA 资源;thread 2 在等待 mutex(没有明确是 mutexB) 资源,但我们从 __lll_lock_wait 信息中可以看到这个锁资源的地址为 0x55a71b4011a0,打印 mutexB 的地址,可以知晓就是它。

死锁3.png

进入两个线程从,并分别查看堆栈情况,依旧是找我们自己的代码,并自行分析死锁原因。

查看 thread 2 的堆栈情况:

死锁4.png

查看 thread 3 的堆栈情况:

死锁5.png

然后到相应的地方自己去分析,通常我们解决死锁的办法是按顺序获取锁资源。

远程调试

被调试的目标机器需要安装 gdbserver:apt-get install gdbserver

(一)目标机程序没有启动

1
2
3
目标机:gdbserver 192.168.204.154:9999 ./test

调试机:target remote 192.168.204.154:9999

目标机上运行者需要被调试的程序,调试机是主动远程去调试目标机的机器。

目标机的命令在 shell 命令行执行,调试机命令在进入 gdb 之后再执行。

(二)目标机程序已经启动

1
2
3
目标机:gdbserver 192.168.204.154:9999 --attach pid

调试机:target remote 192.168.204.154:9999

如果目标机的程序已经启动,则无法使用直接启动的方式来进行远程调试。此时需要采用附加到远程进程的方式来进行远程调试。

小技巧

查看当前函数的参数

1
info args

在没有进入函数内部之前,函数的参数肯定是没有实际意义的数据。

step 进入函数之后,执行上面的命令就有效果了。

查看/修改变量的值

1
2
3
print 变量名		查看

print 变量名=值 修改 结构体也可以修改,例如p test->x=30

自动显示变量的值

print 需要每次都输入,如果你非常关心某个变量值的变化,display 可以帮大忙。

1
2
3
4
5
6
7
display 变量名			自动显示变量名的值

info display 查看已经设置的自动显示的变量信息

delete display 序号 取消指定变量的序号的自动显示

undisplay 取消所有的自动显示

调试正在运行的程序

如果我们的程序正在运行,你也许可以选择终止程序,再重新启动之前加上 gdb,然后进行调试。

这当然是可行的,如果你有一种不关闭正在运行的程序或者你懒得不想去关闭再启动的需求,那么你按照下面这样做:

1
2
3
gdb attach pid

建议用root用户去执行该命令

你知道如何查询某个程序的PID 吗?

1
ps aux | grep 程序名

清除断点并终止正在调试的程序

1
2
3
4
5
(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) quit

注意:只执行delete,程序仍然会卡住。

设置观察点

监控一个变量或者一个表达式的值,当这个值或者表达式的值发生变化时程序会暂停,而不需要提前在某些地方设置断点。

1
2
3
watch 变量或者表达式				写观察点

该变量或者表达式的值在发生变化时,程序会发生中断,并且在变量或者表达式发生改变的地方暂停

搜索源代码

1
2
3
search 正则表达式		正向搜索,从前往后

reverse-search 正则表达式 反向搜索,从后往前

反向执行

反向执行是真正意义上的代码"回溯"。

不过有点需要提醒,对于 IO 操作的回溯无效。就比方说你执行代码后把文件写入磁盘中,你反向执行(回溯)是不可能把已经写入的文件给回收的,希望你能理解代码"回溯"的含义。

1
2
3
4
5
6
7
8
9
record				// 开始记录程序执行过程,记录的信息包括程序的指令、寄存器的状态等。这可以帮助你在后续的调试中回溯程序的执行

reverse-next // 在反向执行模式下执行一条指令。在反向执行模式下,程序会从当前位置往回执行一条指令

reverse-continue // 在反向执行模式下继续执行程序,直到下一个断点或者程序结束

reverse-finish // 在反向执行模式下执行程序,直到当前函数结束

record stop // 停止记录程序执行过程

回溯的范围是从 record 指定开始标记。

对代码的正向执行使用 next、continue、finish,当你要准备回溯之前的执行,就选择相对应的 reverse-next、reverse-continue、reverse-finish。

不需要反向执行功能之后,记得 record stop 掉。