基于QEMU的内核调试环境搭建

背景

在没有相应的实体硬件,只有自己的一台开发机器,在学习内核或是调试破坏性大的内核功能时,又不想用庞大麻烦的Virtualbox或VMware,只是简单单纯地调试下内核,QEMU是个不错的选择。

关于QEMU

QEMU is a generic and open source machine emulator and virtualizer.

When used as a machine emulator, QEMU can run OSes and programs made for one machine (e.g. an ARM board) on a different machine (e.g. your own PC). By using dynamic translation, it achieves very good performance.

When used as a virtualizer, QEMU achieves near native performance by executing the guest code directly >on the host CPU. QEMU supports virtualization when executing under the Xen hypervisor or using the >KVM kernel module in Linux. When using KVM, QEMU can virtualize x86, server and embedded PowerPC, >64-bit POWER, S390, 32-bit and 64-bit ARM, and MIPS guests.

这里虚拟一个与Host 相同架构的x86机器,也可以使用其他架构,比如 riscv等,只是需要相应交叉编译,使用相应的qemu程序,后面会写一篇基于RISC-V的。

环境

  • OS:Ubuntu20.04 x86_x64
  • QEMU: 6.2.0

安装QEMU

使用如下命令安装QEMU:

$ sudo apt install qemu qemu-system qemu-kvm

准备文件系统

根据自己的需求大致有4种方式做文件系统:

  • 使用自己的程序: 适用于不需要任何核外环境的情况
  • 使用busybox:适用于只需要最基本的核外环境,最小文件系统,无需做较多修改定制的情况
  • 使用buildroot:适用核外环境需要做较多修改定制的情况
  • 使用现成发行版的镜像:适用于需要最完善的核外环境的情况

这里主要测试了前3种情况。

使用自己的程序

如果不需要任何核外,可以直接使用一个最简单的while死循环的helloworld程序替代:

hello.c

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("Hello World!\n");
    fflush(stdout);
    while(1) {sleep(1);}
    return 0;
}

编译并制作rootfs:

#编译 helloworld
$ gcc --static -o hello hello.c

#打包ramfs作为rootfs
$ echo hello | cpio -o --format=newc > hello.rootfs

使用busybox

这里需要一个相对完善点的核外环境,所以基于busybox制作一个最基本的文件系统。

获取busybox源码:

$ git clone https://gitee.com/mirrors/busyboxsource.git

配置:

$ make menuconfig
#select `Settings -> Build Options -> Build static binary (no shared libs)`

编译:

$ make -j8
$ make install

会在源码目录生成编译输出文件夹_install

使用buildroot

首先安装一些依赖包,报错需要安装啥就安装啥

#拉取源码
$ git clone https://gitlab.com/buildroot.org/buildroot.git

#配置
$ make menuconfig
#根据自己的需要修改配置
# select `Target Options -> Target Architecture -> x86_64`
# select `Filesystem images -> ext2/3/4 root file system -> ext4`

#编译
make

生成的rootfs就在:output/images/rootfs.ext4

制作基于ramfs的rootfs

$ cd rootfs
$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

制作磁盘文件的rootfs

这里需要持久化,所以使用磁盘文件做文件系统。
如果不需要持久化,只需要打包成 ramfs 即可。

生成img文件

#生成 qemu image
$ qemu-img create rootfs.img  1g
#格式化为 ext4
$ mkfs.ext4 rootfs.img

添加和修改img内容

添加和修改 rootfs 内容,这里以 busybox 为例,其他的类似:

$ mkdir rootfs
$ sudo mount -o loop rootfs.img  rootfs

#这里以 busybox 为例,其他的类似
$ sudo cp -rfd ../busyboxsource/_install/* .
$ sudo mkdir proc sys dev etc etc/init.d

新建一个简单的init的 rc 文件(etc/init.d/rcS),内容如下:

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s

修改 rc 文件权限:

$ sudo chmod +x etc/init.d/rcS

卸载img

$ sudo umount rootfs

至此,文件系统就准备完成。

准备内核

这里拉取的是清华源的 stable 分支:

$ git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git

按照正常流程配置编译:

#配置
$ make x86_64_defconfig
#根据自己的需要修改配置
$ make menuconfig
#编译
$ make -j8

最终会在arch/x86_64/boot/bzImage目录生成我们需要的内核

运行

#基于ramfs运行qemu 
$ qemu-system-x86_64 \
    -kernel ./bzImage \
    -initrd ./hello.rootfs \
    -append "root=/dev/ram rdinit=/hello console=ttyS0" \
#   -initrd ./initramfs.cpio.gz \
#  	-append "console=ttyS0" \
    -nographic


#基于磁盘文件运行qemu 
$ qemu-system-x86_64  \
    -kernel ./bzImage \
    -drive file=rootfs.img,format=raw \
#   -drive file=rootfs.ext4,format=raw \
    -append "root=/dev/sda rw console=ttyS0" \
    -nographic

退出 qemu 使用组合键: Ctrl+A X

至此基于QEMU的内核调试环境简单搭建就完成了。

参考