coredump配置、产生、分析以及分析示例【转】


转自:

关键词:coredump、core_pattern、coredump_filter等等。

应用程序在运行过程中由于各种异常或者bug导致退出,在满足一定条件下产生一个core文件。

通常core文件包含了程序运行时内存、寄存器状态、堆栈指针、内存管理信息以及函数调用堆栈信息。

core就是程序当前工作转改存储生成的一个文件,通过工具分析这个文件,可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题点并解决。

1. 配置coredump

如果需要使用需要通过ulimit进行设置,可以通过ulimit -c查看当前系统是否支持coredump。如果为0,则表示coredump被关闭。

通过ulimit -c unlimited可以打开coredump。

coredump文件默认存储位置与可执行文件在同一目录下,文件名为core。

可以通过/proc/sys/kernel/core_pattern进行设置。

%p  出Core进程的PID
%u  出Core进程的UID
%s  造成Core的signal号
%t  出Core的时间,从1970-01-0100:00:00开始的秒数
%e  出Core进程对应的可执行文件名

通过echo "core-%e-%p-%s-%t" > /proc/sys/kernel/core_pattern。

在每个进程下都有coredump_filter节点/proc//coredump_filter。

通过配置coredump_filter可以选择需在coredump的时候,将哪些内容dump到core文件中。

  - (bit 0) anonymous private memory
  - (bit 1) anonymous shared memory
  - (bit 2) file-backed private memory
  - (bit 3) file-backed shared memory
  - (bit 4) ELF header pages in file-backed private memory areas (it is effective only if the bit 2 is cleared)
  - (bit 5) hugetlb private memory
  - (bit 6) hugetlb shared memory
  - (bit 7) DAX private memory
  - (bit 8) DAX shared memory

coredump_filter的默认值是0x33,也即发生coredump时会将所有anonymous内存、ELF头页面、hugetlb private memory内容保存。

coredump_filter可以被子进程继承,可以echo 0xXX > /proc/self/coredump_filter设置当前进程的coredump_filter。

static ssize_t proc_coredump_filter_write(struct file *file,
                      const char __user *buf,
                      size_t count,
                      loff_t *ppos)
{
...
    ret = kstrtouint_from_user(buf, count, 0, &val);-------------------------将buf转换成val值。
    if (ret < 0)
        return ret;
...
    for (i = 0, mask = 1; i < MMF_DUMP_FILTER_BITS; i++, mask <<= 1) {
        if (val & mask)
            set_bit(i + MMF_DUMP_FILTER_SHIFT, &mm->flags);------------------将coredump_filter的值映射到mm->flags上,后续coredump时使用。
        else
            clear_bit(i + MMF_DUMP_FILTER_SHIFT, &mm->flags);
    }
...
}

其中MMF_DUMP_FILTER_SHIFT为2,所以flags和coredump_filter存在如下对应关系。

#define MMF_DUMP_ANON_PRIVATE    2
#define MMF_DUMP_ANON_SHARED    3
#define MMF_DUMP_MAPPED_PRIVATE    4
#define MMF_DUMP_MAPPED_SHARED    5
#define MMF_DUMP_ELF_HEADERS    6
#define MMF_DUMP_HUGETLB_PRIVATE 7
#define MMF_DUMP_HUGETLB_SHARED  8
#define MMF_DUMP_DAX_PRIVATE    9
#define MMF_DUMP_DAX_SHARED    10

2. coredump原理

在do_signal()中根据信号判断是否触发coredump,当然还跟coredump limit、mm->flags等等相关。

满足coredump条件后,由do_coredump()进行coredump文件生成,核心是由binfmt->core_dump()进行的。

2.1 触发coredump的条件?

在内核返回用户空间的时候,会调用do_signal()处理信号。

get_signal(&ksig)) {
...
    }
...
}

sig_kernel_coredump(signr)) {
            if (print_fatal_signals)------------------------------可以通过kernel.print-fatal-signals = 1进行设置,对应的节点是/proc/sys/kernel/print-fatal-signals。
                print_fatal_signal(ksig->info.si_signo);----------打印当前信号及当前场景的栈信息。
            proc_coredump_connector(current);
            do_coredump(&ksig->info);
        }
...
    }
    spin_unlock_irq(&sighand->siglock);

    ksig->sig = signr;
    return ksig->sig > 0;
}

SIG_KERNEL_COREDUMP_MASK)

format_corename(&cn, &cprm);-------------------------------根据core_pattern判断是否是ispipe,然后根据core_pattern的设置生成coredump文件名称。 if (ispipe) {-------------------------------------------------------通过管道处理coredump信息。 int dump_count; char **helper_argv; struct subprocess_info *sub_info; if (ispipe < 0) { printk(KERN_WARNING "format_corename failed\n"); printk(KERN_WARNING "Aborting core\n"); goto fail_unlock; } if (cprm.limit == 1) { printk(KERN_WARNING "Process %d(%s) has RLIMIT_CORE set to 1\n", task_tgid_vnr(current), current->comm); printk(KERN_WARNING "Aborting core\n"); goto fail_unlock; } cprm.limit = RLIM_INFINITY; dump_count = atomic_inc_return(&core_dump_count); if (core_pipe_limit && (core_pipe_limit < dump_count)) { printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n", task_tgid_vnr(current), current->comm); printk(KERN_WARNING "Skipping core dump\n"); goto fail_dropcount; } helper_argv = argv_split(GFP_KERNEL, cn.corename, NULL);----------将cn.corename参数进行拆分。 if (!helper_argv) { printk(KERN_WARNING "%s failed to allocate memory\n", __func__); goto fail_dropcount; } retval = -ENOMEM; sub_info = call_usermodehelper_setup(helper_argv[0], helper_argv, NULL, GFP_KERNEL, umh_pipe_setup, NULL, &cprm);---------------------通过usermodehelper调用用户空间的helper_argv[0]程序进行core_pattern。 if (sub_info) retval = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);-----------------------------UMH_WAIT_EXEC表示在内核exec用户空间程序之后就退出,此时用户空间程序就通过pipe等待接收数据。 argv_free(helper_argv); if (retval) { printk(KERN_INFO "Core dump to |%s pipe failed\n", cn.corename); goto close_fail; } } else { struct inode *inode; int open_flags = O_CREAT | O_RDWR | O_NOFOLLOW | O_LARGEFILE | O_EXCL; if (cprm.limit < binfmt->min_coredump) goto fail_unlock; if (need_suid_safe && cn.corename[0] != '/') { printk(KERN_WARNING "Pid %d(%s) can only dump core "\ "to fully qualified path!\n", task_tgid_vnr(current), current->comm); printk(KERN_WARNING "Skipping core dump\n"); goto fail_unlock; } if (!need_suid_safe) { mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); /* * If it doesn't exist, that's fine. If there's some * other problem, we'll catch it at the filp_open(). */ (void) sys_unlink((const char __user *)cn.corename); set_fs(old_fs); } if (need_suid_safe) {---------------------------------------------创建coredump文件。 struct path root; task_lock(&init_task); get_fs_root(init_task.fs, &root); task_unlock(&init_task); cprm.file = file_open_root(root.dentry, root.mnt, cn.corename, open_flags, 0600); path_put(&root); } else { cprm.file = filp_open(cn.corename, open_flags, 0600); } if (IS_ERR(cprm.file)) goto fail_unlock; inode = file_inode(cprm.file); if (inode->i_nlink > 1)------------------------------------------coredummp文件不能有多个硬链接。 goto close_fail; if (d_unhashed(cprm.file->f_path.dentry)) goto close_fail; if (!S_ISREG(inode->i_mode))--------------------------------------coredump文件必须为普通文件。 goto close_fail; if (!uid_eq(inode->i_uid, current_fsuid())) goto close_fail; if ((inode->i_mode & 0677) != 0600) goto close_fail; if (!(cprm.file->f_mode & FMODE_CAN_WRITE))-----------------------coredump文件必须可写。 goto close_fail; if (do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file)) goto close_fail; } /* get us an unshared descriptor table; almost always a no-op */ retval = unshare_files(&displaced); if (retval) goto close_fail; if (displaced) put_files_struct(displaced); if (!dump_interrupted()) { file_start_write(cprm.file); core_dumped = binfmt->core_dump(&cprm);---------------------------调用对应程序加载器的core_dump进行处理,将数据写入到cprm.file中。 file_end_write(cprm.file); } if (ispipe && core_pipe_limit) wait_for_dump_helpers(cprm.file); close_fail: if (cprm.file) filp_close(cprm.file, NULL); fail_dropcount: if (ispipe) atomic_dec(&core_dump_count); fail_unlock: kfree(cn.corename); coredump_finish(mm, core_dumped); revert_creds(old_cred); fail_creds: put_cred(cred); fail: return; }

format_corename()根据core_pattern中的设置,生成coredump文件名。并且判断coredump文件生成方式,ispipe为真则通过管道传输给其他应用处理;否则直接保存成文件。

umh_pipe_setup()创建了一个管道,这个管道给内核coredump和用户空间程序搭建了一个桥梁。

内核coredump的数据写入管道,用户空间程序在管道另一端接收进行处理。

create_pipe_files(files, 0);----------------------------创建一个pipe管道,files[0]是管道的读端;files[1]是管道的写端。
    if (err)
        return err;

    cp->file = files[1];----------------------------------------------cp->file指向管道的写端,后面coredump写入这里。

    err = replace_fd(0, files[0], 0);---------------------------------这里将files[0]作为usermodehelper执行程序的输入,coredump的数据通过管道给用户空间程序接收。
    fput(files[0]);
    /* and disallow core files too */
    current->signal->rlim[RLIMIT_CORE] = (struct rlimit){1, 1};

    return err;
}

 elf_core_dump,
    .min_coredump    = ELF_EXEC_PAGESIZE,
};

dump_emit(cprm, elf, sizeof(*elf)))---------------------------写入elf头到cprm->file文件,在使用pipe的情况下,这些数据都交给usermodehelper启动的用户空间程序进行处理。
        goto end_coredump;

    if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note)))---------------写入phdr4node到cprm->file文件。
        goto end_coredump;

    /* Write program headers for segments dump */
    for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
            vma = next_vma(vma, gate_vma)) {
        struct elf_phdr phdr;

        phdr.p_type = PT_LOAD;
        phdr.p_offset = offset;
        phdr.p_vaddr = vma->vm_start;
        phdr.p_paddr = 0;
        phdr.p_filesz = vma_filesz[i++];
        phdr.p_memsz = vma->vm_end - vma->vm_start;
        offset += phdr.p_filesz;
        phdr.p_flags = vma->vm_flags & VM_READ ? PF_R : 0;
        if (vma->vm_flags & VM_WRITE)
            phdr.p_flags |= PF_W;
        if (vma->vm_flags & VM_EXEC)
            phdr.p_flags |= PF_X;
        phdr.p_align = ELF_EXEC_PAGESIZE;

        if (!dump_emit(cprm, &phdr, sizeof(phdr)))
            goto end_coredump;
    }

    if (!elf_core_write_extra_phdrs(cprm, offset))
        goto end_coredump;

     /* write out the notes section */
    if (!write_note_info(&info, cprm))
        goto end_coredump;

    if (elf_coredump_extra_notes_write(cprm))
        goto end_coredump;

    /* Align to page */
    if (!dump_skip(cprm, dataoff - cprm->pos))
        goto end_coredump;

    for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
            vma = next_vma(vma, gate_vma)) {
        unsigned long addr;
        unsigned long end;

        end = vma->vm_start + vma_filesz[i++];

        for (addr = vma->vm_start; addr < end; addr += PAGE_SIZE) {
            struct page *page;
            int stop;

            page = get_dump_page(addr);
            if (page) {
                void *kaddr = kmap(page);
                stop = !dump_emit(cprm, kaddr, PAGE_SIZE);
                kunmap(page);
                put_page(page);
            } else
                stop = !dump_skip(cprm, PAGE_SIZE);
            if (stop)
                goto end_coredump;
        }
    }
    dump_truncate(cprm);

    if (!elf_core_write_extra_data(cprm))
        goto end_coredump;

    if (e_phnum == PN_XNUM) {
        if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum)))
            goto end_coredump;
    }

end_coredump:
    set_fs(fs);

cleanup:
    free_note_info(&info);
    kfree(shdr4extnum);
    kfree(vma_filesz);
    kfree(phdr4note);
    kfree(elf);
out:
    return has_dumped;
}

Core file 文件格式(Linux Coredump文件结构)》,GDB解析coredump文件参考《GDB如何从Coredump文件恢复动态库信息》。

3. coredump案例

下面创建一个简单产生coredump的示例,然后通过gdb进行分析。

3.1 coredump示例

#include 
#include 
#include 
#include 

int myfunc(int i) {
    *(int*)(NULL) = i; /* line 7 */
    return i - 1;
}

int main(int argc, char **argv) {
    /* Setup some memory. */
    char data_ptr[] = "string in data segment";
    char *mmap_ptr;
    char *text_ptr = "string in text segment";
    (void)argv;
    mmap_ptr = (char *)malloc(sizeof(data_ptr) + 1);
    strcpy(mmap_ptr, data_ptr);
    mmap_ptr[10] = 'm';
    mmap_ptr[11] = 'm';
    mmap_ptr[12] = 'a';
    mmap_ptr[13] = 'p';
    printf("text addr: %p\n", text_ptr);
    printf("data addr: %p\n", data_ptr);
    printf("mmap addr: %p\n", mmap_ptr);

    /* Call a function to prepare a stack trace. */
    return myfunc(argc);
}

使用如下命令编译,-ggdb3表示产生更多适合GDB的调试信息,3是最高等级。

gcc -ggdb3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c 

3.2 coredump+gdb分析

通过ulimit -c unlimited打开coredump功能,执行./main.out产生core文件。

text addr: 0x4007d4
data addr: 0x7ffff28fdc30
mmap addr: 0x10bb010
Segmentation fault (core dumped)

通过gdb ./main.out core,显示了进程由于什么信号导致的coredump(SIGSEGV)?在哪个文件(main.cc)?在哪个函数(myfunc())?具体位置的代码?等等信息。

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1...
Reading symbols from ./main.out...done.
[New LWP 8651]
Core was generated by `./main.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400635 in myfunc (i=1) at main.c:7
7        *(int*)(NULL) = i; /* line 7 */

关于core+gdb更详细的分析方法可以参考《通过core+gdb离线分析》,在分析过程中需要加载动态库可以参考《GDB动态库搜索路径》。

4. coredump使用优化(适用嵌入式)

在/etc/profile中,打开对coredump的配置以及对core_pattern进行配置:

sysctl -p -q -e
ulimit -c unlimited

配置/etc/sysctl.conf文件:

kernel.core_pattern=|/usr/bin/coredump_helper.sh core_%e_%I_%p_sig_%s_time_%t.gz
kernel.core_uses_pid=1

增加处理coredump文件的脚本:

#!/bin/sh

if [ ! -d "/var/coredump" ];then
    mkdir -p /var/coredump
fi
gzip > "/var/coredump/$1"

最终在/var/coredump目录下生成core_<线程名>_<线程ID>_<进程ID>_sig_<信号值>_time_.gz文件。

5. 小结

至此大概总结了,对coredump的设置(ulimit/core_pattern/coredump_filter)?触发coredump的条件(SIG_KERNEL_COREDUMP_MASK )?coredump生成core文件流程(do_coredump())?gdb如何识别core文件(《GDB如何从Coredump文件恢复动态库信息》)?如何通过gdb分析core文件发现问题(gdb->backtrace)?

联系方式:arnoldlu@qq.com

相关