在执行程序时,有时会遇到“Segmentation fault(core dumped)”,一般说程序core掉了,通常是指应用程序由于异常或bug导致异常退出或终止,并产生一个coredump文件。coredump文件中包含了程序运行时的内存、寄存器状态,堆栈指针、函数调用堆栈等信息,通过对coredump文件的分析,可以帮助我们定位导致程序异常退出的问题并及时解决。
默认地,coredump文件跟可执行程序在同一个目录下,可以通过查看core_pattern文件来查看core文件的存储位置
#cat /proc/sys/kernel/core_pattern
core
修改程序对应的coredump文件
echo '/tmp/core-%e-%p-%t' > /proc/sys/kernel/core_pattern
即将coredump文件保存在/tmp目录下,并命名为“core-可执行文件名-PID-timestamp”;
core_patten中使用的变量模板很多,常用的有:
%%:输出一个"%"字符;
%e:executable filename,可执行文件名;
%p:dump进程的PID;
%u:dump进程的UID;
%s:导致该coredump的signal;
%t::coredump文件创建的时间
注:如果第一个字符是'|',表示把后面的pattern作为命令来执行,这样不会创建coredump文件,而是将其输入到某个程序来处理。
比如 '|/usr/share/apport/apport %p %s %c %d %P' 就是表示由apport来处理core dump文件。
在Ubuntu发行版中,default是不会开启core dump的。
#ulimit -c
检查单个core dump文件的大小,如果返回0,就不会产生coredump文件
#ulimit -c unlimited
对生成的core dump文件大小不做限制;
但是ulimit的作为范围是当前shell进程及其派生出的子进程,如果用户同时运行了两个shell终端进程,只是在其中一个环境中设置了ulimit -c unlimited,那只会在该进程里创建的core dump文件生效,另一个shell终端及其上运行的子进程都不会受其影响。
要想在全部shell窗口生效,需要修改/etc/profile文件,
在/etc/profile中添加: #ulimit -c unlimited
保存后,#source /etc/profile 就会在所有shell窗口中生效了。
我们通常使用GDB查看coredump文件,定位程序异常,下面是我使用coredump + GDB下Ubuntu下查找segmentation fault 原因的一个例子。
我在userspace执行一个测试程序pperf时,报告Segmentation fault (core dumped)
查看coredump文件格式
# file /tmp/core-pperf-19395-1584497577
ELF 64-bit LSB core file x86-64;
使用gdb查看coredump文件,定位问题
#gdb /tmp/core-pperf-19395-1584497577
结果出现:not in executable format,file format not recognized问题;
使用-c 指定coredump文件即可。
#gdb -c /tmp/core-pperf-19395-1584497577
显示如下:
Core was generated by './pperf 0 0 0 1'
Program terminated with signal SIGSEGV, segmentation fault.
#0 0x0000558fc19c39c6 in ??()
(gdb) list //列出源码,显示行号
显示No symbol file is loaded,use the 'file' command
(gdb) file
No executable file now
No symbol file now.
退出gdb,修改Makefile,在CFLAGS中添加-g选项,重新编译程序;这样才能把源文件信息编译到可执行文件中,否则我们就无法在gdb中查看到源程序了。
再次执行pperf程序,出现segmentation fault,保存coredump文件为/tmp/core-pperf-19395-1584497577。之后,在启动gdb时,指定executable file的名字,如下;
#gdb -c /tmp/core-tuxclocker-19395-1584497577 pperf
Program terminated with signal SIGSEGV, segmentation fault.
#0 inner () at cache_miss_load.S:18
18 wbinvd
(gdb)
能看到这里已经把导致segmentation fault的文件和行号显示出来了,cache_miss_load.S中的18行, wbinvd命令导致了segmentation fault。
去查x86 指令手册,不难发现wbinvd是privileged instruction,需要在CPL=0(即kernel space)下使用,我们这里在userspace下执行会触发#GP(0) 。
常用的几个gdb命令
l(list):显示源代码,可以看到对应的行号;
b(break) N:N是行号,表示在对应的行号处设置断点;
p(print) X:X是变量名,表示打印变量X的值;
r(run) :继续执行到断点的位置
n(next):单步执行下一步
c(continue):继续执行;
q(quit):退出gdb;
导致程序coredump的原因有很多,以下列出常见的几种:
1)内存访问越界
a) 使用错误的下标,导致数组访问越界;
2)多线程程序使用了线程不安全的函数
3)多线程读写的数据没有加锁保护;
4)非法指针
a) 使用空指针,
b) 随意使用指针转换;
5)堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),容易造成堆栈溢出,破坏系统的栈和堆结构,导致莫名其妙的错误。
注:Ubuntu下的apport 内部错误报告程序
Ubuntu的桌面版预装了apport,它是一个错误收集系统,会收集软件崩溃、未处理异常和其他,包括程序bug,并为调试生成崩溃报告。当一个应用程序崩溃或是出现bug 时,Apport就会通过弹窗警告用户并且询问是否提交崩溃报告。
apport service启动之后,默认地使用apport来处理core dump文件,就不会再生成core dump文件了,我们在查看core_patten时,结果如下:
# cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P
表示由apport来处理coredump文件;
关闭apport之后,才会创建core dump文件,查看core_pattern如下
# cat /proc/sys/kernel/core_pattern
core
临时关闭apport :$sudo service apport stop
重启之后,apport会再次开启;
永久关闭apport,修改/etc/default/apport,设置enabled =0
之后,重启Ubuntu,apport就不会再启动了。