当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做 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:
Signal | Action | Comment | 说明 |
---|---|---|---|
SIGABRT | Core | Abort signal from abort | 来自abort的终止信号 |
SIGBUS | Core | Bus error (bad memory access) | 总线错误(内存访问错误) |
SIGFPE | Core | Floating-point exception | 浮点异常 |
SIGILL | Core | Illegal Instruction | 非法指令 |
SIGIOT | Core | IOT trap. A synonym for SIGABRT | 物联网陷阱。SIGABRT 的同义词 |
SIGQUIT | Core | Quit from keyboard | 从键盘退出 |
SIGSEGV | Core | Invalid memory reference | 无效的内存引用 |
SIGSYS | Core | Bad system call (SVr4) | 错误的系统调用 |
SIGTRAP | Core | Trace/breakpoint trap | 跟踪/断点陷阱 |
SIGUNUSED | Core | Synonymous with SIGSYS | SIGSYS 的同义词 |
SIGXCPU | Core | CPU time limit exceeded (4.2BSD) | 超出 CPU 时间限制 |
SIGXFSZ | Core | File 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文件的情况:
此外,如果使用了 madvise(2) MADV_DONTDUMP 标志,则核心转储可能会排除进程的部分地址空间。
在使用 systemd(1) 作为 init 框架的系统上,核心转储可能会被放在一个由 systemd(1) 决定的位置。有关更多详细信息,请参见下文。
默认情况下,核心转储文件名为 core,但可以设置 /proc/sys/kernel/core_pattern 文件(自 Linux 2.6 和 2.4.21 起)来定义用于命名核心转储文件的模板。模板可以包含 % 说明符,在创建核心文件时,这些说明符会替换为以下值:
模板末尾的单个 % 将从核心文件名中删除,就像 % 后跟除上面列出的字符之外的任何字符的组合一样。模板中的所有其他字符都成为核心文件名的文字部分。模板可能包含“/”字符,这些字符被解释为目录名称的分隔符。生成的核心文件名的最大大小为 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 模板参数添加的可执行文件名称可能会拆分为多个参数。因此核心转储处理程序需要将可执行文件名称作为最后一个参数,并确保它使用空格连接可执行文件名称的所有部分。带有多个空格的可执行文件名称在早期内核中无法正确表示,这意味着核心转储处理程序需要使用机制来查找可执行文件名称。
核心转储不是写入文件,而是作为程序的标准输入提供。请注意以下几点:
当通过管道将核心转储收集到用户空间程序时,收集程序从该进程的 /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(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
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)