常用命令
命令 | 全称 | 解释 |
---|---|---|
l | list | 查看源码 |
b | break | 设置断点 |
r | run | 运行程序,在断点处停止 |
n | next | 单条语句执行(步过) |
s | step | 单条语句执行(步入) |
c | continue | 继续运行程序,下一个断点处停止 |
f | finish | 继续运行直到当前函数执行完成 |
p | 打印 | |
q | quit | 退出GDB |
调试段错误文件
先保证能生成 coredump 文件
转存文件可以是某个进程的,也可以是整个系统的。
可以是进程活着的时候生成的,也可以是进程或者系统崩溃的时候自动生成的。
但明显段错误文件是要进程或者系统崩溃的时候自动生成,因为段错误必然不可能让进程活着。
1 |
|
调试基本步骤
1 |
|
通过 bt 就可以看出发生段错误的调用堆栈,调用顺序从下往上看,一般去找自己实现的代码进行错误排查。
调试死锁文件
1 |
|
死锁文件是正在运行的,要通过 gcore 命令生成 coredump 文件。
1 |
|
查看所有线程的堆栈,找出处于 lock wait 的线程:
可以看到,线程 2 和 线程 3 是 处于 lock wait
状态,表明它们正在等待锁。当多个线程在 __lll_lock_wait
处停住时,很可能是因为它们在等待某个锁资源,出现了互相等待的情况。
切换到 这两个线程,打印我们在代码中定义的 mutex 变量(mutexA 和 mutexB)。其中 owner 属性代表的就是当前锁占有者的线程 ID(注意在 gdb 中为LWP)。
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 的地址,可以知晓就是它。
进入两个线程从,并分别查看堆栈情况,依旧是找我们自己的代码,并自行分析死锁原因。
查看 thread 2 的堆栈情况:
查看 thread 3 的堆栈情况:
然后到相应的地方自己去分析,通常我们解决死锁的办法是按顺序获取锁资源。
远程调试
被调试的目标机器需要安装
gdbserver:apt-get install gdbserver
(一)目标机程序没有启动
1 |
|
目标机上运行者需要被调试的程序,调试机是主动远程去调试目标机的机器。
目标机的命令在 shell 命令行执行,调试机命令在进入 gdb 之后再执行。
(二)目标机程序已经启动
1 |
|
如果目标机的程序已经启动,则无法使用直接启动的方式来进行远程调试。此时需要采用附加到远程进程的方式来进行远程调试。
小技巧
查看当前函数的参数
1 |
|
在没有进入函数内部之前,函数的参数肯定是没有实际意义的数据。
step 进入函数之后,执行上面的命令就有效果了。
查看/修改变量的值
1 |
|
自动显示变量的值
print 需要每次都输入,如果你非常关心某个变量值的变化,display 可以帮大忙。
1 |
|
调试正在运行的程序
如果我们的程序正在运行,你也许可以选择终止程序,再重新启动之前加上 gdb,然后进行调试。
这当然是可行的,如果你有一种不关闭正在运行的程序或者你懒得不想去关闭再启动的需求,那么你按照下面这样做:
1 |
|
你知道如何查询某个程序的PID 吗?
1 |
|
清除断点并终止正在调试的程序
1 |
|
注意:只执行delete,程序仍然会卡住。
设置观察点
监控一个变量或者一个表达式的值,当这个值或者表达式的值发生变化时程序会暂停,而不需要提前在某些地方设置断点。
1 |
|
搜索源代码
1 |
|
反向执行
反向执行是真正意义上的代码"回溯"。
不过有点需要提醒,对于 IO 操作的回溯无效。就比方说你执行代码后把文件写入磁盘中,你反向执行(回溯)是不可能把已经写入的文件给回收的,希望你能理解代码"回溯"的含义。
1 |
|
回溯的范围是从 record 指定开始标记。
对代码的正向执行使用 next、continue、finish,当你要准备回溯之前的执行,就选择相对应的 reverse-next、reverse-continue、reverse-finish。
不需要反向执行功能之后,记得 record stop 掉。