Contents

在Linux中配置和管理核心转储

1. 概述

在本教程中,我们将了解如何管理和配置核心 转储。我们将研究kernel.core_pattern,然后我们将继续使用coredumpctl

2. 简介

核心转储是在程序崩溃后由 Linux 内核自动生成的文件。该文件包含应用程序崩溃时的内存、寄存器值和调用堆栈

3. 使用信号生成核心转储

在本节中,我们将学习如何终止程序并强制它生成核心转储。为此,我们将使用kill 命令,它使用信号 来终止应用程序。这些信号产生核心转储。

如果我们查看 signal 的手册页提供的表格, 我们可以看到一个信号列表,这些信号使用 core dump 终止程序。这些信号具有标识为CoreAction

 信号标准动作注释
       ────────────────────────────────────────────────────────────────────────
 SIGABRT P1990 来自 abort(3) 的核心中止信号
 SIGALRM P1990 来自警报的 Term Timer 信号 (2)

例如,让我们将sleep 用作无限期运行的程序:

$ sleep 500
[1] 5464
$ kill -s SIGTRAP $(pgrep sleep)
[1]+  Trace/breakpoint trap (core dumped) sleep 500

我们可以看到“core dumped”的消息表明一个成功的核心转储。我们还注意到“Trace/breakpoint trap”,它表示SIGTRAP的信号

现在我们已经有了这个框架,让我们看看如何配置核心转储。

4. 配置核心转储

有两种配置核心转储的方法。一种是通过管道传递核心转储,另一种是将其存储在文件中。

**主要配置参数是kernel.core_pattern。*这适用于基于文件和基于管道的核心转储。除了这个配置参数,基于文件的转储对它们有大小限制。我们可以使用ulimit *配置这个大小。

我们将在以下部分介绍这两种配置类型。

4.1. 将核心转储重定向到管道

让我们看看如何配置我们的系统以通过管道生成核心转储。首先,我们需要一个示例程序来从管道中提取核心转储。之后,我们将配置内核以将程序名称作为参数和核心转储提供给我们的程序。

让我们编写一个程序,它只会在崩溃进程处于睡眠状态时生成核心转储:

#!/usr/bin/python2.7
# Filename: /tmp/core_dump_example.py
import sys
# Expect sys.argv to have %e configured in kernel.core_pattern
process_filename = sys.argv[1]
if process_filename == "sleep":
    with open("/tmp/sleep_core_dump", "wb") as core_dump:
        core_contents = bytearray(sys.stdin.read())
        core_dump.write(core_contents)

在这里,我们注意到程序检查了第一个参数,并且仅在包含sleep时才输出核心转储。让我们将其存储在*/tmp/core_dump_example.py*下并赋予其可执行权限。

现在,我们希望操作系统在生成核心转储时调用我们的脚本。通过阅读core 的手册页,我们可以通过使用*sysctl 配置kernel.core_patttern*属性 来实现 :

$ sudo sysctl -w kernel.core_pattern="|/tmp/core_dump_example.py %e"

模式开头的管道表示操作系统应该通过标准输入将核心转储的内容传递给我们的脚本。

注意末尾的*%e 。%e*是一个扩展为崩溃应用程序进程名称的模板。core 手册页中描述了更多可用的模板。

让我们尝试创建一个核心转储:

$ sleep 500 &
[1] 8828
$ kill -s SIGTRAP $(pgrep sleep)
[1]+  Trace/breakpoint trap (core dumped) sleep 500

让我们检查一下我们使用 python 脚本创建的文件的签名:

$ file /tmp/sleep_core_dump
/tmp/sleep_core_dump: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'sleep 500', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/usr/bin/sleep', platform: 'x86_64'

通过使用 core dump 上的*file ,我们可以立即看到崩溃的程序是/usr/bin/sleep*。它还向我们展示了其他信息,例如启动此过程的 UID。

4.2. 将核心转储重定向到文件

接下来,让我们配置我们的系统以生成核心转储文件。为此,我们将kernel.core_pattern设置为我们想要的文件名使用核心 手册页中的模板,我们可以修饰核心转储文件名。

首先,让我们设置核心转储文件名:

$ sudo sysctl -w kernel.core_pattern="/tmp/%e_core_dump.%p"

睡眠应用程序崩溃时,我们希望模式为sleep_core_dump.pid的文件出现在*/tmp下。其中%e是程序名称,%p*是程序的 PID。

请注意,我们可以提供文件名而不是绝对路径。这将在崩溃进程的当前工作目录中创建一个核心转储文件。

接下来,我们需要检查使用ulimit 施加的任何限制。核心转储文件默认设置了限制ulimit设置的这些限制不会影响基于管道的核心转储处理程序。

核心转储大小的单位是。让我们找出每个块有多少字节:

$ stat -fc %s .
4096

使用每个块 4096 字节,让我们将限制设置为 5 MB,因为我们不希望示例生成大于 5 MB 的核心转储。这可以计算为nblocks = desired_limit / block_size其中 desired_limit 和 block_size 都以字节为单位。5 MB 相当于 1280 个块 = (5 * 1024 * 1024) / 4096。

默认情况下,核心转储的硬限制设置为0。要设置限制,我们必须将以下两行添加到*/etc/security/limits.conf *:

blogdemo_user hard core 1280
blogdemo_user soft core 1280

硬限制是系统范围的限制,软限制是基于用户的限制。软限制应小于相应的硬限制。在此之后我们需要重新启动。

让我们检查重启后核心转储文件的大小限制:

$ ulimit -c
1280

太好了,已经生效了。让我们尝试创建一个核心转储:

$ sleep 500 &
[1] 9183
$ kill -s SIGTRAP $(pgrep sleep)
[1]+  Trace/breakpoint trap (core dumped) sleep 500
$ ls /tmp/*_core_*
-rw------- 1 user user 372K Jun 26 23:31 /tmp/sleep_core_dump.1780

我们已经创建了一个具有所需模式的核心转储文件。

5. 为正在运行的进程生成核心转储

有时为正在运行的进程生成核心转储可能很有用。GDB 可以捕获正在运行的进程的核心转储,但它还附带一个名为*gcore 的实用程序。* gcore是一个命令行实用程序,可以捕获正在运行的进程的核心转储。**

让我们尝试使用gcore捕获核心转储:

$ sleep 500 &
[1] 3000
$ sudo gcore -o sleep 3000
0x00007f975eee630e in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
warning: target file /proc/3000/cmdline contained unexpected null characters
warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000.
Saved corefile sleep.3000
[Inferior 1 (process 3000) detached]

我们可以看到启动了一个sleep进程,PID 为 3000。之后,启动了gcore并将其自身附加到sleep进程。结果,gcore随后生成了一个sleep.3000的核心转储文件并自行分离。gcore从进程中分离出来后,进程将愉快地继续运行而不受影响。

注意:gcore需要sudo才能附加到进程。我们可以使用sysctlkernel.yama.ptrace_scope设置为0。这将允许gcore附加到没有sudo 的进程。但是,请注意,这应该谨慎使用,因为它存在安全风险。任何进程都可以使用*ptrace *系统调用并检查任何程序内部结构。

6. coredumpctl介绍

在本节中,我们将介绍一个名为coredumpctl 的实用程序。与手动配置核心转储相比,coredumpctl自动管理核心转储。** coredumpctl记录核心转储本身并维护崩溃历史记录。**

在以下部分中,我们假设系统上已经安装了coredumpctl

6.1. 配置coredumpctl

coredumpctl附带一个名为**systemd-coredump 的服务。**这是一项获取核心转储的服务,然后对其进行处理以从中提取元数据。然后它将此信息存储在/var/lib/systemd/coredump/ 下。

我们可以通过检查kernel.core_pattern来检查此服务是否已配置:

$ sysctl -n kernel.core_pattern
|/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h

我们已经确认kernel.core_pattern设置为使用systemd-coredump。这告诉内核将与核心转储相关的任何信息传递给systemd-coredump

要尝试coredumpctl,我们首先需要生成一个新的核心转储:

$ sleep 500 &
[1] 2826
$ kill -s SIGTRAP $(pgrep sleep)
[1]+  Trace/breakpoint trap (core dumped) sleep 500
$ coredumpctl
TIME                            PID   UID   GID SIG COREFILE  EXE
Sun 2020-06-28 18:52:59 BST    2826  1000  1000   5 present   /usr/bin/sleep

这真的很酷。通过尝试coredumpctl,我们可以看到我们有崩溃的历史!

6.2. 从历史中提取核心转储文件

要为特定崩溃提取核心转储文件,我们可以使用PID、可执行文件的名称或崩溃时间。例如,让我们尝试使用 PID保存睡眠的核心转储

$ coredumpctl dump 2826 --output=core.dump
           PID: 2826 (sleep)
           UID: 1000 (user)
           ...
                Stack trace of thread 2826:
                #0  0x00007f7ec62f730e __GI___clock_nanosleep (libc.so.6 + 0xe030e)
                #1  0x00007f7ec62fceb7 __GI___nanosleep (libc.so.6 + 0xe5eb7)
           ...

除了核心转储文件外,我们还可以看到一个简短的摘要,然后是堆栈跟踪。这来自systemd-coredump对核心转储的预处理。

6.3. 使用coredumpctl运行调试会话

让我们看看如何使用debug命令启动调试会话:

$ coredumpctl debug 2826
...
Reading symbols from /usr/bin/sleep...
(No debugging symbols found in /usr/bin/sleep)
[New LWP 2959]
Core was generated by `sleep 500'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
...
(gdb)

请注意gdb是如何在加载核心转储文件的情况下自动打开的。

要检查崩溃,让我们输入“disassemble”。结果,我们可以看到如下反汇编:

Dump of assembler code for function __GI___clock_nanosleep:
<__GI___clock_nanosleep+80>
   0x00007fb71b22c307 <+39>:    mov    eax,0xe6
   0x00007fb71b22c30c <+44>:    syscall
=> 0x00007fb71b22c30e <+46>:    mov    edx,eax

我们可以看到,“ mov wax, 0xe6”后面跟着一个syscall指令。查看系统调用 列表,似乎 230 (0xe6) 是clock_nanosleep syscall。这是捕获核心转储的位置。