一、什么是core dump
我们经常听到大家说到程序core掉了,需要定位解决,这里说的大部分是指对应程序由于各种异常或者bug导致在运行过程中异常退出或者终止,
将进程此时的内存中的内容拷贝到磁盘文件中存储,我们称之为core dump。在满足一定条件时会产生一个叫做core的文件。
通常情况下,core文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成的一个文件,许多的程序出错的时候都会产生一个core文件,通过工具分析这个文件,我们可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。
二、Core Dump 如何产生
上面说当程序运行过程中
异常终止或
崩溃时会发生 core dump,但还没说到什么具体的情景程序会发生异常终止或崩溃,例如我们使用
kill
-9
命令杀死一个进程会发生 core dump 吗?实验证明是不能的,那么什么情况会产生呢?
Linux 中信号是一种异步事件处理的机制,每种信号对应有其默认的操作,你可以在
这里 查看
Linux 系统提供的信号以及默认处理。默认操作主要包括忽略该信号(Ingore)、暂停进程(Stop)、终止进程(Terminate)、终止并发生core dump(core)等。如果我们信号均是采用默认操作,那么,以下列出几种信号,它们在发生时会产生 core dump:
Signal |
Action |
Comment |
SIGQUIT
Core
Quit from keyboard
SIGILL
Core
Illegal Instruction
SIGABRT
Core
Abort signal from
abort
SIGSEGV
Core
Invalid memory reference
SIGTRAP
Core
Trace/breakpoint trap
当然不仅限于上面的几种信号。这就是为什么我们使用
Ctrl+z
来挂起一个进程或者
Ctrl+C
结束一个进程均不会产生
core dump,因为前者会向进程发出
SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process);后者会向进程发出
SIGINT 信号,该信号默认操作为终止进程(Terminate Process)。同样上面提到的
kill
-9
命令会发出
SIGKILL 命令,该命令默认为终止进程。而如果我们使用
Ctrl+
来终止一个进程,会向进程发出
SIGQUIT 信号,默认是会产生
core dump 的。还有其它情景会产生 core dump, 如:程序调用
abort()
函数、访存错误、非法指令等等。
三、linux下如何打开core dump
1、打开 core dump 功能
(1)在终端中输入命令 ulimit
-c
,输出的结果为 0,说明默认是关闭 core dump 的,即当程序异常终止时,也不会生成 core dump 文件。
(2)我们可以使用命令 ulimit
-c unlimited
来开启 core dump 功能,并且不限制 core dump 文件的大小; 如果需要限制文件的大小,将 unlimited 改成你想生成 core 文件最大的大小,注意单位为 blocks(KB)。
例如:ulimit –c 4 (注意,这里的size如果太小,则可能不会产生对应的core文件,笔者设置过ulimit –c
1的时候,系统并不生成core文件,并尝试了1,2,3均无法产生core,至少需要4才生成core文件)
(3)用上面命令只会对当前的终端环境有效,如果想需要永久生效,可以修改文件 /etc/security/limits.conf
文件
2、修改 core 文件保存的路径和文件名
(1)默认生成的 core 文件保存在可执行文件所在的目录下,文件名就为 core
。
如果程序中调用了chdir函数,则有可能改变了当前工作目录。这时core文件创建在chdir指定的路径下。
(2)通过修改 /proc/sys/kernel/core_uses_pid
文件可以让生成
core 文件名是否自动加上 pid 号。
例如 echo
1 > /proc/sys/kernel/core_uses_pid
,生成的 core 文件名将会变成 core.pid
,其中
pid 表示该进程的 PID。
(3)还可以通过修改 /proc/sys/kernel/core_pattern
来控制生成
core 文件保存的位置以及文件名格式。
例如可以用 echo
"/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern
设置生成的 core 文件保存在 “/tmp/corefile” 目录下,文件名格式为 “core-命令名-pid-时间戳”。
四、如何判断一个文件是coredump文件?
在类unix系统下,coredump文件本身主要的格式也是ELF格式,因此,我们可以通过readelf命令进行判断。
可以看到ELF文件头的Type字段的类型是:CORE (Core file)
可以通过简单的file命令进行快速判断:
五、利用gdb进行coredump的定位
其实分析coredump的工具有很多,现在大部分类unix系统都提供了分析coredump文件的工具,不过,我们经常用到的工具是gdb。
产生了 core 文件,我们该如何使用该 Core 文件进行调试呢?Linux 中可以使用 GDB 来调试 core 文件,步骤如下:
(1)首先,使用 gcc 编译源文件,加上
-g
以增加调试信息;
(2)按照上面打开 core dump 以使程序异常终止时能生成 core 文件;
(3)运行程序,当core dump 之后,使用命令
gdb
program core
来查看 core 文件,其中 program 为可执行程序名,core 为生成的 core 文件名。如果两者不在同一个目录,则需要写全路径。
六、附注:
1、几个常用的gdb命令:
l(list) ,显示源代码,并且可以看到对应的行号;
b(break)x, x是行号,表示在对应的行号位置设置断点;
p(print)x, x是变量名,表示打印变量x的值
r(run), 表示继续执行到断点的位置
n(next),表示执行下一步
c(continue),表示继续执行
q(quit),表示退出gdb
2、 gdb的查看源码
显示源代码
GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当
程序停下来以后,GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源代码的GDB命令吧。
list
显示程序第linenum行的周围的源程序。
list
显示函数名为function的函数的源程序。
list
显示当前行后面的源程序。
list -
显示当前行前面的源程序。
一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
setlistsize
设置一次显示源代码的行数。
showlistsize
查看当前listsize的设置。
list命令还有下面的用法:
list,
显示从first行到last行之间的源代码。
list ,
显示从当前行到last行之间的源代码。
list +
往后显示源代码。
一般来说在list后面可以跟以下这些参数:
行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
哪个文件的哪一行。
函数名。
哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。
3、一些常用signal的含义
SIGABRT:调用abort函数时产生此信号。进程异常终止。
SIGBUS:指示一个实现定义的硬件故障。
SIGEMT:指示一个实现定义的硬件故障。EMT这一名字来自PDP-11的emulator trap 指令。
SIGFPE:此信号表示一个算术运算异常,例如除以0,浮点溢出等。
SIGILL:此信号指示进程已执行一条非法硬件指令。4.3BSD由abort函数产生此信号。SIGABRT现在被用于此。
SIGIOT:这指示一个实现定义的硬件故障。IOT这个名字来自于PDP-11对于输入/输出TRAP(input/outputTRAP)指令的缩写。系统V的早期版本,由abort函数产生此信号。SIGABRT现在被用于此。
SIGQUIT:当用户在终端上按退出键(一般采用Ctrl-/)时,产生此信号,并送至前台进程组中的所有进程。此信号不仅终止前台进程组(如SIGINT所做的那样),同时产生一个core文件。
SIGSEGV:指示进程进行了一次无效的存储访问。名字SEGV表示“段违例(segmentationviolation)”。
SIGSYS:指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,但其指示系统调用类型的参数却是无效的。
SIGTRAP:指示一个实现定义的硬件故障。此信号名来自于PDP-11的TRAP指令。
SIGXCPUSVR4和4.3+BSD支持资源限制的概念。如果进程超过了其软C P U时间限制,则产生此信号。
SIGXFSZ:如果进程超过了其软文件长度限制,则SVR4和4.3+BSD产生此信号。
4、Core_pattern的格式
可以在core_pattern模板中使用变量还很多,见下面的列表:
%% 单个%字符
%p 所dump进程的进程ID
%u 所dump进程的实际用户ID
%g 所dump进程的实际组ID
%s 导致本次core dump的信号
%t core dump的时间 (由1970年1月1日计起的秒数)
%h 主机名
%e 程序文件名