linux-coredump文件的生成机制

我理解的就是触发了一些底层的错误,从而产生的当时进程上下文的记录,以用于后续的分析。

这些底层的错误包括:

Image

coredump文件产生的原理

什么情况下进程会收到信号

当进程接收到某些信号而导致异常退出时,就会生成 coredump 文件。

当进程从内核态返回到用户态前,内核会查看进程的信号队列中是否有信号没有处理,如果有就调用 do_signal 内核函数处理信号。

进程从内核态返回到用户态的地方有很多,如从系统调用返回、从硬中断处理程序返回和从进程调度程序返回等

Image

从处理信号到生成coredump文件

do_signal
  get_signal_to_deliver
    // 1. 从进程信号队列中获取一个信号
    signr = dequeue_signal(current, mask, info)
    // 2. 判断是否会生成 coredump 文件的信号
    if (sig_kernel_coredump(signr)) {
        // 3. 调用 do_coredump() 函数生成 coredump 文件
         do_coredump((long)signr, signr, regs)

生成coredump文件过程

do_coredump
    binfmt = current->binfmt; // 当前进程所使用的可执行文件格式(如ELF格式)
    // 1. 判断当前进程可生成的 coredump 文件大小是否受到资源限制
    if (current->signal->rlim[RLIMIT_CORE].rlim_cur < binfmt->min_coredump)
        goto fail_unlock;
    // 2. 生成 coredump 文件名
    ispipe = format_corename(corename, core_pattern, signr);
    // 3. 创建 coredump 文件
    file = filp_open(corename, O_CREAT|2|O_NOFOLLOW|O_LARGEFILE|flag, 0600);
    // 4. 把进程的内存信息写入到 coredump 文件中
    retval = binfmt->core_dump(signr, regs, file);

如何使用core_pattern文件生成文件路径名

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

static int format_corename(struct core_name *cn, struct coredump_params *cprm)
{
    const struct cred *cred = current_cred();
    const char *pat_ptr = core_pattern;
    int ispipe = (*pat_ptr == '|');------------------------------------------|表示通过pipe处理coredump文件。
    int pid_in_pattern = 0;
    int err = 0;

    cn->used = 0;
    cn->corename = NULL;
    if (expand_corename(cn, core_name_size))
        return -ENOMEM;
    cn->corename[0] = '\0';

    if (ispipe)
        ++pat_ptr;

    /* Repeat as long as we have more pattern to process and more output
       space */
    while (*pat_ptr) {
        if (*pat_ptr != '%') {
            err = cn_printf(cn, "%c", *pat_ptr++);
        } else {
            switch (*++pat_ptr) {
            /* single % at the end, drop that */
            case 0:
                goto out;
            /* Double percent, output one percent */
            case '%':
                err = cn_printf(cn, "%c", '%');
                break;
            /* pid */
            case 'p':
                pid_in_pattern = 1;
                err = cn_printf(cn, "%d",
                          task_tgid_vnr(current));-------------------------%p表示记录当前进程组的pid。
                break;
            /* global pid */
            case 'P':-------------------------------------------------------%P表示记录当前进程组的pid。
                err = cn_printf(cn, "%d",
                          task_tgid_nr(current));
                break;
            case 'i':
                err = cn_printf(cn, "%d",
                          task_pid_vnr(current));--------------------------%i表示记录当前线程的pid。
                break;
            case 'I':------------------------------------------------------%I表示记录当前线程的pid。
                err = cn_printf(cn, "%d",
                          task_pid_nr(current));
                break;
            /* uid */
            case 'u':-------------------------------------------------------%u表示当前用户id。
                err = cn_printf(cn, "%u",
                        from_kuid(&init_user_ns,
                              cred->uid));
                break;
            /* gid */
            case 'g':-------------------------------------------------------%g表示group id。
                err = cn_printf(cn, "%u",
                        from_kgid(&init_user_ns,
                              cred->gid));
                break;
            case 'd':
                err = cn_printf(cn, "%d",
                    __get_dumpable(cprm->mm_flags));------------------------%d表示dump的用户类型:SUID_DUMP_DISABLE/SUID_DUMP_USER/SUID_DUMP_ROOT。
                break;
            /* signal that caused the coredump */
            case 's':
                err = cn_printf(cn, "%d",
                        cprm->siginfo->si_signo);----------------------------%s记录产生coredump的信号。
                break;
            /* UNIX time of coredump */
            case 't': {
                time64_t time;

                time = ktime_get_real_seconds();
                err = cn_printf(cn, "%lld", time);---------------------------%t记录产生coredump的时间。
                break;
            }
            /* hostname */
            case 'h':--------------------------------------------------------%h记录主机名。
                down_read(&uts_sem);
                err = cn_esc_printf(cn, "%s",
                          utsname()->nodename);
                up_read(&uts_sem);
                break;
            /* executable */
            case 'e':
                err = cn_esc_printf(cn, "%s", current->comm);----------------%e记录进程中comm名称。
                break;
            case 'E':
                err = cn_print_exe_file(cn);---------------------------------%E记录可执行文件名称。
                break;
            /* core limit size */
            case 'c':
                err = cn_printf(cn, "%lu",
                          rlimit(RLIMIT_CORE));------------------------------%c记录coredump的limit值。
                break;
            default:
                break;
            }
            ++pat_ptr;
        }

        if (err)
            return err;
    }

out:
    if (!ispipe && !pid_in_pattern && core_uses_pid) {
        err = cn_printf(cn, ".%d", task_tgid_vnr(current));
        if (err)
            return err;
    }
    return ispipe;
}

我们大致说明了coredump的生成机制,也就是coredump文件的生成流程。

但是实际上,这个流程并不是coredump独用,而是一套通用流程。

Image

在这个流程中,有信号产生方、信号存储方、信号消费方。

在这三个阶段,其中信号和中断有类似的地方,但是二者并不等价。

信号本质上是在软件层次上对中断机制的一种模拟,其主要有以下几种来源:

  • 程序错误:除零,非法内存访问等。

  • 外部信号:终端 Ctrl-C 产生 SGINT 信号,定时器到期产生SIGALRM等

  • 显式请求:kill函数允许进程发送任何信号给其他进程或进程组。

Image

信号如何被接收的在今天这篇文章并不做介绍,只对信号存储和消费两部分进行说明。

Image

首先每个进程的结构体里都有信号的信息存储:

struct task_struct {
    ...
    int sigpending;
    ...
    struct signal_struct *sig;
    sigset_t blocked;
    struct sigpending pending;
    ...
}

sigpending 表示进程是否有信号需要处理(1表示有,0表示没有);成员 blocked 表示被屏蔽的信息,每个位代表一个被屏蔽的信号;成员 sig 表示信号相应的处理方法,其类型是 struct signal_struct。

在进程从用户态陷入内核态时,内核态完成了相关工作之后,返回用户态之前,都会例行对进程的信号队列进行检查。

这段逻辑是汇编实现的,因为在linux内核中,用户态的切换就是汇编来控制的。【可能有同学对用户态和内核态的切换不太了解,其实用大白话讲就是,只是对cpu的寄存器的赋值进行更换,跟普通的进程切换可能没什么区别,但是同时又有区别,因为用户态和内核态的堆栈是分离开的,这样二者在切换的时候是需要在两个堆栈上进行操作,并且保存上下文信息】

Image

ENTRY(ret_from_sys_call)
 ...
ret_with_reschedule:
 ...
 cmpl $0, sigpending(%ebx)  // 检查进程的sigpending成员是否等于1
 jne signal_return          // 如果是就跳转到 signal_return 处执行
restore_all:
 RESTORE_ALL

 ALIGN
signal_return:
 sti                             // 开启硬件中断
 testl $(VM_MASK),EFLAGS(%esp)
 movl %esp,%eax
 jne v86_signal_return
 xorl %edx,%edx
 call SYMBOL_NAME(do_signal)    // 调用do_signal()函数进行处理
 jmp restore_all

可以看到在内核代码中,指定了哪些信号可以产生dump core文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值