QEMU 是一套由法布里斯·贝拉(Fabrice Bellard)所编写的以 GPL 许可证分发源码的模拟处理器,在GNU/Linux 平台上使用广泛。简单来说,QEMU 是一个虚拟机,与常见的 Vmware/VirtualBox 不同的是,QEMU 可以模拟不同平台的硬件,使得我们在 x86 设备上可以运行其他架构的程序。
本文主要讲述如何编译符合 qemu 要求的内核,使用 qemu 成功运行内核。我们需要在 Linux 环境下安装交叉编译工具链,用于在 x86 平台上编译 arm 架构的内核;也要解决其中的依赖问题;如果有需要,还要升级系统自带的 qemu(系统自带版本较高,可以不用重新安装)。如果能够成功的运行内核,后续还可以在 qemu 模拟的环境中,挂载相应的根文件系统,这样就可以完整的运行一个 arm 架构的 linux 系统。
下载配置内核需要的依赖
sudo apt-get install ncurses-devel libncurses-devel flex bison bc
安装交叉工具编译链
sudo apt-get install gcc-arm-linux-gnueabi
# 此工具用来编译生成 arm32 可执行程序
Ubuntu 以及 Kali 自带了 qemu,如果你想升级 qemu 的版本,可以源码安装。
安装依赖
sudo apt-get install zlib1g-dev libglib2.0-0 libglib2.0-dev libtool libsdl1.2-dev autoconf
解压
tar -xvf qemu-4.2.0.tar.xz
为了防止编译后文件比较乱,选择创建 build 目录作为编译中间目标路径
cd qemu-4.2.0/
mkdir build
cd build/
配置、编译并安装 qemu
../configure --target-list=arm-softmmu --audio-drv-list=
make
make install
下载内核,这里推荐几个下载内核源码的镜像站点:
https://mirrors.edge.kernel.org/pub/linux/kernel/
http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/
进入源码根目录,配置和编译
#(1)清除原有的配置与中间文件
make distclean
#(2)配置内核,并生成配置文件
make menuconfig ARCH=arm
#(3)编译内核
make uImage ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all
编译内核的时候,既可以选择 uImage,也可以不填此选项,这样编译的时候,会提示用户,选择使用哪种内核压缩模式。笔者在这里选择的是 zImage,最后生成的文件如下
~/Documents/linux-5.6.6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all
scripts/kconfig/conf --syncconfig Kconfig
CALL scripts/checksyscalls.sh
CALL scripts/atomic/check-atomics.sh
CHK include/generated/compile.h
Kernel: arch/arm/boot/Image is ready
Kernel: arch/arm/boot/zImage is ready
MODPOST 17 modules
也就是说,arch/arm/boot/zImage
就是我们编译好的内核。
使用 arch/arm/configs/versatile_defconfig
文件的配置,versatile_defconfig 的内容将被 copy 到 .config 中,这样生成的内核文件可以直接使用 qemu 进行仿真。
#(1)清除原有的配置与中间文件
make distclean
#(2)配置内核,并生成配置文件
make ARCH=arm versatile_defconfig # vexpress_defconfig
make menuconfig ARCH=arm
#(3)编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all
versatile_defconfig
是指 ARM Versatile Express 开发板的配置,我们也可以用其他硬件仿真。qemu -m
命令可查看当前 qemu 支持仿真的硬件平台,即开发板
lys@kali:~/Documents$ qemu-system-arm -machine help
Supported machines are:
akita Sharp SL-C1000 (Akita) PDA (PXA270)
ast2500-evb Aspeed AST2500 EVB (ARM1176)
ast2600-evb Aspeed AST2600 EVB (Cortex A7)
borzoi Sharp SL-C3100 (Borzoi) PDA (PXA270)
...
tosa Sharp SL-6000 (Tosa) PDA (PXA255)
verdex Gumstix Verdex (PXA270)
versatileab ARM Versatile/AB (ARM926EJ-S)
versatilepb ARM Versatile/PB (ARM926EJ-S)
vexpress-a15 ARM Versatile Express for Cortex-A15
vexpress-a9 ARM Versatile Express for Cortex-A9
...
make vexpress_defconfig ARCH=arm O=./object
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig -j4 O=./object
生成以下两个我们需要的文件
arch/arm/boot/zImage
arch/arm/boot/dts/vexpress-v2p-ca9.dtb
使用方案二生成的内核压缩文件
qemu-system-arm -M versatilepb -m 256M -kernel linux-5.6.6/arch/arm/boot/zImage -nographic -dtb linux-5.6.6/arch/arm/boot/dts/versatile-pb.dtb -append "console=ttyAMA0"
用方案三生成的内核压缩文件以及配置文件
qemu-system-arm -M vexpress-a9 -m 512M -kernel linux-5.6.6/arch/arm/boot/zImage -dtb linux-5.6.6/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -append "console=ttyAMA0" -serial stdio
参数说明
为了在图形窗口中显示,我们需要传递 console=tty1
内核参数。这个内核参数将会被 qemu 通过 -append
选项传递给 Linux。如果不指定 -nographic
,上述命令会打开 qemu 并打开一个黑色的控制台窗口,通过一个 Tuxlogo 来显示图形能力。启动信息将会在这个图形窗口显示。
内核成功启动,如下图,不过我们还没有指定根文件系统,所以报错
-nographic
是为了让系统直接输出,不要可视化界面,但是这样的话,要关闭 qemu 模拟的系统,只能通过 kill
方式。qemu 可以重定向主机上的模拟系统的串口,使用选项 -serial stdio
,则 Linux 可以通过传递 console=ttyAMA0
作为内核参数而在第一个串口中显示它的信息。
qemu-system-arm -M versatilepb -m 256M -kernel linux-5.6.6/arch/arm/boot/zImage \
-dtb linux-5.6.6/arch/arm/boot/dts/versatile-pb.dtb \
-append "console=ttyAMA0" \
-serial stdio \
-nographic
还可以用 -serial telnet::2020,server,nodelay
替代,这样可以另开一个终端,通过输入 telnet 127.0.0.1 2020
的方式连接到 qemu 虚拟机,此方法会有三个终端存在
emu-system-arm -M versatilepb -m 256M -kernel linux-5.6.6/arch/arm/boot/zImage \
-dtb linux-5.6.6/arch/arm/boot/dts/versatile-pb.dtb \
-append "console=ttyAMA0" \
-serial telnet::2020,server,nodelay
后续就是加上 -initrd 参数选项指定固件中的根文件系统,就可以仿真固件。如果你觉得命令过于复杂,写出脚本就可以了
# boot.sh
#! /bin/sh
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ~/qemu/zImage \
-dtb ~/qemu/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd rootfs.ext3
内核运行成功后,需要根文件系统的支撑,才能形成一个完整的系统。这里,我们使用 busybox 制作一个最小根文件系统,加深我们对根文件系统的理解。
从 busybox 官网 下载 busybox ,解压
tar -jxvf busybox-1.31.1.tar.bz2
cd busybox-1.31.1/
编译,busybox 编译的方式与内核编译类似,在配置文件中,建议选择静态编译
make distclean
make menuconfig ARCH=arm
# menuconfig begin
Settings -->
[*] Build static binary (no shared libs)
# menuconfig end
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all install
如果你嫌麻烦,可以直接使用默认配置,默认配置是使用动态链接的方式进行编译
make distclean
make defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all install
busybox 默认安装到 ./_install
目录下
lys@kali:~/Documents/busybox-1.31.1/_install$ ls
bin linuxrc sbin usr
busybox 装完成后,会在 busybox 目录下生成 _install 目录,该目录下的程序就是单板运行所需要的命令。
拷贝该目录下的文件
rm linuxrc # 此文件可删除
cp -r * ~/Documents/rootfs/
从交叉编译器中拷贝所需要的动态链接库,复制到 lib 库目录下(如果 busybox 是静态编译的话,可以忽略此步骤)
lys@kali:~/Documents/rootfs$ mkdir lib
lys@kali:~/Documents/rootfs$ sudo cp -r /usr/arm-linux-gnueabi/lib/* ./lib/
使用 qemu-user 模式测试 busybox 是否能够运行。新版 kali 没有内置 user 模式的 qemu ,需要我们下载安装之后,再进行测试
sudo apt-get install qemu-user
qemu-arm 测试 busybox
测试成功之后,需要新建终端,因为刚刚的 export QEMU
命令已经修改了系统的库目录路径,为了重新设置环境变量,需要新建终端,再进行后续的操作步骤
mknod 用于创建 linux 中的字符设备和块设备,tty1 是设备的名字,c 是指块设备,4 是主设备号 /dev/devices 里面记录现有的设备,1 表示第一个子设备
$ mkdir ./dev
$ sudo mknod ./dev/tty1 c 4 1
$ sudo mknod ./dev/tty2 c 4 2
$ sudo mknod ./dev/tty3 c 4 3
$ sudo mknod ./dev/tty4 c 4 4
lys@kali:~/Documents$ dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=16
像 /dev/nul l一样,/dev/zero 也是一个伪文件,但它实际上产生连续不断的 null 的流(二进制的零流,而不是ASCII型的)。写入它的输出会丢失不见,
/dev/zero
主要的用处是用来创建一个指定长度用于初始化的空文件,像临时交换文件。
格式化生成 ext3 文件系统
mkfs.ext3 a9rootfs.ext3
方法一:
sudo mkdir tmpfs
# -o loop=:使用 loop 模式用来将一个档案当成硬盘分割挂上系统。
sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs
方法二:
sudo mount -t ext3 a9rootfs.ext3 rootfs/ -o loop
sudo umount rootfs
在挂在根文件系统之前,先使用上一章的方法启动内核,看看内核中支持的块设备的名称
以 versatile 开发板为例,支持的设备以及文件系统如上所示,可以看到,只支持 ext2 ,而非我们刚刚制作的 ext3,重新制作个根文件系统就好了。或者使用 vexpress 开发板,这个开发板功能更为高级,支持挂载 ext3 文件系统。
sudo qemu-system-arm -M versatilepb -m 256M -kernel linux-5.6.6/arch/arm/boot/zImage -dtb linux-5.6.6/arch/arm/boot/dts/versatile-pb.dtb -append "root=/dev/ram0 ttyAMA0" -serial stdio -sd a9rootfs.ext2
root=/dev/..
就是上图中,开发板硬件某个设备的名称。经过验证,verstaile 开发板较老,不能很好的支持我们制作的根文件系统,改换 vexpress,重新编译内核和文件系统,挂载成功。
qemu-system-arm -M vexpress-a9 -m 512M -kernel linux-5.6.6/arch/arm/boot/zImage \
-dtb linux-5.6.6/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-append "root=/dev/mmcblk0 console=ttyAMA0" \
-sd a9rootfs.ext3 -serial stdio
fatal error: openssl/opensslv.h: No such file or directory
这是由于没有下载 openssl 库,下载安装即可
sudo apt-get install libssl-dev
/bin/sh: 1: bc: not found
这是由于缺少计算器程序 bc 造成的,下载安装
sudo apt-get install bc
No rule to make target ‘debian/certs/debian-uefi-certs.pem’
打开 .config 文件,注释下面这一句话
CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/[email protected]"
Trying libraries: m resolv
这个其实代表编译成功,因为没有打印 make error
error while loading shared libraries
在加载 init 文件时出错,此类错误多数是因为符号链接的问题,跟库有关系,建议重新编译 busybox,注意要用静态链接的方式编译。
Kernel panic - not syncing: No working init found
原因一:最小文件系统制作有问题,注意是否生成了目标平台的 busybox
原因二:应用程序通过eabi接口编译,内核需要支持这种接口。因此需要重新编译内核,在 Kernel Feture下选中 Use the ARM EABI to compile the kernel
使用 qemu 模拟硬件设备,运行内核,并不是我们的最终目的,后续要做的是提取根文件系统,并将其挂在,这样就可以实现一个完整的设备模拟。