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),但可以轻松设置为使用 kdump(只需在 /usr/share/kdumpst.d/00-default
上设置 USE_PSTORE_RAM=0
即可)。该工具还在 pstore RAM 区域不可用时回退到 kdump。
安装 kdumpst 后,可以检查日志,以下消息表示 kdump 已加载:kdumpst: panic kexec loaded successfully
。如果发生内核崩溃,将收集 kdump,并在随后的启动中,一条消息指示操作成功:kdumpst: logs saved in "/var/crash/kdumpst/logs"
。在该文件夹中,用户将找到一个轻量级的 zip blob,其中包含 dmesg 和一些额外数据。vmcore 本身保存在 /var/crash/kdumpst/crash
中。如有问题,可以使用 OFTC 上的 #kdump IRC 频道,或在 kdumpst 存储库中打开问题。
自动方式:simple-kdump
simple-kdumpAUR 工具提供了一种简单易配置的方式来设置和收集 kdump。与 kdumpstAUR 不同,它与引导加载程序无关,只有一个目标,即将 vmcore 文件保存到 /var/crash/
。
它主要是后面章节中提到的所有手动设置,并通过 systemd 进行了稍微更好的组织,但重复使用了 Archlinux 内核(或最终用户选择的任何内核),因此它非常灵活和简单。
安装 simple-kdumpAUR 后,使用任何具有启用 CONFIG_PROC_VMCORE=y
的启动内核/initramfs 组合填充 /etc/conf.d/simple-kdump.conf
。建议使用 Archlinux 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 中编译自定义内核的更多信息,请参阅 内核编译 文章。这里我们将重点介绍 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。
核心思想是像常规 Archlinux 启动序列一样启动 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
将 CPU 限制为 kdump 环境中的 1 个,这既节省内存(CPU 结构消耗内存!)也更安全,因为它限制了潜在并发问题的表面。如果该选项由于某种原因失败,则可以使用另一个选项代替:maxcpus=1
。第二个选项消耗更多内存,因为它初始化了其他 CPU 结构,但禁用了除 CPU0 之外的此类 CPU,而 nr_cpus
选项有效地丢弃了其他 CPU 结构。更多信息请参见内核 CPU 热插拔 文档。您可以设置 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 作为 earlyhook 运行,希望在内核中的任何内容崩溃之前运行。这里的一个重要提示是我们在紧急模式下运行内核,因为在救援或正常模式下运行内核可能只会导致崩溃内核中再次发生相同的崩溃。
/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 - 崩溃之书
- https://gitlab.freedesktop.org/gpiccoli/kdumpst/ - kdumpst 仓库
- https://crash-utility.github.io - crash 网站
- https://drgn.readthedocs.io/ - drgn 文档网站