Kdump
Kdump 是 Linux 标准的内核崩溃时转储机器内存内容的机制。Kdump 基于 Kexec。Kdump 使用两个内核:常规系统内核和 kdump 捕获内核(以下称为 kdump 内核)。系统内核是正常启动的内核,通过 crashkernel 参数启动 - 我们需要告诉系统内核预留一部分物理内存,kdump 内核将在此内存中加载/执行。然后有必要提前加载 kdump 内核,因为当系统内核崩溃时,没有可靠的方法可以从磁盘读取数据,例如,因为该内核已损坏。
一旦发生内核崩溃,系统内核崩溃处理程序将使用 Kexec 机制在预留的内存中启动 kdump 内核。在 kexec 启动期间,系统内核的内存会被保留,并在崩溃时可供 kdump 内核访问。一旦 kdump 内核启动,用户就可以收集 /proc/vmcore 文件来访问崩溃的系统内核的内存。这样的崩溃转储可以保存到磁盘或通过网络复制到另一台机器进行进一步的案后分析。
在服务器生产环境中,系统内核和 kdump 内核可能是不同的 - 系统内核需要很多功能,并且是用许多内核标志/驱动程序编译的,而 kdump 内核的目标是极简,尽量占用最少的内存,例如,如果没有网络支持,它可能被编译,如果我们只将崩溃转储保存到磁盘。但对于桌面和一般非特定设置,系统和 kdump 内核使用相同的内核。这意味着我们将加载相同的内核代码两次 - 一次作为正常的系统内核,另一次加载到保留的内存区域,但使用不同的内核参数。
设置 kdump 的替代方案
自动方式:kdumpst
该 kdumpstAUR 工具是加载 kdump 的一种自动方式。它高度可定制 - 默认使用另一种日志收集方法(称为 pstore),但可以通过在 /usr/share/kdumpst.d/00-default 中设置 USE_PSTORE_RAM=0 来轻松设置为使用 kdump。如果 pstore RAM 区域不可用,该工具还会回退到 kdump。
安装 kdumpst 后,您可以检查 journal,以下消息表示 kdump 已加载:kdumpst: panic kexec loaded successfully。如果发生内核崩溃,kdump 将被收集,并在后续引导中,一条消息表明操作成功:kdumpst: logs saved in "/var/crash/kdumpst/logs"。在该文件夹中,您将找到一个轻量级的 zip 包,其中包含 dmesg 和一些额外数据。vmcore 本身保存在 /var/crash/kdumpst/crash。如有疑问/问题,可以使用 OFTC 上的 #kdump IRC 频道,或在 kdumpst 存储库中打开 issues。
自动方式:simple-kdump
该 simple-kdumpAUR 工具提供了一种简单易配置的方式来设置和收集 kdump。与 kdumpstAUR 不同,它独立于引导加载程序,只有一个目标,即将 vmcore 文件保存到 /var/crash/。
它基本上是后面章节中提到的所有手动设置,但使用 systemd 进行了稍微更好的组织,并且重用了 Arch Linux 内核(或用户选择的任何内核),因此它非常灵活且简单。
安装 simple-kdumpAUR 后,使用任何启用了 CONFIG_PROC_VMCORE=y 的引导内核/initramfs 组合填写 /etc/conf.d/simple-kdump.conf。建议使用 Arch Linux linux 或 linux-lts 内核,它们已经启用了所有必需的功能。
然后添加 crashkernel=[size] 内核参数并重新启动。建议使用不小于 512M 的值。
最后,启用并启动 simple-kdump-setup.service,然后参考 #通过崩溃内核测试 kdump 来验证 kdump 的行为。
kexec 内核应该达到 Emergency Mode to collect vmcore 目标,并出现一个提示要求登录紧急 shell。您可以忽略此登录,因为 vmcore 收集将在后台进行并自动重新启动。
重新启动后,应该会在 /var/crash/crashdump-* 处有一个新的崩溃转储。
手动步骤
如果您偏好手动操作,以下指南将有所帮助。
编译内核
系统/kdump 内核都需要一些可能默认未设置的配置标志。有关在 Arch 中编译自定义内核的更多信息,请参阅 Kernel Compilation 文章。这里我们将重点介绍 Kdump 特定的配置。当前默认的 Arch 内核构建已设置了这些标志。您可以通过查看 /proc/config.gz 来验证您的运行内核是否设置了这些标志。
请注意,默认的 linux 和 linux-lts 内核都启用了必需的选项。但不幸的是,默认内核已剥离调试信息,因此您仍然需要重新编译内核以获取所有调试信息,以便能够正确分析 vmcore。
要创建内核,您需要编辑内核 .config 文件并启用以下配置选项
.config
CONFIG_DEBUG_INFO=y CONFIG_CRASH_DUMP=y CONFIG_PROC_VMCORE=y CONFIG_DEBUG_INFO=y COFNIG_DEBUG_INFO_BTF=y
最后两个选项是为了额外的调试信息,以便像 crash 或 drgn 这样的工具能够分析 vmcore。(或者是否有方法使用 debuginfod 下载内核调试信息?)
还要将软件包基础名称更改为类似 linux-kdump 的名称,以将其与默认的 Arch 内核区分开。编译内核软件包并安装它。保存 *./src/linux-X.Y/vmlinux* 未压缩的系统内核二进制文件 - 它包含调试符号,您稍后在分析崩溃时需要它们。
供参考,在内核 Kdump 文档中可以找到有关构建 kdump 内核或配置 kdump 内核参数的一些详细信息。
重用现有内核和 initramfs
设置 kdump 的最简单方法是使用现有的内核和 initramfs。这里的示例将使用 linux 内核作为示例,它在 /boot/initramfs-linux.img 生成其 initramfs。
核心思想是像常规 Arch Linux 启动过程一样启动 kexec 环境。但带有额外的 systemd 选项来稍微改变启动过程(跳过重新设置 kexec 环境,收集 vmcore,然后重新启动)。
因此,我们不需要生成特殊的 initramfs,与其他发行版不同(我们由 mkinitcpio 生成的默认 initramfs 已经比我们的竞争对手小很多)。
设置 kdump 内核
首先,您需要为 kdump 内核加载预留系统内核中的内存。编辑您的引导加载程序配置,并添加 crashkernel=[size] 内核参数。
根据机器和 kdump 内核的构建方式,通常 256M 到 512M 之间就足够了 - 设置完所有内容后,值得尝试检查是否成功。请注意,预留的内存对系统内核不可用。
重新启动到您的系统内核。为确保内核是以正确的选项启动的,请检查文件 /proc/cmdline 和 /sys/kernel/kexec_crash_size,以查看内存是否确实已预留(有时可能,尽管很少见,但内存预留会失败 - 如果发生这种情况,请检查 dmesg 获取更多信息)。
接下来,您需要告诉 Kexec 您想使用您的 kdump 内核。指定您的内核、initramfs 文件、根设备和其他参数(如果需要):(这里我们使用默认的 linux 内核)
# kexec -p /boot/vmlinuz-linux --initrd=/boot/initramfs-linux.img] --append="root=[root-device] irqpoll nr_cpus=1 reset_devices"
它将 kdump 内核加载到预留区域。如果没有 -p 标志,kexec 会立即启动内核,但在此标志存在的情况下,kdump 内核将被加载到预留内存中,但其启动将被推迟,直到发生崩溃。
nr_cpus=1 将 kdump 环境中的 CPU 限制为 1,这既节省内存(CPU 结构消耗内存!),也更安全,因为它限制了潜在并发问题的表面积。如果此选项因某种原因失败,则可以使用另一个选项:maxcpus=1。第二个选项消耗的内存稍多一些,因为它初始化了其他 CPU 结构但禁用了除 CPU0 之外的所有 CPU,而 nr_cpus 一个则有效丢弃了其他 CPU 结构。有关更多信息,请参阅内核 CPU hotplug 文档。您可能希望设置一个 Systemd 服务来在启动时运行 kexec,而不是手动运行 kexec。
/etc/systemd/system/kdump.service
[Unit] Description=Load the kdump kernel After=local-fs.target [Service] Type=oneshot RemainAfterExit=true ExecStart=/usr/bin/kexec -p /boot/vmlinuz-linux --initrd=/boot/initramfs-linux.img --append="root=[root-device] irqpoll nr_cpus=1 reset_devices systemd.mask=kdump.service" ExecStop=/usr/bin/kexec -p -u [Install] WantedBy=multi-user.target
然后 启用 kdump.service。
请注意,由于服务已启用,并且我们的 kexec 环境像常规启动一样启动,它将尝试启动 kdump.service,但会因内存不足而失败。因此,在 --append= 选项中,指定了 systemd.mask=kdump.service 以避免 kdump 服务本身。
要检查崩溃内核是否已加载,请运行以下命令
$ cat /sys/kernel/kexec_crash_loaded
通过崩溃内核测试 kdump
如果您想测试崩溃,可以使用 sysrq。
# sync; echo 1 > /proc/sys/kernel/sysrq; echo c > /proc/sysrq-trigger
一旦发生崩溃,kexec 将加载您的 kdump 内核,它应该看起来像常规启动,但内存(预留大小)要小得多,并且只有一个 CPU 核心。
保存崩溃的内核内存
一旦启动到 kdump 内核,核心思想是将 /proc/vmcore 的相关内容保存下来以便以后分析。虽然这被公开为一个文件(因此可以像 cp /proc/vmcore /root/vmcore.crashdump 一样复制它,但这不是推荐的方式。vmcore 是系统内存的完整副本,所以如果您的机器有 64G 内存,这个文件将是 64G,例如。它包括所有用户空间加载的数据以及空闲内存。因此,最好的保存方法是使用 makedumpfile 工具。该应用程序能够删除空闲内存和用户空间无关数据,并压缩 vmcore!用法示例
# makedumpfile -z -d 31 /proc/vmcore /root/vmcore.crashdump_compressed
您也可以使用此命令保存崩溃内核的 dmesg 日志
# makedumpfile --dump-dmesg /proc/vmcore /root/vmcore.dmesg
以下 systemd 服务可用于自动保存崩溃转储并重新启动到系统内核
/etc/systemd/system/kdump-save.service
[Unit] Description=Save the kernel crash dump after a crash After=multi-user.target [Service] Type=idle ExecStart=/bin/sh -c 'mkdir -p /var/crash/ && /usr/bin/makedumpfile -z -d 31 /proc/vmcore "/var/crash/crashdump-$$(date +%%F-%%T)"' ExecStopPost=/usr/bin/systemctl reboot UMask=0077
可以从 kdump 内核命令行调用此服务 - 为此,我们应按如下方式编辑 kdump 加载服务
/etc/systemd/system/kdump.service
[Unit] Description=Load the kdump kernel After=local-fs.target [Service] Type=oneshot RemainAfterExit=true ExecStart=/usr/bin/kexec -p /boot/vmlinuz-linux --initrd=/boot/initramfs-linux.img --append="root=[root-device] irqpoll nr_cpus=1 reset_devices systemd.mask=kdump.service systemd.unit=kdump-save.service" ExecStop=/usr/bin/kexec -p -u [Install] WantedBy=multi-user.target
使用 mkinitcpio 进行早期 kdump
您可能会遇到内核在 systemd 服务启动之前就崩溃的情况。在这种情况下,将 kexec 作为 mkinitcpio 钩子而不是服务运行可能会有所帮助。
首先,复制您的 initramfs。这将用于运行崩溃内核。
# cp /boot/initramfs-linux.img /boot/initramfs-linux-crash.img
接下来,创建 mkinitcpio 安装文件。这使得我们能够构建主 initramfs,其中包含一份用于崩溃内核的崩溃 initramfs 副本,以及
/etc/initcpio/install/kdump
build() {
add_binary kexec
add_file /boot/initramfs-linux-crash.img /crash/initramfs.img
add_file /boot/vmlinuz-linux /crash/vmlinuz
add_runscript
}
help() {
cat <<HELPEOF
Installs the crash kernel on boot
HELPEOF
}
接下来,创建 mkinitcpio 钩子文件。这将在内核中的任何内容崩溃之前,尽早运行 kexec。这里的一个重要提示是,我们在紧急模式下运行内核,因为在恢复模式或正常模式下运行内核可能会导致崩溃内核中发生相同的崩溃。
/etc/initcpio/hook/kdump
run_earlyhook() {
msg 'Loading crash kernel..'
if [ -e /crash/vmlinuz ]; then
if [ -e /crash/initramfs.img ]; then
kexec -p /crash/vmlinuz --initrd=/crash/initramfs.img --append="root=[root-device] irqpoll nr_cpus=1 reset_devices emergency"
else
msg 'No initramfs found'
fi
else
msg 'No vmlinuz found'
fi
}
现在使用新的钩子运行 mkinitcpio
# mkinitcpio -A kdump
当崩溃发生时,您将被加载到紧急内核模式。输入密码后,您将进入终端。您需要做的第一件事是使您的根文件系统可写。
$ mount -o remount, rw /
现在您可以使用 makedumpfile 保存转储(参见 #保存崩溃的内核内存)
分析内核核心转储
研究保存的内核核心转储的最佳方法是使用专门为此设计的工具。最常见的替代方案是基于 gdb 的 crash。像这样运行 crash
$ crash vmlinux path/crash.dump
其中 vmlinux 应该包含调试符号,以便从保存的崩溃转储中提取更多信息。
有关调试实践的更多信息,请参阅 man crash 或 [1]。
另一个最近的替代方案是 drgn,一个基于 Python 的完全可脚本化的工具,用于从 vmcore 中提取信息。
参见
- https://docs.linuxkernel.org.cn/admin-guide/kdump/kdump.html - 官方 kdump 文档
- https://www.dedoimedo.com/computers/www.dedoimedo.com-crash-book.pdf - The crash book
- https://gitlab.freedesktop.org/gpiccoli/kdumpst/ - kdumpst 存储库
- https://crash-utility.github.io - The crash 网站
- https://drgn.readthedocs.io/ - The drgn 文档网站