用 C/C++ 编写的程序, 如果遇到 Segmentation Fault 则可以通过生成 coredump 来进行调试, 根据记录的信息定位到出错代码行. 但很多时候可能用 gdb 打开 coredump 文件查看堆栈时, 却出现一堆问号, 无法直接定位到出错代码行. 本文介绍另一种方法来还原错乱的堆栈信息.
例如, 下图是 coredump 后用 gdb 看到的堆栈信息, 可以看到这些代码都是系统依赖库中的函数信息, 如果太多地方调用这些库接口, 就很难定位到问题所在. 下图中应是在调用字符串转数字接口时出错.
coredump 堆栈信息示例
上图中仍无法定位到代码中的具体某一行. 不过寄存器 ebp 中存放了函数帧指针, 这就提供了一个追踪栈信息的线索. 首先将 ebp 的内容打印出来:
寄存器 ebp 的内容
众所周知, 栈即函数的调用栈布局. 每次函数调用, 就会将当前函数的地址及局部变量压入栈顶. 这些调用信息是以一个单向链表来组织的, 上面看到的 ebp 内容就是这个链表的 head 指针. 那么打印出 ebp 内容 0x9e76f354 指针所指内容如下:
寄存器 ebp 内容所指向的区域信息
与 head 节点一样, 第一个节点的最低 4 个字节存储的是下一链表节点位置的指针, 紧随其后的 4 个字节是该层调用的返回地址, 查看其内容如下:
第一个节点调用的返回地址
可以看到, 栈顶的函数地址是与 bt 命令打印的信息一致, 依次打印出该链表上节点信息如下:
链表各节点信息
上述信息已经基本还原了 bt 所能打印出来信息, 上图中后面的节点可以看出导致 coredump 的代码位置, 是在 TtcThmDescOper::tbThmDesc_get 函数中一次 atoll 调用时导致.
以上就是在 bt 无法打印堆栈内容时, 采用另一种方式还原函数调用层级的方式, 希望能对大家定位问题有一定帮助. 另外需要提醒大家的是, 在 64 位系统中, 寄存器 esp 变成了 rsp, 寄存器 ebp 变成了 rbp, 寄存器 ip 变成了 rip. 而在 arm 平台中, 寄存器 ebp 则变成了 fp.