在liunx下c语言开发程序,最近遇到程序崩溃的现象,由于现场看不到任何崩溃的信息,很难定位问题。此时,内核转储(coredump)就派上用场了。
通常情况下coredmp包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等,在设置妥当的情况下,该coredump文件在程序出错时自动生成。
Coredump文件,是Unix/Linux操作系统的一种机制,对于线上服务而言,出现Core的过程意味着服务暂时不能正常响应,需要恢复,并且随着吐Core进程的内存空间越大,此过程可能持续很长一段时间(例如当进程占用60G+以上内存时,完整Core文件需要15分钟才能完全写到磁盘上),这期间产生的流量损失,不可估量。
另一方面,OS在出Core的同时,虽然会终止掉当前进程,但是也会保留下第一手的现场数据,OS仿佛是一架被按下快门的相机,而照片就是产出的Core文件。里面含有当进程被终止时内存、CPU寄存器等信息,可以供后续开发人员进行调试。
简单的说,在一个程序崩溃时,系统会在指定目录下生成一个core文件。core文件主要是用来调试的。
ulimit -c 0
[john@localhost ~]$ ulimit -c
0
[john@localhost ~]$
-c 表示内核转储文件的大小限制,如果显示为零,则未打开。
ulimit -c 1073741824 #改为1G
ulimit -c unlimited #改为无限制
上面所述的方法,只是在当前shell中生效,重启后丢失。永久生效的办法是在profile中添加:
#vi /etc/profile
ulimit -c 1073741824 #注意,若将产生的转储文件大小大于该数字时,将不会产生转储文
或者,
ulimit -c unlimited
这样重启机器后就生效了。也可以使用source命令使之马上生效。
source /etc/profile
缺省情况下,内核在coredump时所产生的core文件与该程序在相同的目录中,并且文件名固定为core。
这时,如果有多个程序产生core文件,或者同一个程序多次崩溃,就会重复覆盖同一个core文件。
可以通过修改kernel的参数,指定内核转储所生成的core文件的路径和文件名。在/etc/sysctl.conf中,设置sysctl变量kernel.core_pattern的值。
#vi /etc/sysctl.conf
kernel.core_pattern = /var/core/core_%e_%p #指定生成coredump文件的路径和文件名
kernel.core_uses_pid = 0
需要说明的是,如果/proc/sys/kernel/core_uses_pid的内容被配置成1,即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。
其中,%e, %p分别表示:
%c 转储文件的大小上限
%e 所dump的文件名
%g 所dump的进程的实际组ID
%h 主机名
%p 所dump的进程PID
%s 导致本次coredump的信号
%t 转储时刻(由1970年1月1日起计的秒数)
%u 所dump进程的实际用户ID
可以使用以下命令,使修改结果马上生效。
sysctl –p /etc/sysctl.conf
有些bug是不会导致程序crash的,比如死锁,这时程序已经不正常了,可是却没有coredump产生。如果环境又不允许gdb调试,就可以尝试强制该进程产生coredump文件。
一般情况下,可以利用 watchdog监控进程,当发现这些进程很长时间没有更新其heartbeat时,可以给这些进程发送可以导致其产生coredump的信号。根据 linux的信号默认的处理行为,SIGQUIT,SIGABRT, SIGFPE和SIGSEGV都可以让该进程产生coredump文件。这样我们可以通过coredump来得知死锁是否发生。 当然, 如果进程添加了这些信号的处理函数,那么就不会产生coredump了。
还有一种情况,进程并没有死锁或者block在某个位置,但是我们需要在某个指定位置进行调试,获取某些变量或者其它信息。但是,有可能是客户环境或者生产环境,不允许我们进行长时间的检测。那么,我们就需要通 过coredump来获得进程在运行到该点时的快照。 这个时候,可以利用gdb来手工产生coredump。在attach上这个进程时,在指定位置打上断点,当断点触发时,使用gdb的命令gcore,可以立即产生一个coredump。
使用gdb调试
这时就可以用backtrace/thread等命令查看当时的错误了,就像程序在本地执行到崩溃点一样,或者用where回车, 也可以显示程序在哪一行当掉的
#include
int main(void)
{
int *a = NULL;
*a = 0x1;
return 0;
}
使用gcc -g a.c
编译生成可执行文件a.out,运行就会显示:Segmentation fault(core dump),这表示在当前目录下, 已经生成了a.out对应的内核转储文件。
使用以下方式启动GDB调试:
#gdb -c ./*.core ./a.out
GNU gdb (GDB) 7.1-Ubuntu
...
Core was generated by './a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000004004dc in main() at a.c:6
6 *a =0x1;
a.c的第6行收到了11号信号。用GDB的list命令可以查看附近的源代码。
[john@localhost ~]$ ulimit -a
core file size (blocks, -c) 0 #此时默认不会生成core文件
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7168
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[john@localhost ~]$
ulimit [-SHacdflmnpstuv [limit]]
在支持它的系统上,对 shell 和它启动的进程,提供对可用资源的控制。 选项 -H 和 -S 指定为所给资源设定的硬性和柔性限额。
硬性限额在设置后不能增加;柔性限额可以增加,直到与硬性限额相等。 如果没有给出 -H 或 -S
选项,将同时设置硬性和柔性限额。 limit 的值可以是一个数字,单位是指定资源的单元值,或者是特殊值 hard, soft, 或 unlim‐
ited 之一,意思分别是当前硬性限额,当前柔性限额和没有限额。如果忽略了 limit,
将打印出当前对资源的柔性限额值,除非给出了 -H 选项。当指定多于一个
资源时,限额名称和单位将在值之前打印出来。其他选项按照如下意义解释:
-a 报告所有当前限额
-c core 文件的最大值
-d 进程数据段的最大值
-f shell 创建的文件的最大值
-l 内存中可以锁定的最大值
-m 常驻内存的最大值
-n 打开的文件描述符最大个数 (大多数系统不允许设置这个值)
-p 管道大小,以 512 字节的块为单位 (这个值可能不能设置)
-s 栈的最大值
-t cpu 时间总数的最大值,以秒计
-u 用户可以运行的最大进程数
-v shell 可用的虚拟内存总量的最大值
如果给出了 limit, 它将是指定资源的新限额 (选项 -a 只显示它们)。如果没有给出选项,则假设有 -f。 值的递增间隔是 1024
字节,除了 -t 单位是 秒, -p 单位是 512 字节的块个数, -n 和 -u 是不可调节的值。返回
0,除非给出了非法的选项或参数,或者在设置新的限额时发生了错误。