使用 Qemu 虚拟 ARM64 平台演示 kdump 崩溃转存

2019年3月6日 710 次阅读 4 条评论 3 人点赞

为什么想起写这篇文章呢?第一是最近在研究 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=yCONFIG_SYSFS=yCONFIG_DEBUG_INFO=yCONFIG_CRASH_DUMP=yCONFIG_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 内核崩溃》写得很棒,既然前人已经种好了树,我这个后人就直接引用了,没有必要重复造轮子,而且造得还不如别人好。


  1. 我自己买了很多书,都是正版,也买网上那些开放的 PDF 文档,尽管他的大部分知识都开放。 ↩︎
  2. 无所谓多少容量,但是为了方便自己在虚拟机内部有更多的空间施展,还是推荐 4G 比较合适。 ↩︎
  3. 同样参考 Linux 官方的手册可以得到详细的说明 ↩︎

文章评论(4)

  • alex

    hello,我近期在研究qemu+wine在arm64位上的机器上QQ、微信,但运行会出错,不知道你在这方面有没有研究?可否向你讨教点经验?
    我目前的进展是:
    在ubuntu32位机器上编译wine32,复制到aarch64机器上,在aarch64上编译qemu。
    qemu+wine在aarch64上的机器上可运行notepad等一些小工具,微信可以正常安装,但运行后会报错,不太清楚如何修正错误。
    如果你也有过研究,希望能传授些经验,感谢,可通过留下的邮箱联系我

    2019年4月22日
    • Jackie Liu

      没这么干过,你的方案理论上应该是可以的,我建议你使用最新的 qemu 和最新的 wine 来做实验,因为低版本可能对 arm64 支持做得不好。另外就是你的 qemu 和 wine 是不是将图形相关的库文件都编译进去了,这个也有影响。第三点就是不要一蹴而就,你先使用 wine 在 x86 上运行起来这些应用之后再尝试用 qemu 来模拟 x86 的环境。

      2019年4月22日
      • alex

        试过了,最新qemu+wine,在ubuntu上直接运行wine可运行微信等程序,但加上qemu,也会运行不了,估计是qemu与wine发生了某些不适,但最终也没解决掉。感觉你们麒麟系统应该做过qemu+wine的尝试,所以问问

        2019年4月23日
        • Jackie Liu

          应用开发团队可能做过,你可以去论坛问问,我这里没有

          2019年4月23日