最近想使用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 环境版本
- 主机硬件平台:Intel x86_64处理器笔记本。
- 主机操作系统:Ubuntu Linux 20.04
- GCC版本:9 (aarch64-linux-gnu-gcc-9)
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
可以通过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
1.3 kdump环境
在qemu虚拟机环境中,使用systemctl status kdump-tools命令来查看kdump服务是否正常工作。如下图,当显示的状态为active表示kdump启动成功。
2、制造panic并获取转储文件
使用echo c > /proc/sysrq-trigger手动制造一个panic,然后触发重启,进入第二内核:
进入第二内核后,会调用makedumpfile进行内核信息转储(可能需要几分钟时间进行加载),转储完成后,自动重启再进去第一内核:
3、使用crash分析转储文件
在ubuntu将带调试符号信息的vmlinux文件拷贝到共享文件夹kmodules,在qemu即可访问这个文件:
接下来在qemu中进入/var/crash目录,转储文件的目录是以日期来命名,然后使用crash工具来加载内核转储文件进行分析(可能需要几分钟时间进行加载):
可输入命令来进行各种分析:
help:用于查看crash命令的帮助
一个比较有用的技巧是使用 help 子命令 查看某个子命令的帮助说明:
bt:输出进程的内核栈函数调用关系
-t:显示栈中所有文本符号-f:显示每一栈帧里的数据-l:显示文件名和行号pid:显示指定PID进程的内核栈的函数调用信息
dis:用来输出反汇编结果
rd:读取内存地址中的值
struct:显示内核中数据结构的定义或者具体的值
struct xxx -o来显示每个成员的偏移量
另外,struct xxx -x addr,可以指定一个地址,用来按照struct的格式以16进制显示每个成员的值,这个在实战中十分的有用。
p:输出内核变量、表达式或者符号的值
p jiffies:输出jiffies的值
p init_mm:输出进程0的mm数据结构的值
p irq_stat:如果一个变量时Per-CPU类型,那么会输出所有Per-CPU变量的地址,p irq_stat:0用来输出某个CPU上内核符号的值
irq:显示中断相关信息
index:显示某个指定irq的信息-b:显示中断下半部信息-s:显示系统中断信息
task:用来显示进程task_struct数据结构和thread_info数据结构的内容,其中-x表示按照16进制显示。
task -R on_cpu,on_rq,comm 709:查看进程中task_struct中的成员
vm:显示进程的地址空间相关信息
-p:显示虚拟地址和物理地址-m:显示mm_struct数据结构-R:搜索特定字符串或者数值-v:显示该进程中所有vm_area_struct数据结构的值-f num:显示数字在vm_flags中对应的位
kmem:显示系统的内存信息
-i:显示系统内存的使用情况-v:显示系统vmalloc的使用情况-V:显示系统vm_stat情况-s:显示slab使用情况-p:显示每个页面的使用情况-g:显示page数据结构里flags的标志位
list:遍历链表,并且可以输出链表成员的值
-h:指定链表头(list_head)的地址-s:用来输出链表成员的值
- 统计内存的使用情况
统计所有用户进程一共占用了多少内存:
ps -u | awk '{ total += $8 } END { printf "Total RSS of user-mode: %.02f GB\n", total/2^20 }'
统计哪些进程占用的内存最多:
ps -u | awk '{ m[$9] += $8 } END { for (item in m) { printf "%20s %10s KB\n", item, m[item] } }' | sort -k 2 -r -n
- 查看进程状态
ps | grep UN:查看系统有哪些进程处于不可中断状态
ps | grep RU:查看系统有哪些进程处于运行状态
ps | grep -c RU:查看系统处于运行状态的进程个数
foreach:遍历系统中所有进程并且做一些统计方面的工作
foreach RU bt:遍历系统所有处于运行状态进程的堆栈
- 查看CPU和进程时间戳
runq -t:查看每个CPU上最近运行的时间戳与task
ps -l:查看每个进程最近运行的时间戳
- 查看dmesg log