简介
Kdump 提供了一种机制在内核出现故障的时候把系统的所有内存信息和寄存器信息 dump 出来成一个文件,后续通过 gdb/crash 等工具进行分析和调试。和用户态程序的 coredump 机制类似。它的主要流程如下图所示:
可以看到它的核心原理是保留一段内存并且预先加载了一个备用的 kernel,在主 kernel 出现故障时跳转到备用 kernel,在备用 kernel 中把主 kernel 使用的内存和发生故障时的寄存器信息 dump 到一个磁盘文件中供后续分析。这个文件的格式是 elf core 文件格式。
kdump 主要还是用来捕捉纯软件的故障,在嵌入式领域还需要加上对硬件故障的捕捉,仿照其原理并进行加强和改造,就能构造出自己的 coredump 机制。
下面就来详细的分析整个 kdump 机制的详细原理。
安装
之前的 kdump 安装需要手工的一个个安装 kexec-tools、kdump-tools、crash,手工配置 grub cmdline 参数。在现在的 ubuntu 中只需要安装一个 linux-crashdump 软件包就自动帮你搞定:
sudo apt-get install linux-crashdump
安装完后,可以通过 kdump-config 命令检查系统是否配置正确:
$ kdump-config show
DUMP_MODE: kdump
USE_KDUMP: 1
KDUMP_SYSCTL: kernel.panic_on_oops=1
KDUMP_COREDIR: /var/crash // kdump 文件的存储目录
crashkernel addr: 0x
/var/lib/kdump/vmlinuz: symbolic link to /boot/vmlinuz-5.8.18+
kdump initrd:
/var/lib/kdump/initrd.img: symbolic link to /var/lib/kdump/initrd.img-5.8.18+
current state: ready to kdump // 显示 ready 状态,说明系统 kdmup 机制已经准备就绪
kexec command:
/sbin/kexec -p --command-line="BOOT_IMAGE=/boot/vmlinuz-5.8.18+ root=UUID=9ee42fe2-4e73-4703-8b6d-bb238ffdb003 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet reset_devices systemd.unit=kdump-tools-dump.service nr_cpus=1 irqpoll nousb ata_piix.prefer_ms_hyperv=0" --initrd=/var/lib/kdump/initrd.img /var/lib/kdump/vmlinuz
linux-crashdump 的本质还是由一个个分离的软件包组成的:
$ sudo apt-get install linux-crashdump -d
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
crash efibootmgr grub-common grub-efi-arm64 grub-efi-arm64-bin
grub-efi-arm64-signed grub2-common kdump-tools kexec-tools libfreetype6
libsnappy1v5 makedumpfile os-prober
Suggested packages:
multiboot-doc xorriso desktop-base
Recommended packages:
secureboot-db
The following NEW packages will be installed:
crash efibootmgr grub-common grub-efi-arm64 grub-efi-arm64-bin
grub-efi-arm64-signed grub2-common kdump-tools kexec-tools libfreetype6
libsnappy1v5 linux-crashdump makedumpfile os-prober
0 upgraded, 14 newly installed, 0 to remove and 67 not upgraded.
Need to get 6611 kB of archives.
触发 kdump
在 kdump 就绪以后我们手工触发一次 panic :
$ sudo bash
# echo c > /proc/sysrq-trigger
在系统 kdump 完成,重新启动以后。我们在 /var/crash 目录下可以找到 kdump 生成的内存转存储文件:
$ ls -l /var/crash/202107011353/
total 65324
-rw------- 1 root whoopsie 119480 Jul 1 13:53 dmesg.202107011353 // 系统 kernel log 信息
-rw------- 1 root whoopsie 66766582 Jul 1 13:53 dump.202107011353 // 内存转存储文件,压缩格式
$ sudo file /var/crash/202107011353/dump.202107011353
/var/crash/202107011353/dump.202107011353: Kdump compressed dump v6, system Linux, node ubuntu, release 5.8.18+, version #18 SMP Thu Jul 1 11:24:39 CST 2021, machine x86_64, domain (none)
默认生成的 dump 文件是经过 makedumpfile 压缩过的,或者我们修改一些配置生成原始的 elf core 文件:
$ ls -l /var/crash/202107011132/
total 1785584
-rw------- 1 root whoopsie 117052 Jul 1 11:32 dmesg.202107011132 // 系统 kernel log 信息
-r-----r-- 1 root whoopsie 1979371520 Jul 1 11:32 vmcore.202107011132 // 内存转存储文件,原始 Elf 格式
$ file /var/crash/202107011132/vmcore.202107011132
/var/crash/202107011132/vmcore.202107011132: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style
调试 kdump
使用 crash 工具可以很方便对 kdump 文件进行分析, crash 是对 gdb 进行了一些包装,生成了更多的调试内核的快捷命令。同样可以利用 gdb 和 trace32 工具进行分析。
$ sudo crash /usr/lib/debug/boot/vmlinux-5.8.0-43-generic /var/crash/202106170338/dump.202106170338
kdump-tools.service 流程分析
在前面我们说过可以把 kdump 默认的压缩格式改成原生 ELF Core 文件格式,本节我们就来实现这个需求。
把/proc/vmcore文件从内存拷贝到磁盘是 crash kernel 中的 kdump-tools.service 服务完成的,我们来详细分析一下其中的流程:
- 首先从 kdump-config 配置中可以看到,第二份 crash kernel 启动后 systemd 只需要启动一个服务 kdump-tools-dump.service:
# kdump-config show
DUMP_MODE: kdump
USE_KDUMP: 1
KDUMP_SYSCTL: kernel.panic_on_oops=1
KDUMP_COREDIR: /var/crash
crashkernel addr: 0x73000000
/var/lib/kdump/vmlinuz: symbolic link to /boot/vmlinuz-5.8.0-43-generic
kdump initrd:
/var/lib/kdump/initrd.img: symbolic link to /var/lib/kdump/initrd.img-5.8.0-43-generic
current state: ready to kdump
kexec command:
/sbin/kexec -p --command-line="BOOT_IMAGE=/boot/vmlinuz-5.8.0-43-generic root=UUID=9ee42fe2-4e73-4703-8b6d-bb238ffdb003 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet reset_devices systemd.unit=kdump-tools-dump.service nr_cpus=1 irqpoll nousb ata_piix.prefer_ms_hyperv=0" --initrd=/var/lib/kdump/initrd.img /var/lib/kdump/vmlinuz
2.kdump-tools-dump.service 服务本质是调用 kdump-tools start 脚本:
# systemctl cat kdump-tools-dump.service
# /lib/systemd/system/kdump-tools-dump.service
[Unit]
Description=Kernel crash dump capture service
Wants=network-online.target dbus.socket systemd-resolved.service
After=network-online.target dbus.socket systemd-resolved.service
[Service]
Type=oneshot
StandardOutput=syslog+console
EnvironmentFile=/etc/default/kdump-tools
ExecStart=/etc/init.d/kdump-tools start
ExecStop=/etc/init.d/kdump-tools stop
RemainAfterExit=yes
3.kdump-tools 调用了 kdump-config savecore:
# vim /etc/init.d/kdump-tools
KDUMP_SCRIPT=/usr/sbin/kdump-config
echo -n "Starting $DESC: "
$KDUMP_SCRIPT savecore
4.kdump-config 调用了 makedumpfile -c -d 31 /proc/vmcore dump.xxxxxx:
MAKEDUMP_ARGS=${MAKEDUMP_ARGS:="-c -d 31"}
vmcore_file=/proc/vmcore
makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP
kdump-tools-dump.service 默认调用 makedumpfile 生成压缩的 dump 文件。但是我们想分析原始的 elf 格式的 vmcore 文件,怎么办?
4.1) 首先我们修改 /usr/sbin/kdump-config 文件中的 MAKEDUMP_ARGS 参数让其出错。
MAKEDUMP_ARGS=${MAKEDUMP_ARGS:="-xxxxx -c -d 31"} // 其中 -xxxxx 是随便加的选项
4.2) 然后 kdump-config 就会调用 cp /proc/vmcore vmcore.xxxxxx 命令来生成原始 elf 格式的 vmcore 文件了
log_action_msg "running makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP"
makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP // 先调用 makedumpfile 生成压缩格式的 dump 文件
ERROR=$?
if [ $ERROR -ne 0 ] ; then // 如果 makedumpfile 调用失败
log_failure_msg "$NAME: makedumpfile failed, falling back to 'cp'"
logger -t $NAME "makedumpfile failed, falling back to 'cp'"
KDUMP_CORETEMP="$KDUMP_STAMPDIR/vmcore-incomplete"
KDUMP_COREFILE="$KDUMP_STAMPDIR/vmcore.$KDUMP_STAMP"
cp $vmcore_file $KDUMP_CORETEMP // 再尝试使用 cp 拷贝原始的 vmcore elf 文件
ERROR=$?
fi
原理分析
kexec 实现了crash kernel 的加载。核心分为两部分:
- kexec_file_load()/kexec_load() 负责在起始时就把备份的 kernel 和 initrd 加载好到内存。
- __crash_kexec() 负责在故障时跳转到备份 kernel 中。
kdump 主要实现把 vmcore 文件从内存拷贝到磁盘,并进行一些瘦身。
本次并不打算对 kexec 加载内核和地址转换流程以及 kdump 的拷贝裁剪进行详细的解析,我们只关注其中的两个重要文件 /proc/kcore 和 /proc/vmcore。其中:
- /proc/kcore 是在 normal kernel 中把 normal kernel 的内存模拟成一个 elf core 文件,可以使用gdb 对当前系统进行在线调试,因为是自己调试自己会存在一些限制。
- /proc/vmcore 是在 crash kernel 中把 normal kernel 的内存模拟成一个 elf core 文件,因为这时 normal kernel 已经停止运行,所以可以无限制的进行调试。我们 kdump 最后得到的 dump 文件,就是把 /proc/vmcore 文件从内存简单拷贝到了磁盘,或者再加上点裁剪和压缩。
所以可以看到 /proc/kcore 和 /proc/vmcore 这两个文件是整个机制的核心,我们重点分析这两部分的实现。
elf core 文件格式
关于 ELF 文件格式,我们熟知它有三种格式 .o文件(ET_REL)、.so文件(ET_EXEC)、exe文件(ET_DYN)。但是关于它的第四种格式 core文件(ET_CORE) 一直很神秘,也很神奇 gdb 一调试就能恢复到故障现场。
以下是 elf core 文件的大致格式:
可以看到 elf core 文件只关注运行时状态,所以它只有 segment 信息,没有 section 信息。其主要包含两种类型的 segment 信息:
- PT_LOAD。每个 segemnt 用来记录一段 memory 区域,还记录了这段 memory 对应的物理地址、虚拟地址和长度。
- PT_NOTE。这个是 elf core 中新增的 segment,记录了解析 memory 区域的关键信息。PT_NOTE segment 被分成了多个 elf_note结构,其中 NT_PRSTATUS 类型的记录了复位前 CPU 的寄存器信息,NT_TASKSTRUCT 记录了进程的 task_struct 信息,还有一个最关键0类型的自定义 VMCOREINFO 结论记录了内核的一些关键信息。
elf core 文件的大部分内容用 PT_LOAD segemnt 来记录 memeory 信息,但是怎么利用这些内存信息的钥匙记录在PT_NOTE segemnt 当中。
我们来看一个具体 vmcore 文件的例子:
- 首先我们查询 elf header 信息:
$ sudo readelf -e vmcore.202107011132
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: