猿创征文|【Linux Debug】有了core-dump,Bug一举拿下!

发布于:2022-11-09 ⋅ 阅读:(12) ⋅ 点赞:(0) ⋅ 评论:(0)
img
个人主页:董哥聊技术
我是董哥,嵌入式领域新星创作者
创作理念:专注分享高质量嵌入式文章,让大家读有所得!
img


在这里插入图片描述

1、core-dump文件

首先,我们来明白core-dump文件是什么?保存了哪些信息?主要作用是什么?

core-dump文件,又称为核心转储,是操作系统在进程收到某些信号终止运行时,将此时进程的地址空间、进程状态以及其他信息写入到一个文件中,这个文件就是core-dump文件,其主要是为了方便开发人员调试,定位问题。

 

2、core-dump如何生成

core-dump文件是操作系统生成的,虽然是操作系统的事情,但是也得有个开关来把控吧!

那么如何生成core-dump文件呢?

 

2.1 打开core-dump开关

我们在命令行输入ulimit -a来查看相关信息:

dong@ubuntu:~$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 18811
max locked memory           (kbytes, -l) 610792
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) 18811
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited

注意core file size,该项表示了core-dump文件的大小限制,为0表示关闭!

我们输入ulimit -c unlimited,来将其大小设置为无限制,即可打开

 

2.2 路径设置

一般默认情况下,生成的core-dump文件在我们进程的当前目录下,要是我们想自定义保存路径该怎么办?

修改 /proc/sys/kernel/core_pattern文件!

例如:

echo "/data/coredump/corefile-%e-%p-%s-%t" > /proc/sys/kernel/core_pattern

/data/coredump/:为要设置的路径信息,如果不加路径信息,默认生成在进程目录下。

corefile-%e-%p-%s-%t:为设置的文件名称,这些特殊格式的意义如下:

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

#完整如下:
Naming of core dump files
       By default, a core dump file is named core, but the /proc/sys/kernel/core_pattern file (since  Linux  2.6  and
       2.4.21)  can  be  set  to  define a template that is used to name core dump files.  The template can contain %
       specifiers which are substituted by the following values when a core file is created:

           %%  a single % character
           %c  core file size soft resource limit of crashing process (since Linux 2.6.24)
           %d  dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE (since Linux 3.7)
           %e  executable filename (without path prefix)
           %E  pathname of executable, with slashes ('/') replaced by exclamation marks ('!') (since Linux 3.0).
           %g  (numeric) real GID of dumped process
           %h  hostname (same as nodename returned by uname(2))
           %i  TID of thread that triggered core dump, as seen in the PID  namespace  in  which  the  thread  resides
               (since Linux 3.18)
           %I  TID of thread that triggered core dump, as seen in the initial PID namespace (since Linux 3.18)
           %p  PID of dumped process, as seen in the PID namespace in which the process resides
           %P  PID of dumped process, as seen in the initial PID namespace (since Linux 3.12)
           %s  number of signal causing dump
           %t  time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
           %u  (numeric) real UID of dumped process

设置完成后,我们输入cat /proc/sys/kernel/core_pattern来查看文件是否设置成功!

到此,我们的core-dump就配置完成了,当发生崩溃或者异常终止的时候,就自动生成core-dump文件了。

 

2.3 特殊信息配置

这一部分,发现很少有人描述到,再此也记录一下!

core-dump信息还可以定制,在每个进程下,会有coredump_filter的一个文件(/proc/<pid>/coredump_filter),该文件会对core-dump生成的信息进行定制!

dong@ubuntu:~$ cat /proc/2395/coredump_filter 
00000033

一般coredump_filter的值为0x33,该值对应的信息如下:

  - (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时会将所有anonymous内存ELF头页面hugetlb private memory内容保存。

 

3、什么情况下产生core-dump文件?

发生core-dump一般都是在进程收到某个信号的时候,那么到底收到什么信号能够触发core-dump文件生成呢?

Linux上现在大概有60多个信号,可以使用 kill -l 命令全部列出来。

dong@ubuntu:~/WorkSpace/Donge_Programs/Donge_Demo/build$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

我们可以输入man 7 signal,来查看更加详细的SIGNAL信号信息!

       Signal      Standard   Action   Comment
       ────────────────────────────────────────────────────────────────────────
       SIGABRT      P1990      Core    Abort signal from abort(3)
       SIGALRM      P1990      Term    Timer signal from alarm(2)
       SIGBUS       P2001      Core    Bus error (bad memory access)
       SIGCHLD      P1990      Ign     Child stopped or terminated
       SIGCLD         -        Ign     A synonym for SIGCHLD
       SIGCONT      P1990      Cont    Continue if stopped
       SIGEMT         -        Term    Emulator trap
       SIGFPE       P1990      Core    Floating-point exception
       SIGHUP       P1990      Term    Hangup detected on controlling terminal
                                       or death of controlling process
       SIGILL       P1990      Core    Illegal Instruction
       SIGINFO        -                A synonym for SIGPWR
       SIGINT       P1990      Term    Interrupt from keyboard
       SIGIO          -        Term    I/O now possible (4.2BSD)
       SIGIOT         -        Core    IOT trap. A synonym for SIGABRT
       SIGKILL      P1990      Term    Kill signal
       SIGLOST        -        Term    File lock lost (unused)
       SIGPIPE      P1990      Term    Broken pipe: write to pipe with no
                                       readers; see pipe(7)
       SIGPOLL      P2001      Term    Pollable event (Sys V);
                                       synonym for SIGIO
       SIGPROF      P2001      Term    Profiling timer expired
       SIGPWR         -        Term    Power failure (System V)
       SIGQUIT      P1990      Core    Quit from keyboard
       SIGSEGV      P1990      Core    Invalid memory reference

       SIGSTKFLT      -        Term    Stack fault on coprocessor (unused)
       SIGSTOP      P1990      Stop    Stop process
       SIGTSTP      P1990      Stop    Stop typed at terminal
       SIGSYS       P2001      Core    Bad system call (SVr4);
                                       see also seccomp(2)
       SIGTERM      P1990      Term    Termination signal
       SIGTRAP      P2001      Core    Trace/breakpoint trap
       SIGTTIN      P1990      Stop    Terminal input for background process
       SIGTTOU      P1990      Stop    Terminal output for background process
       SIGUNUSED      -        Core    Synonymous with SIGSYS
       SIGURG       P2001      Ign     Urgent condition on socket (4.2BSD)
       SIGUSR1      P1990      Term    User-defined signal 1
       SIGUSR2      P1990      Term    User-defined signal 2
       SIGVTALRM    P2001      Term    Virtual alarm clock (4.2BSD)
       SIGXCPU      P2001      Core    CPU time limit exceeded (4.2BSD);
                                       see setrlimit(2)
       SIGXFSZ      P2001      Core    File size limit exceeded (4.2BSD);
                                       see setrlimit(2)
       SIGWINCH       -        Ign     Window resize signal (4.3BSD, Sun)

       The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

我们可以看Action这一列,该列有几项,分别为TermCoreIgnStopCont

  • Term:terminal,终止程序!
  • Core:dump core,核心转储!
  • Ign:ignore,忽略信号
  • Stop:stop,停止进程
  • Cont:continue,继续执行进程

 

4、触发core-dump的信号处理流程

由上文可知,操作系统在接收到某个信号后,进而生成core-dump文件,那么该信号处理流程是怎么样的呢?

4.1 信号处理逻辑

img

我们把这个过程拆分成信号的接收、检测、处理三个步骤。

  • 信号接收接收信号的任务由内核代理,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

  • 信号检测:进程陷入内核态后,有两种场景会对信号进行检测:

    • 进程从内核态返回到用户态前进行信号检测
    • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
  • 信号处理:信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。接下来进程返回到用户态中,执行相应的信号处理函数。信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。

 

4.2 信号处理源码分析

img

进程从内核态返回到用户态的地方有很多,如 从系统调用返回从硬中断处理程序返回从进程调度程序返回 等。上图主要通过 从进程调度程序返回 作为示例,来展示内核是怎么生成 coredump 文件的。

 

4.2.1 do_signal()

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

static void fastcall do_signal(struct pt_regs *regs)
{
    siginfo_t info;
    int signr;
    struct k_sigaction ka;
    sigset_t *oldset;
 
    ...
    signr = get_signal_to_deliver(&info, &ka, regs, NULL);
    ...
}

上面代码去掉了很多与生成 coredump 文件无关的逻辑,最终我们可以看到,do_signal 函数主要调用 get_signal_to_deliver 内核函数来进行进一步的处理。

 

4.2.2 get_signal_to_deliver

get_signal_to_deliver 内核函数的主要工作是从进程的信号队列中获取一个信号,然后根据信号的类型来进行不同的操作。我们主要关注生成 coredump 文件相关的逻辑,如下代码:

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
                          struct pt_regs *regs, void *cookie)
{
    sigset_t *mask = &current->blocked;
    int signr = 0;
 
    ...
    for (;;) {
        ...
        // 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 文件无关的逻辑,最后我们可以看到 get_signal_to_deliver 函数主要完成三个工作:

  • 调用 dequeue_signal 函数从进程的信号队列中获取一个信号。
  • 调用 sig_kernel_coredump 函数判断信号是否会生成 coredump 文件。
  • 如果信号会生成 coredump 文件,那么就调用 do_coredump 函数生成 coredump 文件。

 

4.2.3 do_coredump

如果要处理的信号会触发生成 coredump 文件,那么内核就会调用 do_coredump 函数来生成 coredump 文件。do_coredump 函数的实现如下:

int do_coredump(long signr, int exit_code, struct pt_regs *regs)
{
    char corename[CORENAME_MAX_SIZE + 1];
    struct mm_struct *mm = current->mm;
    struct linux_binfmt *binfmt;
    struct inode *inode;
    struct file *file;
    int retval = 0;
    int fsuid = current->fsuid;
    int flag = 0;
    int ispipe = 0;
 
    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);
 
fail_unlock:
    ...
    return retval;
}

经过代码精简后,最终可以看到 do_coredump 函数完成四个工作:

  • 判断当前进程可生成的 coredump 文件大小是否受到资源限制。
  • 如果不受限制,那么调用 format_corename 函数生成 coredump 文件的文件名。
  • 接着调用 filp_open 函数创建 coredump 文件。
  • 最后根据当前进程所使用的可执行文件格式来选择相应的填充方法来填充 coredump 文件的内容,对于 ELF文件格式 使用的是 elf_core_dump 方法。

elf_core_dump 方法的主要工作是:把进程的内存信息和内容写入到 coredump 文件中,并且以 ELF文件格式 作为 coredump 文件的存储格式。有兴趣的可以自行阅读 elf_core_dump 方法的代码,这里就不作进一步的解说了。

 

补充:

信号处理线程:信号可以发给整个进程,也可以发给特定线程;发给整个进程的信号,随机选取一个线程进行执行;发给特定线程的信号,只能有特定的线程负责处理。一些信号如果是某些线程代码的直接执行而引发,那么只能由特定的线程负责执行,例如SIGILL, SIGSEG.

A signal may be directed to either the process as a whole or to a specific thread. A signal is thread-directed if it is generated as the direct result of the execution of a specific hardware instruction within the context of the thread (SIGBUS, SIGFPE, SIGILL, and SIGSEGV)

应用例子,比如进程触发了SIGSEG异常,我在异常处理函数中进行了while循环,最终仍然是卡死在异常线程的,此时可以看一下CPU占用率,哪个最高就是哪个线程触发的!

 

4.3 生产环境要不要打开core-dump限制

最后,我们来讨论一下在生产环境应不应该打开 coredump 功能。

最近遇过在生产环境打开 coredump 功能而导致的事故,故事如下:

最近我们的应用程序概率性极低出现SIGSEGV段错误,无论是DGB仿真还是排查代码,都不能直接定位到该问题所在。

为了能够抓到现场瞬间,我们就默认把coredump选项打开了,以便能够捕获到core-dump文件。

由于每次不单单只是SIGSEGV会引发core-dump文件,其他某些信号触发仍然会生成core-dump文件,这样随着一段时间过去,引发了一些OOM内存溢出的问题,或者磁盘变为只读,经过一些排查,发现core-dump文件异常的大,一个正常的文件都到300-500M,直接把磁盘撑爆了!!!

所以,经过上面的事故,我建议大家不要在生成环境打开 coredump 功能。

建议是拿出来一台机器特殊标记,打开 coredump 功能,然后模拟发生异常的情况来进行排查。生成 coredump 文件后,可以使用 GDB 来进行调试。

 

5、文章参考

[1]:https://zhuanlan.zhihu.com/p/240633280

[2]:https://blog.csdn.net/zhouhailiang1991/article/details/119172697

img

点赞+关注,永远不迷路

img