使用 Kdump 检查 Linux 内核崩溃

让我们研究一下 kdump 的基本用法,并了解 kdump/kexec 内核实现的内部原理。
338 位读者喜欢这个。
The boot process

Penguin, Boot。由 Opensource.com 修改。CC BY-SA 4.0。

Kdump 是一种获取崩溃的 Linux 内核转储的方法,但是找到解释其用法和内部原理的文档可能具有挑战性。 在本文中,我将研究 kdump 的基本用法,并了解 kdump/kexec 内核实现的内部原理。

Kexec 是一个 Linux 内核到内核的引导加载程序,可帮助从第一个内核的上下文中引导第二个内核。 Kexec 关闭第一个内核,绕过 BIOS 或固件阶段,并跳转到第二个内核。 因此,在没有 BIOS 阶段的情况下,重启速度会更快。

Kdump 可以与 kexec 应用程序一起使用,例如,当第一个内核崩溃时启动第二个内核时,第二个内核用于复制第一个内核的内存转储,该内存转储可以使用 gdb 和 crash 等工具进行分析,以确定崩溃原因。 (在本文中,我将使用第一个内核来表示当前正在运行的内核,第二个内核来表示使用 kexec 运行的内核,捕获内核来表示当前内核崩溃时运行的内核。)

kexec 机制在内核和用户空间中都有组件。 内核为 kexec 重启功能提供了一些系统调用。 一个名为 kexec-tools 的用户空间工具使用这些调用,并提供一个可执行文件来加载和引导第二个内核。 有时,发行版还会添加 kexec-tools 之上的包装器,这有助于捕获和保存各种转储目标配置的转储。 在本文中,我将使用名称发行版-kexec-tools,以避免混淆上游 kexec-tools 和特定于发行版的 kexec-tools 代码。 我的示例将使用 Fedora Linux 发行版。

Fedora kexec-tools

dnf install kexec-tools 在 Fedora 机器上安装 fedora-kexec-tools。 安装 fedora-kexec-tools 后,可以通过执行 systemctl start kdump 来启动 kdump 服务。 启动此服务后,它会创建一个根文件系统 (initramfs),其中包含挂载目标以保存 vmcore 的资源,以及将 vmcore 复制/转储到目标的命令。 然后,此服务将内核和 initramfs 加载到崩溃内核区域内的合适位置,以便可以在内核崩溃时执行它们。

Fedora 包装器提供两个用户配置文件

  1. /etc/kdump.conf 指定配置参数,这些参数的修改需要重建 initramfs。 例如,如果您将转储目标从本地磁盘更改为 NFS 挂载的磁盘,则需要捕获内核加载与 NFS 相关的内核模块。
  2. /etc/sysconfig/kdump 指定配置参数,这些参数的修改不需要重建 initramfs。 例如,如果您只需要修改传递给捕获内核的命令行参数,则无需重建 initramfs。

如果在 kdump 服务启动后内核崩溃,则执行捕获内核,该内核进一步从 initramfs 执行 vmcore 保存过程,然后重启到稳定的内核。

kexec-tools

编译 kexec-tools 源代码会提供一个名为 kexec 的可执行文件。 同一个可执行文件可用于加载和执行第二个内核,或加载可在内核崩溃时执行的捕获内核。

加载第二个内核的典型命令

# kexec -l kernel.img --initrd=initramfs-image.img –reuse-cmdline

--reuse-command 命令行表示使用与第一个内核相同的命令行。 使用 --initrd 传递 initramfs。 -l 表示您正在加载第二个内核,该内核可以通过 kexec 应用程序本身 (kexec -e) 执行。 使用 -l 加载的内核无法在内核崩溃时执行。 您必须传递 -p 而不是 -l 才能加载可在内核崩溃时执行的捕获内核。

加载捕获内核的典型命令

# kexec -p kernel.img --initrd=initramfs-image.img –reuse-cmdline

echo c > /pros/sysrq-trigger 可用于崩溃内核以进行测试。 有关 kexec-tools 提供的选项的详细信息,请参见 man kexec。 在转到下一节(重点介绍内部实现)之前,请观看此 kexec_dump 演示

Kdump:端到端流程

图 1 显示了一个流程图。 在引导第一个内核期间,必须为捕获内核保留 Crashkernel 内存。 您可以在内核命令行中传递 crashkernel=Y@X,其中 @X 是可选的。 crashkernel=256M 适用于大多数 x86_64 系统; 但是,为崩溃内核选择合适的内存量取决于许多因素,例如内核大小和 initramfs,以及 initramfs 中包含的模块和应用程序的运行时内存要求。 有关传递崩溃内核参数的更多方法,请参见 kernel-parameters 文档

pratyush_f1.png

您可以将内核和 initramfs 镜像传递给 kexec 可执行文件,如(kexec-tools)部分的典型命令中所示。 捕获内核可以与第一个内核相同,也可以不同。 通常,保持相同。 Initramfs 是可选的; 例如,当使用 CONFIG_INITRAMFS_SOURCE 编译内核时,您不需要它。 通常,您将第一个 initramfs 与不同的捕获 initramfs 保持分开,因为在捕获 initramfs 中自动复制 vmcore 更好。 执行 kexec 时,它还会加载 elfcorehdr 数据和 purgatory 可执行文件。 elfcorehdr 具有有关系统 RAM 内存组织的信息,而 purgatory 是在捕获内核执行之前执行的二进制文件,并验证第二阶段二进制文件/数据是否具有正确的 SHA。 Purgatory 也可以是可选的。

当第一个内核崩溃时,它会执行最小的必要退出过程,如果存在,则切换到 purgatory。 Purgatory 验证加载的二进制文件的 SHA256,如果这些二进制文件正确,则它将控制权传递给捕获内核。 捕获内核根据从 elfcorehdr 收到的系统 RAM 信息创建 vmcore。 因此,在捕获内核启动后,您将在 /proc/vmcore 中看到第一个内核的转储。 根据您使用的 initramfs,您现在可以分析转储,将其复制到任何磁盘,或者可以自动复制,然后重启到稳定的内核。

内核系统调用

内核提供了两个系统调用 - kexec_load()kexec_file_load(),它们可用于在执行 kexec -l 时加载第二个内核。 它还为 reboot() 系统调用提供了一个额外的标志,可用于使用 kexec -e 引导到第二个内核。

kexec_load(): kexec_load() 系统调用加载一个新内核,该内核可以稍后由 reboot() 执行。 它的原型定义如下

long kexec_load(unsigned long entry, unsigned long nr_segments,
struct kexec_segment *segments, unsigned long flags);

用户空间需要传递不同组件(例如内核、initramfs 等)的段。 因此,kexec 可执行文件有助于准备这些段。 kexec_segment 的结构如下所示

struct kexec_segment {
	void *buf;
	/* Buffer in user space */
	size_t bufsz;
	/* Buffer length in user space */
	void *mem;
	/* Physical address of kernel */
	size_t memsz;
	/* Physical address length */
};

当使用 LINUX_REBOOT_CMD_KEXEC 调用 reboot() 时,它会引导到由 kexec_load() 加载的内核中。 如果将标志 KEXEC_ON_CRASH 传递给 kexec_load(),则加载的内核不会使用 reboot(LINUX_REBOOT_CMD_KEXEC) 执行; 而是将在内核崩溃时执行。 必须定义 CONFIG_KEXEC 才能使用 kexec,并且应为 kdump 定义 CONFIG_CRASH_DUMP

kexec_file_load(): 作为用户,您只需将两个参数(即内核和 initramfs)传递给 kexec 可执行文件。 然后,kexec 从 sysfs 或其他内核信息源读取数据,并创建所有段。 因此,kexec_file_load() 简化了用户空间,您只需传递内核和 initramfs 的文件描述符。 其余的所有段准备工作均由内核本身完成。 应启用 CONFIG_KEXEC_FILE 才能使用此系统调用。 它的原型如下所示

long kexec_file_load(int kernel_fd, int initrd_fd, unsigned long
cmdline_len, const char __user * cmdline_ptr, unsigned long
flags);

请注意,kexec_file_load() 也接受命令行,而 kexec_load() 则不接受。 内核具有不同的特定于体系结构的方式来接受命令行。 因此,在 kexec_load() 的情况下,kexec-tools 通过其中一个段(例如,在 dtbELF 引导注释中等)传递命令行。

目前,仅 x86 和 PowerPC 支持 kexec_file_load()

内核崩溃时会发生什么

当第一个内核崩溃时,在将控制权传递给 purgatory 或捕获内核之前,它会执行以下操作

  • 准备 CPU 寄存器(请参见内核代码中的 crash_setup_regs());
  • 更新 vmcoreinfo 注释(请参见 crash_save_vmcoreinfo());
  • 关闭非崩溃的 CPU 并保存准备好的寄存器(请参见 machine_crash_shutdown()crash_save_cpu());
  • 您可能还需要在此处禁用中断控制器;
  • 最后,它执行 kexec 重启(参见 machine_kexec()),将 kexec 段加载/刷新到内存,并将控制权传递给入口段的执行。入口段可以是 purgatory (中转站) 或下一个内核的起始地址。

ELF 程序头

kdump 中涉及的大多数 dump core 都采用 ELF 格式。因此,理解 ELF 程序头非常重要,特别是如果您想查找 vmcore 准备方面的问题。每个 ELF 文件都有一个程序头,它

  • 由系统加载器读取,
  • 描述了程序应该如何加载到内存中,
  • 并且可以使用 Objdump -p elf_file 来查看程序头。

vmcore 的 ELF 程序头示例

# objdump -p vmcore
vmcore:
file format elf64-littleaarch64
Program Header:
NOTE off 0x0000000000010000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0 filesz
0x00000000000013e8 memsz 0x00000000000013e8 flags ---
LOAD off 0x0000000000020000 vaddr 0xffff000008080000 paddr 0x0000004000280000 align 2**0 filesz
0x0000000001460000 memsz 0x0000000001460000 flags rwx
LOAD off 0x0000000001480000 vaddr 0xffff800000200000 paddr 0x0000004000200000 align 2**0 filesz
0x000000007fc00000 memsz 0x000000007fc00000 flags rwx
LOAD off 0x0000000081080000 vaddr 0xffff8000ffe00000 paddr 0x00000040ffe00000 align 2**0 filesz
0x00000002fa7a0000 memsz 0x00000002fa7a0000 flags rwx
LOAD off 0x000000037b820000 vaddr 0xffff8003fa9e0000 paddr 0x00000043fa9e0000 align 2**0 filesz
0x0000000004fc0000 memsz 0x0000000004fc0000 flags rwx
LOAD off 0x00000003807e0000 vaddr 0xffff8003ff9b0000 paddr 0x00000043ff9b0000 align 2**0 filesz
0x0000000000010000 memsz 0x0000000000010000 flags rwx
LOAD off 0x00000003807f0000 vaddr 0xffff8003ff9f0000 paddr 0x00000043ff9f0000 align 2**0 filesz
0x0000000000610000 memsz 0x0000000000610000 flags rwx

在这个例子中,有一个 note (注释) 段,其余的都是 load (加载) 段。note 段包含关于 CPU notes 的信息,load 段包含关于复制的系统 RAM 组件的信息。

Vmcore 以 elfcorehdr 开始,它具有与 ELF 程序头相同的结构。参见图 2 中 elfcorehdr 的表示。

pratyush_f2.png

kexec-tools 读取 /sys/devices/system/cpu/cpu%d/crash_notes 并为 CPU PT_NOTE 准备头。类似地,它读取 /sys/kernel/vmcoreinfo 并为 vmcoreinfo PT_NOTE 准备头,并从 /proc/iomem 读取系统 RAM 值并准备内存 PT_LOAD 头。当捕获内核接收到 elfcorehdr 时,它会附加来自头中提到的地址的数据并准备 vmcore。

崩溃笔记 (Crash notes)

崩溃笔记是每个 CPU 区域,用于在系统崩溃时存储 CPU 状态;它包含有关当前 PID 和 CPU 寄存器的信息。

vmcoreinfo

这个 note 段有各种内核调试信息,例如结构体大小、符号值、页面大小等。这些值由捕获内核解析并嵌入到 /proc/vmcore 中。vmcoreinfo 主要由 makedumpfile 应用程序使用。Linux 内核中的 include/linux/kexec.h 包含用于定义新的 vmcoreinfo 的宏。一些示例宏如下所示:

  • VMCOREINFO_PAGESIZE()
  • VMCOREINFO_SYMBOL()
  • VMCOREINFO_SIZE()
  • VMCOREINFO_STRUCT_SIZE()

makedumpfile

vmcore 中的许多信息(例如空闲页面)没有用处。Makedumpfile 是一个应用程序,它排除不必要的页面,例如:

  • 填充零的页面,
  • 没有私有标志的缓存页面(非私有缓存);
  • 具有私有标志的缓存页面(私有缓存);
  • 用户进程数据页面;
  • 空闲页面。

此外,makedumpfile 在复制时压缩 /proc/vmcore 数据。它还可以从 dump 中擦除敏感的符号信息;但是,它首先需要内核的调试信息才能做到这一点。此调试信息来自 VMLINUXvmcoreinfo,其输出可以是 ELF 格式或 kdump 压缩格式。

典型用法

# makedumpfile -l --message-level 1 -d 31 /proc/vmcore makedumpfilecore

详细信息请参见 man makedumpfile

调试 kdump 问题

新的 kdump 用户可能遇到的问题

Kexec -p kernel_image 未成功

  • 检查是否分配了 crash 内存。
  • cat /sys/kernel/kexec_crash_size 应该没有零值。
  • cat /proc/iomem | grep "Crash kernel" 应该有一个已分配的范围。
  • 如果未分配,请在命令行中传递正确的 crashkernel= 参数。
  • 如果没有任何显示,则在 kexec 命令中传递 -d,并将调试输出分享给 kexec-tools 邮件列表。

在第一个内核的最后一条消息(例如“bye”)之后,控制台上没有任何显示

  • 检查 kexec -l kernel_image 之后是否 kexec -e 工作。
  • 可能缺少特定于架构或机器的选项。
  • 可能 purgatory SHA 验证失败。如果您的架构不支持 purgatory 中的控制台,则很难调试。
  • 可能第二个内核提前崩溃了。
  • 为您的系统将 earlycon/earlyprintk 选项传递给第二个内核命令行。
  • 与 kexec-tools 邮件列表分享第一个内核和捕获内核的 dmesg 日志。

资源

fedora-kexec-tools

  • 仓库:git://pkgs.fedoraproject.org/kexec-tools
  • 邮件列表:kexec@lists.fedoraproject.org
  • 描述:规格文件和脚本,提供用户友好的命令/服务,以便可以在不同的用户场景中自动化 kexec-tools

kexec-tools

  • 仓库:git://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git
  • 邮件列表:kexec@lists.infradead.org
  • 描述:使用内核系统调用并提供用户命令 kexec

Linux kernel

  • 仓库:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
  • 邮件列表:kexec@lists.infradead.org
  • 描述:实现 kexec_load()kexec_file_load()reboot() 系统调用以及特定于架构的代码,例如 machine_kexec()machine_crash_shutdown() 等。

Makedumpfile

  • 仓库:git://git.code.sf.net/p/makedumpfile/code
  • 邮件列表:kexec@lists.infradead.org
  • 描述:压缩和过滤 dumpfile 中不必要的组件。

了解更多信息,请观看 Pratyush Anand 于 2017 年 6 月 20 日在中国 LinuxCon ContainerCon CloudOpen 大会上发表的 KDUMP:用法和内部原理 演讲。

标签
User profile image.
Pratyush 在 Red Hat 担任 Linux 内核通用专家。 主要负责 Red Hat 产品和上游面临的若干 kexec/kdump 问题。 他还处理 Red Hat 支持的 ARM64 平台周围的其他内核调试/跟踪/性能问题。 除了 Linux 内核,他还为上游 kexec-tools 和 makedumpfile 项目做出了贡献。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.