为什么想起写这篇文章呢?第一是最近在研究 kdump/kexec 等系列的内核崩溃现场保护能力,所以有相关的技术积累,但是这篇文章不打算什么分析,因为接下来的文章我会分析什么是 kdump 以及如何实现内核崩溃现场保护;第二是因为方便记忆,我自己平常不太喜欢写 Word 这种类型的工作总结,比较喜欢 Markdown 这种标记性语言,他方便我很快的排版以及很好的代码展示,所以我写到了这里,既方便了自己也可以服务于有需要的人;还有第三点就是我在刚开始调试 kdump 的时候,手头上没有什么特别好的资料,终于在网上找到一份 Video,内容大概是手把手的演示怎么在虚拟机中使用 kdump,这不就是我要找的吗?WTF ,居然在淘宝上销售,价格为 299!好吧,我不反对技术服务收费,而且我鼓励朋友们可以为知识付费1,但是很讨厌这种伸手来要钱的范,明明所有的技术都是公开的,没有一行代码是你写的,偏偏就是做了个大约 20 多分钟的演示视频居然收费,还这么贵。不管怎样,我将在博客文章中演示该在虚拟机中利用 kdump 进行崩溃调试。
准备工作
自然要做 kdump 的虚拟机内部崩溃演示,首先得准备一下一个完整的虚拟机环境,由于我的宿主机情况比较复杂,我首先进入到一个相对比较完善的 Docker 镜像环境中,这样做的好处就是哪怕虚拟机被我折腾得再乱或者崩溃都不会影响到我的宿主机的其他服务(这台宿主机是一个代码储存服务器,运行着很多相关的服务,不允许我瞎折腾)。
启动 docker 环境
在文章《Ubuntu 下的 Docker 安装与使用 》中我已经介绍了如何安装与配置基本的 docker 服务,现在只需要按照文档中的流程拉取一个需要的 docker 镜像即可,作为演示我选择的目前比较稳定的 debian:buster 版本。
[jackieliu@localhost ~]$ sudo docker pull debian:buster-20190204
安装虚拟机软件包
直接启动这个 docker 镜像并进入容器内部,执行相关的虚拟机软件包安装。
[jackieliu@localhost ~]$ sudo docker run -itd debian:buster-20190204
7d05ac0f566c836d74cebfe9d8d18d09b29bdf1d407686e0584324511bba1f36
[jackieliu@localhost ~]$ sudo docker exec -it 7d05ac0f566c bash
root@Kylin:/$ apt update
Fetched 1789 kB in 25s (72.6 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
root@Kylin:/$ apt install qemu-system-aarch64 -y
等待安装 qemu 虚拟机安装完毕即可,需要注意的是在 docker 的文章中我提到的容器内部所有的动作,包括装包或者文件的修改都需要在宿主机上进行 docker commit
,不然等你退出这个虚拟机再次启动镜像时,这些修改又不存在了。
制作 ubuntu 系统镜像 rootfs.img 文件
进入 Qemu 虚拟机系统需要准备好虚拟机的 rootfs 文件系统,我们可以从 Ubuntu 的官方下载一个比较基础的系统即可,如果还需要更为复杂的 rootfs,可以在网上搜寻其他人制作的文件系统镜像文件。
root@Kylin:/$ wget http://cdimage.ubuntu.com/ubuntu-base/releases/18.04/release/ubuntu-base-18.04.1-base-arm64.tar.gz
--2019-03-04 07:37:23-- http://cdimage.ubuntu.com/ubuntu-base/releases/18.04/release/ubuntu-base-18.04.1-base-arm64.tar.gz
Resolving cdimage.ubuntu.com (cdimage.ubuntu.com)... 91.189.88.168, 2001:67c:1360:8001::28
Connecting to cdimage.ubuntu.com (cdimage.ubuntu.com)|91.189.88.168|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 27767381 (26M) [application/x-gzip]
Saving to: 'ubuntu-base-18.04.1-base-arm64.tar.gz'
通过 dd 命令创建一个大小为 4G2 的文件 rootfs.img 并格式化为 ext4 文件系统。
root@Kylin:/$ dd if=/dev/zero of=rootfs.img bs=1M count=4k oflag=direct
4096+0 records in
4096+0 records out
4294967296 bytes (4.3 GB, 4.0 GiB) copied, 38.4543 s, 112 MB/s
root@Kylin:/$ mkfs.ext4 rootfs.img
mke2fs 1.44.5 (15-Dec-2018)
Discarding device blocks: done
Creating filesystem with 1048576 4k blocks and 262144 inodes
Filesystem UUID: 11f935ee-69b8-4f7f-aab7-20b204f83574
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
接下来就挂载这个文件镜像到系统,并解压 Ubuntu 官方的 tar.gz 系统到该目录,卸载该文件镜像,一个完整的 ubuntu 系统就被导入到 rootfs.img 中。
root@Kylin:/$ mkdir -p rootfs && mount rootfs.img rootfs
root@Kylin:/$ tar -xvf ubuntu-base-18.04.1-base-arm64.tar.gz -C rootfs
root@Kylin:/$ umount rootfs
重编内核 Image
首先需要从官方地址下载最新的 Linux 内核源码,然后拷贝并修改配置文件 arch/arm64/config/defconfig
到 .config,需要打开 CONFIG_KEXEC=y
、CONFIG_SYSFS=y
、CONFIG_DEBUG_INFO=y
、CONFIG_CRASH_DUMP=y
、CONFIG_PROC_VMCORE=y
配置选项,具体请参考 Linux 内核的官方内置手册。
root@Kylin:/$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
进入 linux 目录修改配置文件之后,执行编译 make Image
命令生成 Image 文件,以便虚拟机能够加载该内核文件。
Kdump 崩溃转存演示
安装 kdump-tools 相关的软件包
由于刚刚下载的 Ubuntu Base 系统中只拥有很少的软件包,所以需要利用 chroot 进入到这个 base 文件系统安装一些 kdump 相关的软件包。
root@Kylin:/$ mount rootfs.img rootfs && chroot rootfs
$ apt install kdump-tools crash kexec-tools makedumpfile systemd -y
$ passwd root << EOF
123123
123123
EOF
$ exit
root@Kylin:/$ umount rootfs
由于默认的 Base 系统没有携带大型的启动管理器,所以为了能够对服务进行管理,我们还需要安装 systemd 启动管理软件。等待安装完毕,并卸载文件系统即可,文件系统镜像中已经保留了当前安装的软件,另外还需要将 Linux 内核的 Image 和 vmlinux 二进制程序也拷贝到 rootfs 中,这两个文件是为了能够让 kdump 的服务正确运行,密码也是需要设置的,不然无法正确登录进入系统。
进入虚拟机中的 Ubuntu 系统
rootfs.img 已经成功安装了 kdump-tools 相关的包,那么现在只需要利用 qemu-system-aarch64 虚拟机启动内核并加载该文件系统镜像即可)。
root@Kylin:/$ qemu-system-aarch64 -enable-kvm -machine virt -M virt,gic_version=3 -cpu cortex-a57 \
-machine type=virt -nographic -smp 2 -m 4096 -kernel Image -hda ./rootfs.img \
-append "console=ttyAMA0 root=/dev/vda rw crashkernel=256M nr_cpus=2"
此时已经进入了 qemu 虚拟机下的 ubuntu 系统。现在,我们就需要在这个系统下演示如何转存内核崩溃信息。
调试内核转存文件
要启动 kdump-tools 服务,首先需要在内核的启动参数中添加 crashkernel=X[@Y]
3 这样的参数,表明需要预留一部分内存用以保存 dump 内核的代码,以便在第一个内核崩溃的情况下,通过一系列故障处理之后,迅速切换到第二个内核,也就是所谓的 dump 内核,通过该内核收集第一个内核所产生的崩溃现场信息,并保存到 /var/crash 目录下,方便重启之后可以查看到该奔溃信息,提供给内核开发者调试问题的方向。
确保了启动参数添加了 crashkernel 之后,还需要保证服务正确启动:
root@localhost:~$ /etc/init.d/kdump-tools restart
Restarting kdump-tools (via systemctl): kdump-tools.service[ 21.282910] kdump-tools[2539]: Stopping kdump-tools: * unloaded kdump kernel
[ 21.398844] kdump-tools[2564]: Starting kdump-tools: * Creating symlink /var/lib/kdump/vmlinuz
[ 21.401271] kdump-tools[2564]: * Creating symlink /var/lib/kdump/initrd.img
[ 21.915798] kdump-tools[2564]: * loaded kdump kernel
root@localhost:~$ kdump-config show
DUMP_MODE: kdump
USE_KDUMP: 1
KDUMP_SYSCTL: kernel.panic_on_oops=1
KDUMP_COREDIR: /var/crash
crashkernel addr: 0xefe00000
/var/lib/kdump/vmlinuz: symbolic link to /boot/vmlinuz-5.0.0-rc3-00473-g957491e4ebfe
kdump initrd:
/var/lib/kdump/initrd.img: symbolic link to /var/lib/kdump/initrd.img-5.0.0-rc3-00473-g957491e4ebfe
current state: ready to kdump
kexec command:
/sbin/kexec -p --command-line="console=ttyAMA0 root=/dev/vda rw nr_cpus=2 nr_cpus=1 systemd.unit=kdump-tools.service" --initrd=/var/lib/kdump/initrd.img /var/lib/kdump/vmlinuz
可以看到当前的服务状态已经成功,接下来只需要将内核弄得崩溃即可:
root@localhost:~$ echo c > /proc/sysrq-trigger
root@localhost:/boot# echo c > /proc/sysrq-trigger
[ 52.026012] sysrq: SysRq : Trigger a crash
[ 52.027357] Kernel panic - not syncing: sysrq triggered crash
[ 52.028997] CPU: 0 PID: 2481 Comm: bash Kdump: loaded Not tainted 5.0.0-rc3-00473-g957491e4ebfe-dirty #49
[ 52.031751] Hardware name: linux,dummy-virt (DT)
[ 52.033126] Call trace:
[ 52.034059] dump_backtrace+0x0/0x140
[ 52.035726] show_stack+0x14/0x20
[ 52.037385] dump_stack+0x90/0xb4
[ 52.038770] panic+0x134/0x2c0
[ 52.040175] sysrq_handle_reboot+0x0/0x18
[ 52.041720] __handle_sysrq+0x84/0x170
[ 52.043524] write_sysrq_trigger+0x64/0x80
[ 52.045179] proc_reg_write+0x64/0xa0
[ 52.046902] __vfs_write+0x30/0x170
[ 52.048243] vfs_write+0xa4/0x1b0
[ 52.049767] ksys_write+0x5c/0xc0
[ 52.051018] __arm64_sys_write+0x18/0x20
[ 52.052614] el0_svc_common+0x84/0xf0
[ 52.054060] el0_svc_handler+0x2c/0x80
[ 52.055556] el0_svc+0x8/0xc
[ 52.056715] SMP: stopping secondary CPUs
[ 52.059009] Starting crashdump kernel...
[ 52.060559] Bye!
然后就会自动启动第二个内核,并且启动 kdump-tools 服务的 savecore 功能保存崩溃现场信息。
[ OK ] Reached target System Initialization.
Starting Kernel crash dump capture service...
[ 2.431304] kdump-tools[1564]: Starting kdump-tools: * running makedumpfile -c -d 31 /proc/vmcore /var/crash/201903050735/dump-incomplete
[ 2.451777] kdump-tools[1564]: get_mem_section: Could not validate mem_section.
[ 2.454372] kdump-tools[1564]: get_mm_sparsemem: Can't get the address of mem_section.
[ 2.458981] kdump-tools[1564]: The kernel version is not supported.
[ 2.464283] kdump-tools[1564]: The makedumpfile operation may be incomplete.
[ 2.470796] kdump-tools[1564]: makedumpfile Failed.
[ 2.476827] kdump-tools[1564]: * kdump-tools: makedumpfile failed, falling back to 'cp'
[ 12.020359] kdump-tools[1564]: * kdump-tools: saved vmcore in /var/crash/201903050735
[ 15.444973] kdump-tools[1564]: * running makedumpfile --dump-dmesg /proc/vmcore /var/crash/201903050735/dmesg.201903050735
[ 15.460068] kdump-tools[1564]: get_mem_section: Could not validate mem_section.
[ 15.463537] kdump-tools[1564]: get_mm_sparsemem: Can't get the address of mem_section.
[ 15.471594] kdump-tools[1564]: The kernel version is not supported.
[ 15.478798] kdump-tools[1564]: The makedumpfile operation may be incomplete.
[ 15.484190] kdump-tools[1564]: makedumpfile Failed.
[ 15.489301] kdump-tools[1564]: * kdump-tools: makedumpfile --dump-dmesg failed. dmesg content will be unavailable
[ 15.496868] kdump-tools[1564]: * kdump-tools: failed to save dmesg content in /var/crash/201903050735
[ 15.507109] kdump-tools[1564]: Tue, 05 Mar 2019 07:35:58 +0000
[ 15.807040] reboot: Restarting system
等待其完成保存完毕之后,会自动重新启动系统,此时 /var/crash/ 目录就保存了一个 vmcore 的调试文件(此处也可能是 dump.xxx 文件),然后通过 crash
工具对其进行调试即可。
root@localhost:~$ crash /vmlinux /var/crash/201903050735/vmcore.201903050735
Copyright (C) 1999, 2002, 2007 Silicon Graphics, Inc.
Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
This program is free software, covered by the GNU General Public License,
and you are welcome to change it and/or distribute copies of it under
certain conditions. Enter "help copying" to see the conditions.
This program has absolutely no warranty. Enter "help warranty" for details.
KERNEL: /vmlinux
DUMPFILE: /var/crash/201903050735/vmcore.201903050735
CPUS: 2
DATE: Tue Mar 5 07:35:35 2019
UPTIME: 00:00:51
LOAD AVERAGE: 0.00, 0.00, 0.00
TASKS: 67
NODENAME: localhost.localdomain
RELEASE: 5.0.0-rc3-00473-g957491e4ebfe-dirty
VERSION: #49 SMP PREEMPT Tue Mar 5 06:34:21 UTC 2019
MACHINE: aarch64 (unknown Mhz)
MEMORY: 4 GB
PANIC: "sysrq: SysRq : Trigger a crash"
PID: 2481
COMMAND: "bash"
TASK: ffff8000f7f3ec00 [THREAD_INFO: ffff8000f7f3ec00]
CPU: 0
STATE: TASK_RUNNING (SYSRQ)
crash>
具体的命令就在 crash 的命令行提示符上输入 help 获得帮助即可,命令都很简单,稍微使用一下就可以上手。
其他
遇到的问题
不清楚是宿主机的原因还是代码原因,目前主线的 Linux kernel 代码在执行命令使第一个内核崩溃之后,跳转到第二个内核的过程中卡死,在社区上也有其他人遇到了类似的情况并给出了补丁,但是并没有合并到主线,不过目前为了演示暂时不考虑为何原因导致这个问题的出现,如果你的 arm64 宿主机不存在这个问题,那么就不需要打这个补丁了。奉上补丁如下:
diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c
index aa9c94113700..3b0350d20e31 100644
--- a/arch/arm64/kernel/machine_kexec.c
+++ b/arch/arm64/kernel/machine_kexec.c
@@ -234,19 +234,12 @@ static void machine_kexec_mask_interrupts(void)
for_each_irq_desc(i, desc) {
struct irq_chip *chip;
- int ret;
chip = irq_desc_get_chip(desc);
if (!chip)
continue;
- /*
- * First try to remove the active state. If this
- * fails, try to EOI the interrupt.
- */
- ret = irq_set_irqchip_state(i, IRQCHIP_STATE_ACTIVE, false);
-
- if (ret && irqd_irq_inprogress(&desc->irq_data) &&
+ if (irqd_irq_inprogress(&desc->irq_data) &&
chip->irq_eoi)
chip->irq_eoi(&desc->irq_data);
还有一点需要说明的就是在 Ubuntu 默认仓库的 crash 不支持最新版本的 Linux 内核,需要更新到 7.2.5 版本才可以。
演示视频
这里奉上我自己调试演示的视频信息,视频可能比较复杂,请结合文档提示进行理解消化。
写在最后
本来准备自己写一篇关于 kdump/kexec 的原理分析文档,而且都已经写了一半了,最终还是决定删掉了,因为很多现在已经有了很多深入分析的文档写得非常清晰明了,《使用 kdump 检查 Linux 内核崩溃》写得很棒,既然前人已经种好了树,我这个后人就直接引用了,没有必要重复造*,而且造得还不如别人好。
- 我自己买了很多书,都是正版,也买网上那些开放的 PDF 文档,尽管他的大部分知识都开放。 ↩︎
- 无所谓多少容量,但是为了方便自己在虚拟机内部有更多的空间施展,还是推荐 4G 比较合适。 ↩︎
- 同样参考 Linux 官方的手册可以得到详细的说明 ↩︎