注意看,这个男人叫小帅,他正在排查一个偶发的 coredump 问题。只见他熟练的 gdb -c core elf
打开 core 文件,bt
了一下,发现 core 在了 absl::btree::find
方法中。按理说,Abseil 库久经考验,core 在了他们代码里面,大概率是上层的业务代码出现了并发行为。
带着这个猜想,小帅便想看看其他线程的栈,希望能找到一个活跃在事故代码附近的堆栈。于是,他 thread all apply bt
了一下,但发现输出足足有一万多行,其中包含了 1076 个线程的堆栈。想要靠肉眼找出可疑的栈,小帅的眼睛怕是要瞎…
不要问为什么一个服务会开一千多个线程,代码复杂度把控不住,每个模块都往越来越臃肿的方向演进,最终合力形成一座屎山。
1. 上工具
简单的扫描后发现,这一千多个堆栈中包含大量的重复内容:
|
|
也就是说,大部分线程处于非活跃状态,在等待唤醒,他们的堆栈都是类似的。如果能把这些重复堆栈 group 到一起,折叠成一个,那就能去除不少干扰信息了。但这些重复的堆栈由于入参的不同,他们字面上的字符串并不相等,所以简单的 sort | uniq -c
组合已经解决不了这个需求了,这个时候,只能自己动手写 python 了。
在 GPT 这个废物的帮助下,小帅很快写出了gdb_bt_group.py工具:
它的工作原理很简单——将堆栈的第二列,即所有栈帧 PC 寄存器的组合作为栈的唯一标识字段。有了这个抽象的标识之后,工具就可以将相似的堆栈聚合到一起,并输出统计信息了:
|
|
这里的
[Total 320]
代表当前堆栈聚合了 320 个线程,他们都有相同的堆栈,只需要看一个就够了。
1.1 用法
将 gdb 的 bt 信息导出到文本后,用工具读取这个文本,它就能输出聚合后的堆栈信息了:
|
|
2. 结论
经过工具聚合后,原来的 1076 个堆栈压缩成了 28 个堆栈。小帅仔细的检查了下其余堆栈,果然发现了一个可疑线程,它正在执行同一个 btree
容器的 emplace
方法,但外部只加了个读锁(估计代码抄错了?)。
在这个工具的帮助下,小帅后续又成功的帮同事排查了几个多线程并发造成的 coredump 问题。所以,如果你也遇到了类似的问题,可以试试这个工具——多看几个堆栈,运气好的话,说不定就能找到可以线程呢~
另外,小帅计划,等有空了可以把这段 python 脚本封装成一个 gdb.Command,比如叫 grouped_bt
?这样就可以直接在 gdb 里面调用,而不需要执行额外的导出再读取的操作了。