基于qemu的kdump与crash环境搭建与实例解析

6,341 阅读4分钟

最近想使用qemu搭建一个能调试kdump与crash的环境,用来学习使用crash工具解决一些内核panic、死机、重启稳定性问题,也能更好的学习内核,于是找了一些资料,发现runninglinuxkernel_5.0介绍的比较详细,所以想按照这个文档先搭建个环境进行学习。(如有侵权,可删除)

kdump是一个内核转储工具,其核心是基于Kexec(kernel execution),可以快速启动一个新的内核,它会跳过BIOS或者bootloader等引导程序的初始化阶段,这样在系统崩溃时快速切换到一个备份的内核,这样第一个内核的内存就得到保留。在第二个内核中可以对第一个内核产生的崩溃数据(寄存器、栈、堆等内存数据)进行dump,写到本地磁盘,然后通过crash工具进行离线分析。

crash是redhat的工程师开发的,主要用来离线分析linux内核转存文件,它整合了gdb工具,功能非常强大。可以查看堆栈,dmesg日志,内核数据结构,反汇编等等,是分析linux内核panic、死机、重启等疑难问题的利器。

1、环境搭建

1.1 环境版本

  1. 主机硬件平台:Intel x86_64处理器笔记本。
  2. 主机操作系统:Ubuntu Linux 20.04
  3. GCC版本:9 (aarch64-linux-gnu-gcc-9)

《奔跑吧Linux内核》源码

1.2 搭建qemu虚拟机环境

1.2.1 vmware环境搭建

网上有很多相关资料参考

1.2.2 安装软件包

在Ubuntu Linux 20.04可以通过如下命令来安装需要的软件包:

$ sudo apt update -y
$ sudo apt install net-tools libncurses5-dev libssl-dev build-essential openssl qemu-system-arm libncurses5-dev gcc-aarch64-linux-gnu git bison flex bc vim universal-ctags cscope cmake python3-dev gdb-multiarch openjdk-13-jre trace-cmd kernelshark bpfcc-tools cppcheck docker docker.io
1.2.3 下载runninglinuxkernel源码
mkdir runninglinuxkernel
cd runninglinuxkernel
git clone https://github.com/figozhang/runninglinuxkernel_5.0.git
1.2.4 编译linux内核
$ cd runninglinuxkernel_5.0
$ ./run_debian_arm64.sh build_kernel
1.2.5 编译根文件系统
$ cd runninglinuxkernel_5.0
$ sudo ./run_debian_arm64.sh build_rootfs
1.2.6 运行qemu环境
$ cd runninglinuxkernel_5.0
$ ./run_debian_arm64.sh run

执行时遇到virtio-9p-pci is not a valid device model name问题(virtio-9p-pci是一个用来在ubuntu与qemu虚拟机之间进行文件共享的模块,可以直接将ubuntu上的文件直接拷贝到qemu,而不用向之前一样还需要重新编译根文件系统),原因是我使用的qemu版本是 5.1.0,通过qemu-system-aarch64 -device help查看的确是没有virtio-9p-pci这个device,参考这个qemu virtio-9p-pci is not a valid device model name的解决办法,需要重新安装qemu。

$ wget https://download.qemu.org/qemu-5.1.0.tar.xz 
$ tar xvJf qemu-5.1.0.tar.xz 
$ cd qemu-5.1.0
$ qemu-system-aarch64 --version
QEMU emulator version 5.1.0
Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers
//加上--enable-virtfs使得qemu能支持virtio-9p-pci,注意需要 libcap 和 libattr 等依赖库
//可使用apt-get build-dep qemu去安装所有的依赖库
$ apt-get build-dep qemu
$ ./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm --enable-virtfs
$ make 
$ sudo make install

再尝试运行qemu环境:

$ cd runninglinuxkernel_5.0
$ ./run_debian_arm64.sh run

image.png

可以通过qemu将virt machine的dtb dump出来,在命令中增加dumpdtb=dump.dtb,并且使用dtc工具转换为dts格式分析查看

run_qemu_debian(){
                cmd="$QEMU -m 4096 -cpu max,sve=on,sve256=on -M virt,gic-version=3,its=on,iommu=smmuv3,dumpdtb=dump.dtb\
                        -nographic $SMP -kernel arch/arm64/boot/Image \
                        -append \"$kernel_arg $debug_arg $rootfs_arg $crash_arg $dyn_arg\"\
                        -drive if=none,file=$rootfs_image,id=hd0\
                        -device virtio-blk-device,drive=hd0\
                        --fsdev local,id=kmod_dev,path=./kmodules,security_model=none\
                        -device virtio-9p-pci,fsdev=kmod_dev,mount_tag=kmod_mount\
                        $DBG"
                echo "running:"
                echo $cmd
                eval $cmd

}

然后执行./run_debian_arm64.sh run,生成dump.dtb,然后执行:

dtc -o dump.dts -O dts -I dtb dump.dtb 

生成dump.dts

image.png

1.3 kdump环境

在qemu虚拟机环境中,使用systemctl status kdump-tools命令来查看kdump服务是否正常工作。如下图,当显示的状态为active表示kdump启动成功。 image.png

2、制造panic并获取转储文件

使用echo c > /proc/sysrq-trigger手动制造一个panic,然后触发重启,进入第二内核: image.png

进入第二内核后,会调用makedumpfile进行内核信息转储(可能需要几分钟时间进行加载),转储完成后,自动重启再进去第一内核:

image.png

3、使用crash分析转储文件

在ubuntu将带调试符号信息的vmlinux文件拷贝到共享文件夹kmodules,在qemu即可访问这个文件:

image.png

接下来在qemu中进入/var/crash目录,转储文件的目录是以日期来命名,然后使用crash工具来加载内核转储文件进行分析(可能需要几分钟时间进行加载):

image.png

可输入命令来进行各种分析:

  1. help:用于查看crash命令的帮助

image.png

一个比较有用的技巧是使用 help 子命令 查看某个子命令的帮助说明:

image.png

  1. bt:输出进程的内核栈函数调用关系
  • -t:显示栈中所有文本符号
  • -f:显示每一栈帧里的数据
  • -l:显示文件名和行号
  • pid:显示指定PID进程的内核栈的函数调用信息

image.png

image.png

image.png

  1. dis:用来输出反汇编结果

image.png

  1. rd:读取内存地址中的值

image.png

  1. struct:显示内核中数据结构的定义或者具体的值

struct xxx -o来显示每个成员的偏移量

image.png

另外,struct xxx -x addr,可以指定一个地址,用来按照struct的格式以16进制显示每个成员的值,这个在实战中十分的有用。

image.png

  1. p:输出内核变量、表达式或者符号的值

p jiffies:输出jiffies的值

image.png

p init_mm:输出进程0的mm数据结构的值

image.png

p irq_stat:如果一个变量时Per-CPU类型,那么会输出所有Per-CPU变量的地址,p irq_stat:0用来输出某个CPU上内核符号的值

image.png

image.png

  1. irq:显示中断相关信息
  • index:显示某个指定irq的信息
  • -b:显示中断下半部信息
  • -s:显示系统中断信息

image.png

image.png

image.png

  1. task:用来显示进程task_struct数据结构和thread_info数据结构的内容,其中-x表示按照16进制显示。

image.png

task -R on_cpu,on_rq,comm 709:查看进程中task_struct中的成员

image.png

  1. vm:显示进程的地址空间相关信息
  • -p:显示虚拟地址和物理地址
  • -m:显示mm_struct数据结构
  • -R:搜索特定字符串或者数值
  • -v:显示该进程中所有vm_area_struct数据结构的值
  • -f num:显示数字在vm_flags中对应的位

image.png

image.png

image.png

  1. kmem:显示系统的内存信息
  • -i:显示系统内存的使用情况
  • -v:显示系统vmalloc的使用情况
  • -V:显示系统vm_stat情况
  • -s:显示slab使用情况
  • -p:显示每个页面的使用情况
  • -g:显示page数据结构里flags的标志位

image.png

image.png

image.png

image.png

image.png

image.png

  1. list:遍历链表,并且可以输出链表成员的值
  • -h:指定链表头(list_head)的地址
  • -s:用来输出链表成员的值
  1. 统计内存的使用情况

统计所有用户进程一共占用了多少内存:

ps -u | awk '{ total += $8 } END { printf "Total RSS of user-mode: %.02f GB\n", total/2^20 }'

image.png

统计哪些进程占用的内存最多:

ps -u | awk '{ m[$9] += $8 } END { for (item in m) { printf "%20s %10s KB\n", item, m[item] } }' | sort -k 2 -r -n

image.png

  1. 查看进程状态

ps | grep UN:查看系统有哪些进程处于不可中断状态

image.png

ps | grep RU:查看系统有哪些进程处于运行状态

image.png

ps | grep -c RU:查看系统处于运行状态的进程个数

image.png

  1. foreach:遍历系统中所有进程并且做一些统计方面的工作

foreach RU bt:遍历系统所有处于运行状态进程的堆栈

image.png

  1. 查看CPU和进程时间戳

runq -t:查看每个CPU上最近运行的时间戳与task

image.png

ps -l:查看每个进程最近运行的时间戳

image.png

  1. 查看dmesg log

image.png