Linux 知:coredump

当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做 Core Dump(中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。

核心转储如何产生

上面说当程序运行过程中异常终止或崩溃时会发生 core dump,但还没说到什么具体的情景程序会发生异常终止或崩溃,例如我们使用 kill -9 命令杀死一个进程会发生 core dump 吗?实验证明是不能的,那么什么情况会产生呢?

Linux 中信号是一种异步事件处理的机制,每种信号都有其对应的默认操作,你可以在 signal(7) 查看 Linux 系统提供的信号以及默认处理。默认操作主要包括:终止进程(Term)、忽略该信号(Ing)、终止进程并发生核心转储(Core)、暂停进程(Stop)、继续运行被暂停的进程(Cont)。如果我们信号均是采用默认操作,那么,以下列出的几种信号,它们在发生时会产生 core dump:

SignalActionComment说明
SIGABRTCoreAbort signal from abort来自abort的终止信号
SIGBUSCoreBus error (bad memory access)总线错误(内存访问错误)
SIGFPECoreFloating-point exception浮点异常
SIGILLCoreIllegal Instruction非法指令
SIGIOTCoreIOT trap. A synonym for SIGABRT物联网陷阱。SIGABRT 的同义词
SIGQUITCoreQuit from keyboard从键盘退出
SIGSEGVCoreInvalid memory reference无效的内存引用
SIGSYSCoreBad system call (SVr4)错误的系统调用
SIGTRAPCoreTrace/breakpoint trap跟踪/断点陷阱
SIGUNUSEDCoreSynonymous with SIGSYSSIGSYS 的同义词
SIGXCPUCoreCPU time limit exceeded (4.2BSD)超出 CPU 时间限制
SIGXFSZCoreFile size limit exceeded (4.2BSD)超出文件大小限制

这就是为什么我们使用 Ctrl+z 来挂起一个进程或者 Ctrl+C 结束一个进程均不会产生 core dump,因为前者会向进程发出 SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process);后者会向进程发出SIGINT 信号,该信号默认操作为终止进程(Terminate Process)。同样上面提到的 kill -9 命令会发出 SIGKILL 命令,该命令默认为终止进程。而如果我们使用 Ctrl+\ 来终止一个进程,会向进程发出 SIGQUIT 信号,默认是会产生 core dump 的。还有其它情景会产生 core dump, 如:程序调用 abort() 函数、访存错误、非法指令等等。

上面的列出的信号只是说明其默认行为是产生core dump的,可实际中并不一定会生成 core dump文件,下面列出一些不会生成core dump文件的情况:

  • 进程没有写入核心文件的权限。(默认情况下,核心文件称为 core 或 core.pid,其中 pid 是转储核心的进程的 ID,并在当前工作目录中创建。有关命名的详细信息,请参见下文。)如果出现以下情况,则写入核心文件失败:要创建的目录不可写,或者如果存在同名文件且不可写或不是常规文件(例如,它是目录或符号链接)。
  • 一个(可写的、常规的)文件与用于核心转储的同名文件已经存在,但有多个硬链接到该文件。
  • 将创建核心转储文件的文件系统已满;或已用完 inode;或以只读方式安装;或者用户已达到文件系统的配额。
  • 要创建核心转储文件的目录不存在。
  • 进程的 RLIMIT_CORE(核心文件大小)或 RLIMIT_FSIZE(文件大小)资源限制设置为零;请参阅 getrlimit(2) 和 shell 的 ulimit 命令的文档(csh(1) 中的限制)。
  • 进程正在执行的二进制文件没有启用读取权限。(这是一种安全措施,可确保内容不可读的可执行文件不会产生可能可读的核心转储,其中包含可执行文件的映像。)
  • 进程正在执行一个set-user-ID(set-group-ID)程序,该程序被除进程的真实用户(组)ID之外的用户(组)拥有,或者进程正在执行具有文件能力(capabilities)的程序(请参阅 capabilities(7))。 (但是,请参阅 prctl(2) PR_SET_DUMPABLE 操作的说明,以及 proc(5) 中 /proc/sys/fs/suid_dumpable 文件的说明)
  • /proc/sys/kernel/core_pattern 为空且 /proc/sys/kernel/core_uses_pid 包含值 0。(这些文件如下所述。)请注意,如果 /proc/sys/kernel/core_pattern 为空且 /proc/ sys/kernel/core_uses_pid 包含值 1,核心转储文件将具有 .pid 形式的名称,除非使用 ls(1) -a 选项,否则此类文件将被隐藏。
  • (自 Linux 3.7 起)内核配置时没有配置 CONFIG_COREDUMP 选项。

此外,如果使用了 madvise(2) MADV_DONTDUMP 标志,则核心转储可能会排除进程的部分地址空间。

在使用 systemd(1) 作为 init 框架的系统上,核心转储可能会被放在一个由 systemd(1) 决定的位置。有关更多详细信息,请参见下文。

核心转储文件的命名

默认情况下,核心转储文件名为 core,但可以设置 /proc/sys/kernel/core_pattern 文件(自 Linux 2.6 和 2.4.21 起)来定义用于命名核心转储文件的模板。模板可以包含 % 说明符,在创建核心文件时,这些说明符会替换为以下值:

  • %% 单个 % 字符。
  • %c 崩溃进程的核心文件大小软资源限制(自 Linux 2.6.24)。
  • %d 转储模式——与 prctl(2) PR_GET_DUMPABLE 返回的值相同(自 Linux 3.7 起)。
  • %e 进程或线程的 comm 值,通常与可执行文件名相同(不带路径前缀,并截断为最多 15 个字符),但可能已被修改为不同的内容;参见 proc(5) 中对 /proc/[pid]/comm 和 /proc/[pid]/task/[tid]/comm 的讨论。
  • %E 可执行文件的路径名,斜线 (’/’) 替换为感叹号 (’!’)(自 Linux 3.0 起)。
  • %g 转储进程的数字化真实 GID。
  • %h 主机名(与 uname(2) 返回的节点名相同)。
  • %i 触发核心转储的线程的 TID,如线程所在的 PID 命名空间中所见(自 Linux 3.18 起)。
  • %I 触发核心转储的线程的 TID,如初始 PID 命名空间中所示(自 Linux 3.18 起)。
  • %p 转储进程的 PID,如进程所在的 PID 命名空间中所见。
  • %P 转储进程的 PID,如初始 PID 命名空间中所示(自 Linux 3.12 起)。
  • %s 导致转储的信号编号。
  • %t 转储时间,表示为自纪元以来的秒数,1970-01-01 00:00:00 +0000 (UTC)。
  • %u 转储进程的数字化真实 UID。

模板末尾的单个 % 将从核心文件名中删除,就像 % 后跟除上面列出的字符之外的任何字符的组合一样。模板中的所有其他字符都成为核心文件名的文字部分。模板可能包含“/”字符,这些字符被解释为目录名称的分隔符。生成的核心文件名的最大大小为 128 字节(在 2.6.19 之前的内核中为 64 字节)。此文件中的默认值为“core”。为了向后兼容,如果 /proc/sys/kernel/core_pattern 不包含 %p 并且 /proc/sys/kernel/core_uses_pid(见下文)非零,则 .PID 将附加到核心文件名。

路径根据对崩溃过程有效的设置进行解释。这意味着崩溃进程的挂载命名空间(请参阅 mount_namespaces(7))、其当前工作目录(通过 getcwd(2) 找到)及其根目录(请参阅 chroot(2))。

从 2.4 版本开始,Linux 还提供了一种更原始​​的方法来控制核心转储文件的名称。如果 /proc/sys/kernel/core_uses_pid 文件包含值 0,则核心转储文件简单地命名为 core。如果此文件包含非零值,则核心转储文件以 core.PID 形式的名称包含进程 ID。

从 Linux 3.6 开始,如果 /proc/sys/fs/suid_dumpable 设置为 2(“suidsafe”),则模式必须是绝对路径名(以前导“/”字符开头)或管道,如下定义。

核心转储传送到程序

从内核 2.6.19 开始,Linux 支持 /proc/sys/kernel/core_pattern 文件的替代语法。如果此文件的第一个字符是管道符号 (|),则该行的其余部分将被解释为要执行的用户空间程序(或脚本)的命令行。

从内核 5.3.0 开始,在扩展模板参数之前,管道模板在空格上被拆分成一个参数列表。在早期的内核中,模板参数首先被扩展,结果字符串在空格上被分割成一个参数列表。这意味着在早期内核中,由 %e 和 %E 模板参数添加的可执行文件名称可能会拆分为多个参数。因此核心转储处理程序需要将可执行文件名称作为最后一个参数,并确保它使用空格连接可执行文件名称的所有部分。带有多个空格的可执行文件名称在早期内核中无法正确表示,这意味着核心转储处理程序需要使用机制来查找可执行文件名称。

核心转储不是写入文件,而是作为程序的标准输入提供。请注意以下几点:

  • 程序必须使用绝对路径名(或相对于根目录的路径名,/)指定,并且必须紧跟在“|”字符之后。
  • 命令行参数可以包含上面列出的任何 % 说明符。例如,要传递正在转储的进程的 PID,请在参数中指定 %p。
  • 为运行程序而创建的进程以用户和组 root 身份运行。
  • 以 root 身份运行不会提供任何特殊的安全绕过。也就是说,LSM(例如,SELinux)仍然处于活动状态,并且可能会阻止处理程序通过 /proc/[pid] 访问有关崩溃进程的详细信息。
  • 程序路径名是根据初始安装命名空间解释的,因为它总是在那里执行。它不受崩溃进程的设置(例如,根目录、挂载命名空间、当前工作目录)的影响。
  • 进程在初始命名空间(PID、mount、user 等)中运行,而不是在崩溃进程的命名空间中运行。如果需要,可以使用诸如 %P 之类的说明符来找到正确的 /proc/[pid] 目录并探测/输入崩溃进程的命名空间。
  • 该进程以其当前工作目录作为根目录开始。如果需要,可以使用 %P 说明符提供的值更改转储进程的工作目录,以通过 /proc/[pid]/cwd 更改转储进程的位置。
  • 可以向程序提供命令行参数(自 Linux 2.6.24 起),以空格分隔(最多 128 字节的总行长)。
  • RLIMIT_CORE 限制不适用于通过此机制传送到程序的核心转储。

/proc/sys/kernel/core_pipe_limit

当通过管道将核心转储收集到用户空间程序时,收集程序从该进程的 /proc/[pid] 目录收集有关崩溃进程的数据可能很有用。为了安全地执行此操作,内核必须等待收集核心转储的程序退出,以免过早删除崩溃进程的 /proc/[pid] 文件。这反过来又产生了这样一种可能性,即行为不当的收集程序可以通过简单地永不退出来阻止对崩溃进程的收割。

从 Linux 2.6.32 开始,可以使用 /proc/sys/kernel/core_pipe_limit 来防范这种可能性。该文件中的值定义了可以并行将多少并发崩溃进程通过管道传输到用户空间程序。如果超过此值,则在内核日志中记录高于此值的那些崩溃进程,并跳过它们的核心转储。

此文件中的值 0 是特殊的。它表示可以并行捕获无限进程,但不会发生等待(即,收集程序不能保证访问 /proc/)。此文件的默认值为 0。

控制将哪些映射写入核心转储

从内核 2.6.23 开始,Linux 特有的 /proc/[pid]/coredump_filter 文件可用于控制在对具有相应进程的进程执行核心转储的情况下将哪些内存段写入核心转储文件ID。

文件中的值是内存映射类型的位掩码(请参阅 mmap(2))。如果掩码中设置了一个位,则转储相应类型的内存映射;否则它们不会被转储。此文件中的位具有以下含义:

bit 0 转储匿名私有映射。
bit 1 转储匿名共享映射。
bit 2 转储文件支持的私有映射。
bit 3 转储文件支持的共享映射。
bit 4(自 Linux 2.6.24 起)转储 ELF 标头。
bit 5(自 Linux 2.6.28 起)转储私有大页面。
bit 6 (自 Linux 2.6.28) 转储共享大页面。
bit 7(自 Linux 4.4 起)转储私有 DAX 页面。
bit 8(自 Linux 4.4 起)转储共享 DAX 页面。

默认情况下,设置了以下位:0、1、4(如果启用了 CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS 内核配置选项)和 5。可以在引导时使用 coredump_filter 引导选项修改此默认值。

该文件的值以十六进制显示。 (因此默认值显示为 33。)

无论 coredump_filter 值如何,内存映射 I/O 页面(例如帧缓冲区)都不会转储,并且始终转储虚拟 DSO (vdso(7)) 页面。

通过 fork(2) 创建的子进程继承其父进程的 coredump_filter 值; coredump_filter 值在 execve(2) 中保留。

在运行程序之前在父 shell 中设置 coredump_filter 会很有用,例如:

$ echo 0x7 > /proc/self/coredump_filter
$ ./some_program

仅当使用 CONFIG_ELF_CORE 配置选项构建内核时才提供此文件。

核心转储和systemd

在使用 systemd(1) 作为 init 框架的系统上,核心转储可能会被放在一个由 systemd(1) 决定的位置。为此,systemd(1) 使用了 core_pattern 功能,该功能允许将核心转储传输到程序。可以通过检查核心转储是否通过管道传输到 systemd-coredump(8) 程序来验证这一点:

$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

在这种情况下,核心转储将放置在为 systemd-coredump(8) 配置的位置,通常是 /var/lib/systemd/coredump/ 目录中的 lz4(1) 压缩文件。可以使用 coredumpctl(1) 列出 systemd-coredump(8) 记录的核心转储:

$ coredumpctl list | tail -5
Wed 2017-10-11 22:25:30 CEST  2748 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:29:10 CEST  2716 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:30:50 CEST  2767 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:37:40 CEST  2918 1000 1000 3 present  /usr/bin/cat
Thu 2017-10-12 08:13:07 CEST  2955 1000 1000 3 present  /usr/bin/cat

为每个核心转储显示的信息包括转储的日期和时间、转储进程的 PID、UID 和 GID、导致核心转储的信号编号以及被转储进程执行的可执行文件的路径名。 coredumpctl(1) 的各种选项允许将指定的 coredump 文件从 systemd(1) 位置提取到指定的文件中。例如,要将上面显示的 PID 2955 的核心转储提取到当前目录中名为 core 的文件中,可以使用:

$ coredumpctl dump 2955 -o core

有关更多详细信息,请参阅 coredumpctl(1) 手册页。

要(永久)禁用归档核心转储的 systemd(1) 机制,恢复到更像传统 Linux 行为的行为,可以使用以下内容为 systemd(1) 机制设置覆盖:

# echo "kernel.core_pattern=core.%p" > \
               /etc/sysctl.d/50-coredump.conf
# /lib/systemd/systemd-sysctl

也可以使用如下命令临时(即,直到下次重新启动)更改 core_pattern 设置(这会导致核心转储文件的名称包含可执行文件名称以及触发核心转储的信号编号):

# sysctl -w kernel.core_pattern="%e-%s.core"

测试

下面构造一个无效内存引用的测试用例 test.c,如下所示,访问NULL指针:

#include <stdio.h>

int main (int argc, char *argv[])
{
  int *p = NULL;
  *p = 0;

  return 0;
}

编译执行如下:

maminjie@fedora ~/w/g/coredump> gcc test.c
maminjie@fedora ~/w/g/coredump> ./a.out
fish: Job 1, './a.out' terminated by signal SIGSEGV (Address boundary error)
maminjie@fedora ~/w/g/coredump [SIGSEGV]> ls
a.out  test.c

执行后发生信号SIGSEGV,但在当前目录下并没有生成core文件。

1)查看core文件大小限制

maminjie@fedora ~/w/g/coredump> ulimit -a
Maximum size of core files created                           (kB, -c) unlimited
Maximum size of a process’s data segment                     (kB, -d) unlimited
Maximum size of files created by the shell                   (kB, -f) unlimited
Maximum size that may be locked into memory                  (kB, -l) 64
Maximum resident set size                                    (kB, -m) unlimited
Maximum number of open file descriptors                          (-n) 1024
Maximum stack size                                           (kB, -s) 8192
Maximum amount of cpu time in seconds                   (seconds, -t) unlimited
Maximum number of processes available to a single user           (-u) 7638
Maximum amount of virtual memory available to the shell      (kB, -v) unlimited
maminjie@fedora ~/w/g/coredump> ulimit -c
unlimited

大小不受限制。

2)查看/proc/sys/kernel/core_pattern

maminjie@fedora ~/w/g/coredump> cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h

本系统使用的是systemd作为init框架。

3)使用coredumpctl查看core信息

maminjie@fedora ~/w/g/coredump> coredumpctl list
TIME                           PID  UID  GID SIG     COREFILE EXE                                         SIZE
Wed 2021-06-02 00:08:54 CST   1093  983  978 SIGSEGV missing  /usr/libexec/gsd-usb-protection              n/a
Wed 2021-06-02 00:09:21 CST   1365  983  978 SIGSEGV missing  /usr/libexec/gsd-usb-protection              n/a
...
Sat 2021-10-16 00:39:55 CST  37143 1000 1000 SIGSEGV present  /home/maminjie/work/gdb/access/a.out       15.3K
Sat 2021-10-16 23:04:43 CST  40826 1000 1000 SIGABRT present  /home/maminjie/work/gdb/a.out              15.8K
Mon 2021-10-18 22:26:28 CST   1592 1000 1000 SIGSEGV present  /home/maminjie/work/gdb/coredump/a.out     15.3K

4)提取core文件

maminjie@fedora ~/w/g/coredump> coredumpctl dump 1592 -o core
           PID: 1592 (a.out)
           UID: 1000 (maminjie)
           GID: 1000 (maminjie)
        Signal: 11 (SEGV)
     Timestamp: Mon 2021-10-18 22:26:28 CST (13min ago)
  Command Line: ./a.out
    Executable: /home/maminjie/work/gdb/coredump/a.out
 Control Group: /user.slice/user-1000.slice/session-2.scope
          Unit: session-2.scope
         Slice: user-1000.slice
       Session: 2
     Owner UID: 1000 (maminjie)
       Boot ID: e5eca14d1e3c4ed6b4de8873e1e243bf
    Machine ID: d16e090c0ec94531ac731f29501ddf68
      Hostname: fedora
       Storage: /var/lib/systemd/coredump/core.a\x2eout.1000.e5eca14d1e3c4ed6b4de8873e1e243bf.1592.1634567188000000.zst (present)
     Disk Size: 15.3K
       Message: Process 1592 (a.out) of user 1000 dumped core.

                Stack trace of thread 1592:
                #0  0x000000000040111d n/a (/home/maminjie/work/gdb/coredump/a.out + 0x111d)
                #1  0x00007fce41f49b75 __libc_start_main (libc.so.6 + 0x27b75)
                #2  0x000000000040104e n/a (/home/maminjie/work/gdb/coredump/a.out + 0x104e)
maminjie@fedora ~/w/g/coredump> ls
a.out  core  test.c

5)通过gdb调试core文件

maminjie@fedora ~/w/g/coredump> gdb a.out core
GNU gdb (GDB) Fedora 10.1-13.fc34
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(No debugging symbols found in a.out)
[New LWP 1592]
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000000000040111d in main ()
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.33-5.fc34.x86_64
(gdb) bt
#0  0x000000000040111d in main ()
(gdb)

参考

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值