使用gdb判断Segmentation fault (core dump)是否为栈溢出导致

当程序访问了非法的内存地址后会发生段错误(segment fault),并会产生coredump文件。这通常不是我们所期望的结果,需要对产生的coredump文件进行深入的分析并排查原因。本文我们将学习当发生coredump时,如何快速的排查是否是由于栈溢出导致的。

示例代码

#include <iostream>

void test()
{
    char tmp[512];     // 申请512字节的栈空间
    test();            // 递归调用自己
}
int main()
{
    test();
    return 0;
}

操作系统:linux
编译命令:g++ test.cpp -o test -g -O0
代码分析:在示例代码中,定义了一个test函数,其中在栈空间上申请了512字节的char数组,为了造成栈溢出,接下来递归调用test函数,会不停的申请栈空间,最后导致栈溢出,发生段错误。

运行程序

首先,设置ulimit参数,以保证正常生成coredump文件。

[root@VM-8-2-centos gdb_stack_overflow]# ulimit -c unlimited

查看设置是否成功

[root@VM-8-2-centos gdb_stack_overflow]# ulimit -c
unlimited

运行程序

[root@VM-8-2-centos gdb_stack_overflow]# ./test
Segmentation fault (core dumped)

coredump文件一般会生成在当前目录下或/var/lib/systemd/coredump/目录下,将core文件放到跟可执行程序相同的目录中,如果是压缩的格式,则将coredump文件解压。

调试coredump文件

命令:gdb 可执行文件名 coredump文件名

[root@VM-8-2-centos gdb_stack_overflow]# gdb test core.test.0.b04e6db77aa04f9fbeb741681dae9e12.3265122.1715049796000000
......忽略一些gdb版本信息

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...done.
[New LWP 3265122]
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  test () at test.cpp:6
6           test();
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64 libgcc-8.5.0-21.el8.x86_64 libstdc++-8.5.0-21.el8.x86_64
(gdb)

使用bt命令查看程序调用栈信息,命令:bt

(gdb) bt
#0  test () at test.cpp:6
#1  0x00000000004006b6 in test () at test.cpp:6
#2  0x00000000004006b6 in test () at test.cpp:6
#3  0x00000000004006b6 in test () at test.cpp:6
#4  0x00000000004006b6 in test () at test.cpp:6
#5  0x00000000004006b6 in test () at test.cpp:6
......
#15867 0x00000000004006b6 in test () at test.cpp:6
#15868 0x00000000004006b6 in test () at test.cpp:6
#15869 0x00000000004006b6 in test () at test.cpp:6
#15870 0x00000000004006b6 in test () at test.cpp:6
#15871 0x00000000004006b6 in test () at test.cpp:6
#15872 0x00000000004006b6 in test () at test.cpp:6
#15873 0x00000000004006b6 in test () at test.cpp:6
#15874 0x00000000004006c2 in main () at test.cpp:11
(gdb)

通过查看调用栈信息,证明了正如示例程序设计那样,程序一直递归调用test函数直到栈溢出段错误,调用了超过1.5万次。

判断栈溢出

coredump文件中记录了所有的函数调用栈的栈帧信息,通过将最后一帧和第0帧的栈指针寄存器rsp的值相减就能计算出在程序崩溃的时刻使用的栈空间的总量。
命令:(1)f 栈帧号
切换到该栈帧号对应的栈帧
(2)p $rsp
打印栈指针寄存器rsp中存储的值,也就是当前栈帧的栈顶的地址

#15868 0x00000000004006b6 in test () at test.cpp:6
#15869 0x00000000004006b6 in test () at test.cpp:6
#15870 0x00000000004006b6 in test () at test.cpp:6
#15871 0x00000000004006b6 in test () at test.cpp:6
#15872 0x00000000004006b6 in test () at test.cpp:6
#15873 0x00000000004006b6 in test () at test.cpp:6
#15874 0x00000000004006c2 in main () at test.cpp:11
(gdb) 
(gdb) f 15874
#15874 0x00000000004006c2 in main () at test.cpp:11
11          test();
(gdb) p $rsp
$1 = (void *) 0x7ffffdeca3f0
(gdb) f 0
#0  test () at test.cpp:6
6           test();
(gdb) p $rsp
$2 = (void *) 0x7ffffd6cbfd0
(gdb) p 0x7ffffdeca3f0 - 0x7ffffd6cbfd0
$3 = 8381472
(gdb)

从本例中,最后一帧(main函数对应的栈帧,15874号)和第0帧的栈指针寄存器rsp的值相减等于8381472,单位为字节,约为8MB。
接下来,查看一下当前环境的单个线程的最大栈大小,命令:ulimit -s

[root@VM-8-2-centos gdb_stack_overflow]# ulimit -s
8192

可见,当前环境单个线程的最大栈大小为8192,单位是KB,等于8MB。而coredump文件中显示栈空间使用了约8MB,这时就基本可以判定是由于栈溢出导致程序出现段错误。如果有测试环境,可以将栈空间的最大值改大,假如改成16MB,命令:ulimit -s 16384,再启动测试程序,测试一下是否运行正常。

结束语

通过本文的学习,能够在产生段错误时快速地排查是否由于栈溢出导致。如果一开始就排查代码,将会有较大的工作量。建议先将代价较小的可能方向快速排查一下,都排除之后再进行深度的排查。

  • 23
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PerfMan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值