怎么使用GDB快速定位问题?
作者:
| 更新日期:看到很多人不怎么会使用GDB, 这里简单记录一下我的实践.
本文首发于公众号:天空的代码世界,微信号:tiankonguse
大家好, 这里是tiankonguse的公众号(tiankonguse-code).
tiankonguse曾是一名ACMer, 现在是鹅长视频部门的后台开发.
这里主要记录工作中的技术架构与经验,计算机相关的技术,数学、算法、生活上好玩的东西
是的, 这里一般只讲技术, 不讲其他的.这篇文章从公众号tiankonguse-code自动同步过来.
如果转载请加上署名:公众号tiankonguse-code,并附上公众号二维码,谢谢。
零、定位问题的两大神器
作为c/c++后台开发, 平常开发服务的时候,避免不了程序挂掉.
比如这周四, 我接手的微博中转在测试环境就出现问题了, 于是使用两大神器慢慢的解开了问题的面纱.
- 第一神器: printf 或者 LOG
- 第二神器: GDB
printf和LOG由于太强大了, 这里就不多说了.
这里来说说第二个神器GDB吧.
两年前这个时候已经写过gdb 学习记录(文章底部点击原文可以查看连接).
看到很多人不怎么使用GDB, 这里介绍我常用的一些命令, 然后介绍一下我的实践.
如果你们有GDB的疑问, 或者不知道怎么定位程序core的话, 可以留言, 我会提出一些建议.
一、基本实践
基本用法由于google上到处都是, 这里就不细说了, 只简单提一下.
这里不会讲这些基本操作.
- 程序挂到GDB
gdb [exe] [PID|core]
- 设置命令行参数
- 基本的打断点
break file:line|file:function
,info breakpoints
,delete
- 看源码
directory path
,list
- 启动程序
run
,next
,continue
- 函数操作
step
,finish
- 当前堆栈
where
,bt
- 打印
print
二、实践开始
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
.
十一、参考资料
十二、其他文章
- UNION重生之架构篇
- 每秒千万级别的量是重生还是炼狱?
- 每秒千万每天万亿级别服务之诞生
- UNION系统的运营与运维
- 谈谈cache
- 浪潮之巅
- 排名算法
- 字符串hash函数的本质
- 谈谈布隆过滤器(Bloom Filter)
长按图片关注公众号, 接受最新文章消息.
本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。