QEMU 是一套由法布里斯·贝拉(Fabrice Bellard)所编写的以 GPL 许可证分发源码的模拟处理器,在GNU/Linux 平台上使用广泛。简单来说,QEMU 是一个虚拟机,与常见的 Vmware/VirtualBox 不同的是,QEMU 可以模拟不同平台的硬件,使得我们在 x86 设备上可以运行其他架构的程序。
本文主要讲述如何编译符合 qemu 要求的内核,使用 qemu 成功运行内核。我们需要在 Linux 环境下安装交叉编译工具链,用于在 x86 平台上编译 arm 架构的内核;也要解决其中的依赖问题;如果有需要,还要升级系统自带的 qemu(系统自带版本较高,可以不用重新安装)。如果能够成功的运行内核,后续还可以在 qemu 模拟的环境中,挂载相应的根文件系统,这样就可以完整的运行一个 arm 架构的 linux 系统。
0x10 前期准备
0x11 编译内核需要的依赖及工具
下载配置内核需要的依赖
sudo apt-get install ncurses-devel libncurses-devel flex bison bc
安装交叉工具编译链
sudo apt-get install gcc-arm-linux-gnueabi
# 此工具用来编译生成 arm32 可执行程序
0x12 编译 QEMU
Ubuntu 以及 Kali 自带了 qemu,如果你想升级 qemu 的版本,可以源码安装。
安装依赖
sudo apt-get install zlib1g-dev libglib2.0-0 libglib2.0-dev libtool libsdl1.2-dev autoconf
sudo apt install libsdl2-dev -y # 不安装可能会出现 VNC Server running ":1:5900"
解压
tar -xvf qemu-4.2.0.tar.xz
为了防止编译后文件比较乱,选择创建 build 目录作为编译中间目标路径
cd qemu-4.2.0/
mkdir build
配置、编译并安装 qemu
./configure --prefix=./build --target-list=arm-softmmu,arm-linux-user --enable-debug
make -j$(nproc)
make install
–target-list 指定要编译的target(guest),arm-softmmu 表示要编译 system mode 的 arm qemu;arm-linux-user 表示要编译 user mode 的 arm qemu。
如果要重新编译,请删除原先的编译中间文件
sudo make clean
sudo make distclean
查看 qemu 支持的开发板
qemu-system-arm -M help
0x20 编译内核
下载内核,这里推荐几个下载内核源码的镜像站点:
https://mirrors.edge.kernel.org/pub/linux/kernel/http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/
0x21 方案一:编译生成与平台无关的内核
进入源码根目录,配置和编译
#(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
就是我们编译好的内核。
0x22 方案二:编译生成特定开发板(qemu)运行的内核
使用 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
...
0x23 方案三:手动配置内核源码并编译
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
0x30 QEMU 加载内核
使用方案二生成的内核压缩文件
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
参数说明
- -M 指定开发板
- -m 指定内存大小
- -kernel 指定内核文件
- -dtb 指定dtb文件
- -nographic 指定不需要图形界面
- -append 指定扩展显示界面,串口或者LCD,“console=ttyAMA0” 内核启动参数,这里告诉内核vexpress单板运行,串口设备是哪个tty
0x31 不开启图形终端
为了在图形窗口中显示,我们需要传递 console=tty1
内核参数。这个内核参数将会被 qemu 通过 -append
选项传递给 Linux。如果不指定 -nographic
,上述命令会打开 qemu 并打开一个黑色的控制台窗口,通过一个 Tuxlogo 来显示图形能力。启动信息将会在这个图形窗口显示。
内核成功启动,如下图,不过我们还没有指定根文件系统,所以报错
0x32 重定向模拟系统的串口
-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 \
0x33 开启远程登录
-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
0x34 后续操作
后续就是加上 -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
0x40 内核挂载根文件系统
内核运行成功后,需要根文件系统的支撑,才能形成一个完整的系统。这里,我们使用 busybox 制作一个最小根文件系统,加深我们对根文件系统的理解。
0x41 使用 busybox 制作根文件系统
1 下载和编译 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
2 创建 rootfs 目录
busybox 装完成后,会在 busybox 目录下生成 _install 目录,该目录下的程序就是单板运行所需要的命令。
拷贝该目录下的文件
rm linuxrc # 此文件可删除
cp -r * ~/Documents/rootfs/
3 复制动态链接库
从交叉编译器中拷贝所需要的动态链接库,复制到 lib 库目录下(如果 busybox 是静态编译的话,可以忽略此步骤)
lys@kali:~/Documents/rootfs$ mkdir lib
lys@kali:~/Documents/rootfs$ sudo cp -r /usr/arm-linux-gnueabi/lib/* ./lib/
4 测试目标二进制程序是否能够运行
使用 qemu-user 模式测试 busybox 是否能够运行。新版 kali 没有内置 user 模式的 qemu ,需要我们下载安装之后,再进行测试
sudo apt-get install qemu-user
qemu-arm 测试 busybox
测试成功之后,需要新建终端,因为刚刚的 export QEMU
命令已经修改了系统的库目录路径,为了重新设置环境变量,需要新建终端,再进行后续的操作步骤
5 创建 4 个 tty 终端设备
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
6 生成映象文件
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
7 将 rootfs 挂载到制作的 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
0x42 挂载根文件系统
在挂在根文件系统之前,先使用上一章的方法启动内核,看看内核中支持的块设备的名称
以 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
系统成功启动
0x50 Q&A 可能遇到的问题
0x51 编译内核
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/benh@debian.org.cert.pem"
0x52 编译 busybox
Trying libraries: m resolv
这个其实代表编译成功,因为没有打印 make error
0x53 加载文件系统
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
0x60 总结
使用 qemu 模拟硬件设备,运行内核,并不是我们的最终目的,后续要做的是提取根文件系统,并将其挂在,这样就可以实现一个完整的设备模拟。