在大多数日常的应用场景中,我们需要一个7 * 24小时稳定运行的服务.然而很多时候,由于服务器环境实时的改变以及程序在编写过程中可能存在的瑕疵,都会导致进程的崩溃.这个时候core文件就显示出了它的作用.
由于core文件是对进程实时运行的内存的固化,很多时候我们可能并不需要大部分信息,并且过大的core文件也可能把磁盘空间占满,本文将对core文件的生成以及如何裁剪core文件进行讨论.
一 core文件的生成
Linux下core文件有两种生成的方式.
- 后台abrtd服务生成进程的core文件
其配置文件分别是/etc/abrt/和/proc/sys/kernel/core_pattern,但是此服务不受ulimit的控制.并且abrtd是通过调用在core_pattern中配置的abrt-hook-ccpp完成core文件的生成.
2. abrt-ccpp hook
如果要启用这个机制,需要将abrtd服务停掉即可.注意这种方式受控与ulimit,所以确保ulimit -c为unlimited.
二 core文件的位置
abrtd默认会把core文件存放在/var/spool/abrt目录.这是由配置abrt.conf决定core文件的存放目录.
而abrt-ccpp hook会默认把core文件放在程序运行的目录.并且根据/proc/sys/kernel/core_uses_pid决定是否在core尾部追加pid.
三 core文件的大小
abrtd通过配置文件对core文件的最大上限进行限制,即如果core文件超过这个大小,则不会生成.配置文件在/etc/abrt/abrt.conf
abrt-ccpp hook通过设置ulimit -c对生成的core文件进行大小限制.但是这种方式很粗暴,仅仅是生成到指定大小以后就停止生成,可能问题点就在没有生成的部分.
如果要有选择的去裁剪core文件,我们需要更高级的功能,coredump-filter,这个我们稍后讨论.
四 core文件生成异常
abrtd默认会把core文件存放在/var/spool/abrt目录.这是由配置abrt.conf决定,但是经常我们程序崩溃了,却发现指定目录没有core文件,查看系统日志可以发现:
细心的朋友可能已经发现了,只要把ProcessUnpackaged选项赋值为yes即可,这个选项在配置文件文件/etc/abrt/abrt-action-save-package-data.conf中.意思是abrtd是否只对源中的程序生成core.
五 coredump-filter
前面提到,如果程序占用内存非常,core文件也会很大,即使你只是申请了一块10G的内存,而只用了其中的1bit,最终的core也会包含这10G(当然core很聪明的利用了磁盘空洞使得文件看起来很大,但是占用磁盘却很小).
那么我们该怎样有选择的去裁剪core文件使得我们既不会丢失问题点,还能尽可能缩小core呢.这就需要coredump-filter了.裁剪规则如下:
- (bit 0) anonymous private memory(匿名私有内存段,malloc,堆栈数据,代码段都属于这个段.这个bit务必为1,否则连代码段都会被丢掉)
- (bit 1) anonymous shared memory(匿名共享内存段,systemv的shmget属于这里)
- (bit 2) file-backed private memory(file-backed 私有内存段):文件映射的内存,调用了mmap,并且选项是MAP_PRIVATE.
- (bit 3) file-backed shared memory(file-bakced 共享内存段):文件映射的内存,调用了mmap,并且选项是MAP_SHARED.
- (bit 4) ELF header pages in file-backed private memory areas (it is effective only if the bit 2 is cleared)(ELF 文件映射,只有在bit 2 复位的时候才起作用)
- (bit 5) hugetlb private memory(大页面私有内存):mmap 映射的MAP_HUGETLB类型数据,大页面内存不要理解歪,一般而言一个内存页的大小是4K,而使用了hugetlb的内存页可以大于这个大小,最大可以设置到2M.
- (bit 6) hugetlb shared memory(大页面共享内存)
接下来我们用一个例子来做一下说明,代码为网上复制,很有说服力,这里借用一下:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/shm.h>
const int a = 10; //text static int c = 2; //data static int b; int d = 1; int e; //bss #define MAX_LEN (4 * 1024 * 1024 * 1024UL) int main(int argc, char *argv[]) { int len = 0; char *qqq = NULL; char *string = NULL; char *ptr1; char *ptr2; char *ptr3; char *p_malloc = malloc(MAX_LEN); //heap char *shem = NULL; int shmid = 0; const int a1 = 10; static int c1 = 2; static int b1; int d1 = 1; int e1;
int fd = open("111.txt",O_RDWR); char cmd[1024]; sprintf(cmd,"echo '%s' > /proc/%d/coredump_filter",argv[1],getpid()); system(cmd); //filebackend 共享内存映射 ptr1 = mmap(NULL, MAX_LEN , PROT_READ|PROT_WRITE,MAP_SHARED , fd , 0); //filebackend 私有内存映射 ptr2 = mmap(NULL, MAX_LEN , PROT_READ|PROT_WRITE,MAP_PRIVATE , fd , 0); //匿名共享内存 映射 ptr3 = mmap(NULL, MAX_LEN , PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1 , 0); // systemV share 内存 shmid = shmget(IPC_PRIVATE, MAX_LEN, IPC_CREAT|0600 ); shem = shmat(shmid,NULL ,0);
qqq[0] = 1; free(p_malloc); shmctl(shmid,IPC_RMID, NULL); munmap(ptr1, MAX_LEN); munmap(ptr2, MAX_LEN); munmap(ptr3, MAX_LEN); close(fd); return 0; } |
编译这个程序,gcc -ggdb ttt.c -o r,分别用1 3 5 7 f设置coredump_filter然后让程序崩溃.
运行命令 | core文件大小 | core文件可打印变量 |
./r 0x01 | 8568832 | p_malloc |
./r 0x03 | 16957440 | p_malloc,ptr3,shem |
./r 0x05 | 12414976 | p_malloc,ptr2 |
./r 0x09 | 20803584 | p_malloc,ptr1 |
./r 0x0f | 20803584 | p_malloc,ptr1,ptr2,ptr3,shem |
通过上面的这个图表可以看到,对于core文件而言,我们可以做到粗略的裁剪大小,以达到既可以调试程序,又不至于浪费太多磁盘空间的目的,这里注意,0x01位是必须要置1的,否则连函数栈都无法打印.
总结一下,我们可以去定制core的路径和大小,但是这种定制并非随心所欲的.linux内核给我们限定了严格的范围.但是有的时候,我们只想知道函数的崩溃时堆栈,这个时候core就显得有点多余了.后面我们会继续讨论,如何在函数崩溃的瞬间打印函数堆栈.