Kdump
Kdump 是一种标准的 Linux 机制,用于在内核崩溃时转储机器内存内容。Kdump 基于 Kexec。Kdump 利用两个内核:常规系统内核和 kdump 捕获内核(以下简称 kdump 内核)。系统内核是正常引导的内核,带有 crashkernel 参数 —— 我们需要告知系统内核预留一定量的物理内存,用于加载/执行 kdump 内核。然后必须提前加载 kdump 内核,因为当系统内核崩溃时,由于内核已损坏,没有可靠的方法从磁盘读取数据。
一旦发生内核崩溃,系统内核的崩溃处理程序将使用 Kexec 机制在预留内存中启动 kdump 内核。系统内核的内存在这种 kexec 引导中得以保留,并且在崩溃时刻对 kdump 内核可见。一旦 kdump 内核启动,用户可以通过收集 /proc/vmcore 文件来访问崩溃系统内核的内存。此类崩溃转储可以保存到磁盘,或通过网络复制到其他机器以进行进一步的死后分析 (post-mortem investigation)。
在服务器生产环境中,系统内核和 kdump 内核可能不同 —— 系统内核需要大量功能,并编译了许多内核标志/驱动程序,而 kdump 内核的目标是极简且占用尽可能少的内存(例如,如果仅将崩溃转储存储在磁盘上,则可以编译为不支持网络)。但对于桌面端以及一般的非特定设置,系统内核和 kdump 内核使用同一个内核。这意味着我们将加载两次相同的内核代码 —— 一次作为正常的系统内核,另一次加载到预留内存区域,但使用不同的内核参数。
设置 kdump 的替代方案
自动化方式:kdumpst
kdumpstAUR 工具是一种加载 kdump 的自动化方式。它具有高度的可定制性 —— 默认情况下使用另一种日志收集方法(称为 pstore),但可以轻松设置为使用 kdump(只需在 /usr/share/kdumpst.d/00-default 中设置 USE_PSTORE_RAM=0)。如果 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 仓库中提交 issue。
自动化方式: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(收集 vmcore 的紧急模式)目标,并出现提示要求登录紧急 shell。您可以忽略该登录,因为 vmcore 的收集将在后台进行并自动重启。
重启后,/var/crash/crashdump-* 目录下应该会出现一个新的崩溃转储文件。
/etc/conf.d/simple-kdump.conf 中的默认 BOOT_OPTIONS 是占位符 (root=/dev/os/root)。必须将其替换为您正常引导时使用的相同 root=, rootflags=, rootfstype= 以及任何解密参数,否则 kexec 内核无法挂载根文件系统,导致 /var/crash/ 保持为空。请不要包含 crashkernel= 或 quiet;服务会自动附加 nr_cpus=1 reset_devices systemd.unit=emergency-kdump.target。编辑后,运行 systemctl restart simple-kdump-setup.service 以重新加载 kexec 镜像。1G 内存。用于 kdump 的 initramfs 中也必须包含 encrypt 钩子(或 sd-encrypt)(对于已经从 LUKS 引导的安装,默认已包含)。为了实现完全无人值守的捕获,请将转储存储在独立的、未加密的分区上并相应调整 simple-kdump-collect.service,或者通过嵌入在 initramfs 中的密钥文件临时解锁根设备(参见 Dm-crypt/设备加密#密钥文件)。手动步骤
如果您倾向于手动操作,以下指南将提供帮助。
编译内核
系统内核和 kdump 内核都需要一些默认可能未设置的配置标志。请参阅 内核编译 文章以获取更多关于在 Arch 中编译自定义内核的信息。这里我们将重点介绍 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 内核为例,其 initramfs 生成在 /boot/initramfs-linux.img。
其核心思想是将 kexec 环境的启动过程视为一个常规的 Arch Linux 启动序列。但通过额外的 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 热插拔 文档。您可能不想手动运行 kexec,而是想设置一个在启动时运行 kexec 的 Systemd 服务
/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 作为 earlyhook 运行,有望在内核任何部分崩溃之前执行。这里的一个重要笔记是,我们以紧急模式 (emergency mode) 运行内核,因为以救援 (rescue) 或正常模式运行内核可能会导致崩溃内核中再次发生相同的崩溃。
/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 保存转储文件(参见 #保存崩溃内核的内存)
分析内核核心转储 (core dump)
研究保存的内核核心转储(kernel core dump)的最佳方法是使用专门为此设计的工具。最常见的替代方案是基于 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 - crash 官方网站
- https://drgn.readthedocs.io/ - drgn 文档网站