当前位置: 首页 > 科技观察

使用Kdump检查Linux内核崩溃

时间:2023-03-15 09:39:21 科技观察

让我们先看看kdump的基本用法以及kdump/kexec在内核中是如何实现的。kdump是获取崩溃的Linux内核转储的一种方法,但要找到解释其使用和内部结构的文档可能有点困难。在本文中,我将研究kdump的基本用法以及kdump/kexec在内核中是如何实现的。kexec是一个Linux内核到内核引导加载程序,可帮助从第一个内核的上下文引导到第二个内核。kexec关闭第一个内核,绕过BIOS或固件阶段,跳转到第二个内核。因此,在没有BIOS阶段的情况下重新启动变得更快。kdump可以与kexec应用程序一起使用-例如当第一个内核崩溃时启动第二个内核,第二个内核用于复制第一个内核的内存转储,可以使用gdb和crash等工具分析崩溃的原因。(在本文中,我将使用术语“***内核”来表示当前正在运行的内核,使用“辅助内核”来表示使用kexec运行的内核,使用“捕捉内核”来表示当前内核崩溃时运行的内核。)kexec机制内核和用户空间都有组件。内核为kexec的重启功能提供了多个系统调用。称为kexec-tools的用户空间工具使用这些调用并提供可执行文件来加载和引导“第二个内核”。一些发行版还在kexec-tools上添加了包装器,以帮助捕获和保存各种转储目标配置的转储。在本文中,我将使用一个名为distro-kexec-tools的工具来避免上游kexec工具和特定于发行版的kexec-tools代码之间的混淆。我的示例将使用FedoraLinux发行版。Fedorakexec-tools工具使用dnfinstallkexec-tools命令在Fedora机器上安装fedora-kexec-tools。安装fedora-kexec-tools后,可以执行systemctlstartkdump命令启动kdump服务。当此服务启动时,它会创建一个根文件系统(initramfs),其中包含要挂载到目标位置以保存vmcore的资源,以及将vmcore复制和转储到目标位置的命令。该服务然后将内核和initramfs加载到崩溃内核区域内的适当位置,以便它们可以在内核崩溃时执行。Fedorawrapper提供了两个用户配置文件:/etc/kdump.conf指定修改后需要重新构建的配置参数。例如,如果将转储目标从本地磁盘更改为挂载NFS的磁盘,则需要“捕获内核”加载NFS相关的内核模块。/etc/sysconfig/kdump指定修改后不需要重建initramfs的配置参数。例如,如果您只需要修改传递给“捕获内核”的命令行参数,则不需要重建initramfs。如果kdump服务启动后内核出现故障,则执行“捕获内核”,进一步执行initramfs中的vmcore保存过程,然后重启进入稳定内核。kexec-tools工具对kexec-tools的源代码进行编译,得到一个名为kexec.c的可执行文件。这个同名的可执行文件可用于加载和执行“辅助内核”,或加载可在内核崩溃时执行的“捕获内核”。加载“二级内核”的命令:#kexec-lkernel.img--initrd=initramfs-image.img-reuse-cmdline--reuse-command该参数表示使用与“***内核”相同的命令行”。使用--initrd传递initramfs。-l表示您正在加载一个“辅助内核”,它可以由kexec应用程序本身执行(kexec-e)。无法在内核崩溃时执行使用-l加载的内核。为了加载可以在内核崩溃时执行的“捕获内核”,必须传递参数-p而不是-l。加载捕获的内核的命令:#kexec-pkernel.img--initrd=initramfs-image.img--reuse-cmdlineechoc>/pros/sysrq-trigger可用于使内核崩溃以进行测试。有关kexec-tools提供的选项的详细信息,请参见mankexec。在进入下一节之前,先看看这个kexec_dump的演示:kdump:端到端流程下图显示了流程图。crashkernel的内存必须在启动“***kernel”期间为捕获内核保留。您可以在内核命令行上传递crashkernel=Y@X,其中@X是可选的。crashkernel=256M适用于大部分x86_64系统;然而,为崩溃内核选择合适的内存取决于许多因素,例如内核和initramfs的大小,以及initramfs中包含的模块和应用程序运行时的内存要求。有关传递崩溃内核参数的更多方法,请参阅内核参数文档。pratyush_f1.png您可以将内核和initramfs映像传递给kexec可执行文件,如(kexec-tools)部分中的命令所示。“捕获内核”可以与“***内核”相同,也可以不同。通常,同样会这样做。initramfs是可选的;例如,当使用CONFIG_INITRAMFS_SOURCE编译内核时,您不需要它。通常,保存一个与第一个initramfs不同的captureinitramfs,因为最好自动执行captureinitramfs中的vmcore副本。kexec执行的时候,同时加载了elfcorehdr数据和purgatory可执行文件(LCTT译注:purgatory是一个bootloader,为kdump定制的,起这么个奇怪的名字“purgatory”应该只是一种调侃).elfcorehdr有关于系统内存组织的信息,而purgatory可以在“捕获内核”执行之前执行并验证第二阶段二进制文件或数据是否具有正确的SHA。炼狱也是可选的。当“***内核”崩溃时,它会执行必要的退出程序并切换到炼狱(如果存在)。炼狱验证加载的二进制文件的SHA256,如果正确,则将控制权传递给“捕获内核”。“捕获内核”根据从elfcorehdr接收到的系统内存信息创建一个vmcore。所以在“捕获内核”启动后,您会在/proc/vmcore中看到“***内核”的转储。根据您使用的initramfs,您现在可以分析转储,将其复制到任何磁盘,或自动复制,然后重新启动到稳定的内核。内核系统调用内核提供了两个系统调用:kexec_load()和kexec_file_load(),可用于在执行kexec-l时加载“第二个内核”。它还为reboot()系统调用提供了一个附加标志,可用于使用kexec-e启动到“第二个内核”。kexec_load():kexec_load()系统调用加载一个新内核,然后可以通过reboot()执行。其原型定义如下:longkexec_load(unsignedlongentry,unsignedlongnr_segments,structkexec_segment*segments,unsignedlongflags);用户空间需要为不同的组件传递不同的段,比如kernel、initramfs等,因此kexec可执行文件帮助准备了这些段。kexec_segment的结构如下:structkexec_segment{void*buf;/*用户空间缓冲区*/size_tbufsz;/*用户空间缓冲区长度*/void*mem;/*内核物理地址*/size_tmemsz;/*物理地址长度*/;当使用LINUX_REBOOT_CMD_KEXEC调用reboot()时,它会引导至由kexec_load加载的内核。如果将标志KEXEC_ON_CRASH传递给kexec_load(),则加载的内核将不会通过reboot(LINUX_REBOOT_CMD_KEXEC)进行引导;相反,这将在内核恐慌时执行。必须定义CONFIG_KEXEC才能使用kexec,必须定义CONFIG_CRASH_DUMP才能使用kdump。kexec_file_load():作为用户,您只需要将两个参数(即kernel和initramfs)传递给kexec可执行文件。然后,kexec从sysfs或其他内核信息源读取数据并创建所有段。所以使用kexec_file_load()可以简化用户空间,只传递内核和initramfs的文件描述符。剩下的由内核自己完成。使用此系统调用时应启用CONFIG_KEXEC_FILE。其原型如下:请注意,kexec_file_load也可以接受命令行,但kexec_load()不能。内核根据不同的系统架构接受和执行命令行。因此,在kexec_load()的情况下,kexec-tools将通过其中一个段传递命令行(如在dtb或ELF引导注释等中)。目前,kexec_file_load()仅支持x86和PowerPC。当内核崩溃时会发生什么当第一个内核崩溃时,在控制权被传递给炼狱或“捕获内核”之前,执行以下操作:准备CPU寄存器(参见内核代码中的crash_setup_regs());vmcoreinfo更新备注(见crash_save_vmcoreinfo());关闭未崩溃的CPU并保存准备好的寄存器(参见machine_crash_shutdown()和crash_save_cpu());您可能需要在此处禁用中断控制器;***,它执行kexec在重新启动时(参见machine_kexec()),它会将kexec段加载或刷新到内存中,并将控制权传递给进入该段的可执行文件。输入部分可以是下一个内核的炼狱或起始地址。ELF程序头kdump中引用的大部分转储核心都是ELF格式。因此,了解ELF程序头非常重要,尤其是当您想要查找vmcore就绪问题时。每个ELF文件都有一个程序头:由系统加载器读取,描述如何将程序加载到内存中,可以使用Objdump-pelf_file查看程序头。vmcore的ELF程序头的示例如下:#objdump-pvmcorevmcore:fileformatelf64-littleaarch64ProgramHeader:NOTEoff0x0000000000010000vaddr0x0000000000000000paddr0x0000000000000000align2**0filesz0x00000000000013e8memsz0x00000000000013e8flags---LOADoff0x0000000000020000vaddr0xffff000008080000paddr0x0000004000280000align2**0filesz0x0000000001460000memsz0x0000000001460000flagsrwxLOADoff0x0000000001480000vaddr0xffff800000200000paddr0x0000004000200000align2**0filesz0x000000007fc00000memsz0x000000007fc00000flagsrwxLOADoff0x0000000081080000vaddr0xffff8000ffe00000paddr0x00000040ffe00000align2**0filesz0x00000002fa7a0000memsz0x00000002fa7a0000flagsrwxLOADoff0x000000037b820000vaddr0xffff8003fa9e0000paddr0x00000043fa9e0000align2**0filesz0x0000000004fc0000memsz0x0000000004fc0000flagsrwxLOADoff0x00000003807e0000vaddr0xffff8003ff9b0000paddr0x00000043ff9b0000align2**0filesz0x0000000000010000memsz0x0000000000010000flagsrwxLOADoff0x00000003807f0000vaddr0xffff8003ff9f0000paddr0x00000043ff9f0000align2**0filesz0x0000000000610000memsz0x0000000000610000flagsrwx本例中有一个note段,其余为load段。note段提供有关CPU的信息,load段提供有关复制的系统内存组件的信息。vmcore以elfcorehdr开头,它与ELF程序头具有相同的结构。请参见下图中elfcorehdr的表示:pratyush_f2.pngkexec-tools读取/sys/devices/system/cpu/cpu%d/crash_notes并为CPUPT_NOTE准备标头。同样,它读取/sys/kernel/vmcoreinfo并准备vmcoreinfoPT_NOTE标头,从/proc/iomem读取系统内存并准备内存PT_LOAD标头。当“捕获核心”收到elfcorehdr时,它从标头中提到的地址读取并准备vmcore。CrashnoteCrashnotes是每个CPU中的一个区域,用于在系统崩溃时存储CPU状态;它有关于当前PID和CPU寄存器的信息。vmcoreinfo注释部分有各种内核调试信息,比如结构体大小、符号值、页面大小等,这些值由捕获内核解析并嵌入到/proc/vmcore中。vmcoreinfo主要由makedumpfile应用程序使用。在Linux内核中,include/linux/kexec.h宏定义了一个新的vmcoreinfo。一些示例宏如下:VMCOREINFO_PAGESIZE()VMCOREINFO_SYMBOL()VMCOREINFO_SIZE()VMCOREINFO_STRUCT_SIZE()makedumpfilevmcore中的许多信息(例如可用页面)都没有用。makedumpfile是一个排除不必要页面的应用程序,例如:充满零的页面;没有私有标志的缓存页面(非私有缓存);带有私有标志的缓存页面(私有缓存);用户进程数据页;可用页面。另外,makedumpfile在拷贝的时候会压缩/proc/vmcore的数据。它还可以从转储中删除敏感的符号信息;然而,为了做到这一点,它首先需要内核的调试信息。此调试信息来自VMLINUX或vmcoreinfo,其输出可以是ELF格式或kdump压缩格式。典型用法:#makedumpfile-l--message-level1-d31/proc/vmcoremakedumpfilecore详情参见manmakedumpfile。kdump调试新手在使用kdump时可能会遇到问题:kexec-pkernel_image不成功查看是否分配了崩溃内存。cat/sys/kernel/kexec_crash_size不应为零值。猫/proc/iomem|grep“崩溃内核”应该有一个指定的范围。如果未分配,请在命令行上传递正确的crashkernel=参数。如果没有,将-d参数传递给kexec命令并将输出发送到kexec-tools邮件列表。在“***kernel”的最后一条消息之后,控制台上看不到任何内容(如“bye”)检查kexec-e之后的kexec-lkernel_image命令是否有效。可能缺少支持的体系结构或特定机器的选项。可能炼狱的SHA验证失败。如果您的体系结构不支持炼狱中的控制台,则很难调试。可能是“第二核”早就坠毁了。将系统的earlycon或earlyprintk选项传递给“辅助内核”命令行。使用kexec-tools邮件列表共享第一个内核和捕获内核的dmesg日志。资源fedora-kexec-toolsGitHub存储库:git://pkgs.fedoraproject.org/kexec-tools邮件列表:kexec@lists.fedoraproject.org描述:specs文件和脚本为kexec-Tools提供用户友好的命令和服务在不同的用户场景中实现自动化。kexec-toolsGitHub存储库:git://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git邮件列表:kexec@lists.infradead.org描述:使用内核系统调用并提供用户命令kexec。Linux内核GitHub存储库: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()。MakedumpfileGitHub存储库:git://git.code.sf.net/p/makedumpfile/code邮件列表:kexec@lists.infradead.org描述:从转储文件中压缩和过滤不需要的组件。(标题图片:Penguin,Boot,修改:Opensource.com。CCBY-SA4.0)