怎么使用GDB快速定位问题?

作者: | 更新日期:

看到很多人不怎么会使用GDB, 这里简单记录一下我的实践.

大家好, 这里是tiankonguse的公众号(tiankonguse-code).
tiankonguse曾是一名ACMer, 现在是鹅长视频部门的后台开发.
这里主要记录工作中的技术架构与经验,计算机相关的技术,数学、算法、生活上好玩的东西
是的, 这里一般只讲技术, 不讲其他的.

这篇文章从公众号tiankonguse-code自动同步过来.
如果转载请加上署名:公众号tiankonguse-code,并附上公众号二维码,谢谢。

零、定位问题的两大神器

作为c/c++后台开发, 平常开发服务的时候,避免不了程序挂掉.
比如这周四, 我接手的微博中转在测试环境就出现问题了, 于是使用两大神器慢慢的解开了问题的面纱.

  1. 第一神器: printf 或者 LOG
  2. 第二神器: GDB

printf和LOG由于太强大了, 这里就不多说了.
这里来说说第二个神器GDB吧.

两年前这个时候已经写过gdb 学习记录(文章底部点击原文可以查看连接).
看到很多人不怎么使用GDB, 这里介绍我常用的一些命令, 然后介绍一下我的实践.
如果你们有GDB的疑问, 或者不知道怎么定位程序core的话, 可以留言, 我会提出一些建议.

一、基本实践

基本用法由于google上到处都是, 这里就不细说了, 只简单提一下.
这里不会讲这些基本操作.

二、实践开始

GDB稍微复杂的命令如果掌握几个, 可以快速定位到程序的问题.

一般程序core了, 能马上找到对应的行数, 然后我们直接看源代码看那里为什么错误就能解决问题了.
但是有时候我们的程序是主动退出的, 如调用exit退出的.

对应的代码可能大量的调用了exit.
这个时候很有效的方式是exit上打断点b exit,然后堆栈移动up, 然后就可以快速找到问题了where.
一种更好的方法是直接结束exit函数return,然后对后面的单步调试.

三. 挂在哪了

有时候我们不知道程序挂在哪了.
对于这种情况, 可以使用信号断点handle Signals stop, 然后堆栈移动frame|up|down到正常的函数里即可.
这个方法也可以用于定位因为使用STL导致堆栈全是问号的问题.

四. 异常值

有时候我们会发现某个变量出现了非预期的值, 想看看是什么时候变的.
很多初学者会直接一路next下去, 如果没有循环还好, 逻辑复杂了循环多了, 将会耗费大量的人力与时间.
我们可以利用watch|rwatch|awatch三个变量名来帮助我们快速定位到具体位置.

五. 多线程调试

多线程问题一般是竞争资源没加锁导致的, 这篇文章不讲这个话题.

这里说说多线程常用的场景.

info thread用于显示线程信息
thread <ID> 切换到某个线程
break filename:line thread ID 给某个线程设置断点
set scheduler-locking off|on|step 其他线程都停止
set follow-fork-mode child是子进程的,这里也提一下.

六、宏

有时候我们希望看看宏的值, 但是宏预处理时就没了.
所以我们编译的时候需要告诉编译器保留宏信息-ggdb3.
然后就可以使用info macro 查看宏了.
当然也可以使用macro expand展开宏.

七、变量

有时候我们在GDB里需要进行逻辑运算, 复杂了就需要定义几个变量.
GDB中变量名以$开始.
比如set $index = 0.
如果要修改程序的变量,则可以直接set var name=value.

八. 自动查看变量

对于逻辑复杂的程序, 我们单步调试时可能会频繁的输出某些变量的值.
此时我们可以使用display命令来自动打印变量的值.

九、执行函数查询值

比如我们有一个hash函数, 想看看我们指定的参数运算后的值, 就可以手动的调用函数了call fun(argv).

十、我定位出的问题

微博中转有一个进程监听子进程的状态,子进程死了就会重新拉取子进程。
发生的问题是子进程一直在死.

所以我第一步是通过set follow-fork-mode child进入到子进程的.

子进程是主动调用exit退出的, 但是八万行代码, 大量的使用了exit.
所以我在exit上打了断点b exit,然后栈移动up找到在哪行代码调用exit的.

找到程序退出的代码位置后, 开始在对应的地方单步调试next, 查看为什么会进入退出逻辑.
发现调用了一个网络框架代码, 并返回了一个错误码, 于是打印错误码p iRet,然后进入函数step.

进入函数后发现访问的socket为0才返回错误退出的, socket是子进程初始化的时候统一创建的, 所以需要对初始化逻辑单步调试.

初始化逻辑里面找到创建socket的地方, 发现最后一个socket创建失败, 并打印错误码p errno发现为24.

然后查询到24为FD不够了,然后查询进程的FD最大数量cat /proc/num/limits.
查询进程已经使用的fd数量lsof -p num,发现确实使用完了.

阅读网络框架, 发现网络框架存在BUG, 给用户预留的FD比较少, 结果父进程拉起子进程时, 已经使用了很多个了, 子进程就不够了.
于是修改网络框架, 预留多点FD修复问题.

这里我不会告诉你GDB可以像c语言一样使用指针,数组,对象访问, 如p (pInfo->NumList)[index<<1] + *pData.

十一、参考资料

十二、其他文章


长按图片关注公众号, 接受最新文章消息.

点击查看评论

关注公众号,接收最新消息

关注小密圈,学习各种算法

tiankonguse +
穿越