QEMU的两种模式
QEMU(快速模拟器)是一个开源的机器模拟器和虚拟化器,它能够模拟多种处理器架构,并且可以在不同平台上运行。QEMU 支持两种模式:用户模式和系统模式。
- 用户模式(User Mode):
- 用户模式下的 QEMU 仅模拟用户空间的二进制执行环境,不模拟底层硬件。
- 这种模式下,QEMU 可以运行不同架构编译的二进制程序,而不需要这些程序与宿主机的架构相匹配。
- 用户模式通常用于测试和运行不同架构的应用程序,或者在不支持特定硬件架构的系统上运行程序。
- 用户模式不需要硬件虚拟化支持,因为它不模拟硬件。
- 系统模式(System Mode):
- 系统模式下的 QEMU 模拟整个计算机系统,包括 CPU、内存、硬盘、网络接口等硬件设备。
- 这种模式下,QEMU 可以运行完整的操作系统镜像,允许用户安装和运行完整的操作系统。
- 系统模式需要更多的资源,并且可能需要硬件虚拟化支持,以提高性能和安全性。
- 系统模式允许用户进行更全面的测试,包括操作系统级别的测试和硬件兼容性测试。
- 在系统模式下,QEMU 还可以使用不同的后端来提高性能,例如使用 KVM(Kernel-based Virtual Machine)在 Linux 系统上进行硬件辅助虚拟化。KVM 可以将 QEMU 转换为一个全功能的虚拟机监控器,提供接近原生硬件的性能。
要启动 QEMU 并使用用户模式或系统模式,你需要使用不同的命令行参数。例如:
用户模式:qemu-<arch> -cpu <cpu-type> -L <path-to-libraries> <program-to-run>
系统模式:qemu-system-<arch> -m <memory-size> -hda <disk-image>
其中 <arch> 是你想要模拟的架构,比如aarch64,<cpu-type> 是特定的 CPU 类型,<path-to-libraries> 是库文件的路径,<program-to-run> 是你想要运行的程序,<memory-size> 是分配给虚拟机的内存大小,<disk-image> 是虚拟硬盘镜像的路径。
QEMU 是一个非常灵活的工具,可以根据需要进行配置和扩展,以满足不同的模拟和虚拟化需求。
用户模式的安装
简单的执行如下命令就可以安装用户模式:
sudo apt-get update
sudo apt-get install qemu-user qemu-user-static gdb-multiarch build-essential gcc-aarch64-linux-gnu
用户模式C程序的测试
编写一个简单的C程序:
#include <stdio.h>
int main(void) {
return printf("Hello AARCH64!\n");
}
使用aarch64的编译器进行静态的交叉编译:
aarch64-linux-gnu-gcc -static -o hello64 hello.c
编译后使用file命令确认一下编译产生的事aarch64架构文件。
developer@ecs-cloud-host-ubuntu-img-make:~$ file hello64
hello64: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=c6209c6d4f0f2ec56fc1e9fe2443c34009ad60d5, for GNU/Linux 3.7.0, not stripped
如果没有安装QEMU用户模式程序,直接运行该程序会报告错误:
bash: ./hello64: cannot execute binary file: Exec format error
安装了QEMU用户模式之后,直接运行程序就可以得到正确的输出了。这是因为系统内有qemu-user-static,它支持了其他架构程序的直接运行。
developer@ecs-cloud-host-ubuntu-img-make:~$ ./hello64
Hello AARCH64!
也可以不使用static编译,比如:
aarch64-linux-gnu-gcc -o hello64dyn hello.c
不过不能直接运行hello64dyn这个程序,会报告错误:
developer@ecs-cloud-host-ubuntu-img-make:~$ ./hello64dyn
aarch64-binfmt-P: Could not open '/lib/ld-linux-aarch64.so.1': No such file or directory
这是需要使用qemu-user了:
developer@ecs-cloud-host-ubuntu-img-make:~$ qemu-aarch64 -L /usr/aarch64-linux-gnu ./hello64dyn
Hello AARCH64!
用户模式汇编程序的测试(调用C库)
准备一个简单的汇编程序hello.S
.section .text
.global main
main:
// Call puts and return
stp fp, lr, [sp, #0x10]!
mov fp, sp
adr x0, msg
bl puts
mov w0, wzr
ldp fp, lr, [sp], #0x10
ret
msg:
.asciz "Hello Again!"
.align 2
这个程序里面调用了C语言的puts函数。
前两条指令是所谓的序言,SP 寄存器被调整,以便在可能的中断情况下保存数据,以便使用 puts 函数。
adr 指令将 x0 设置为字符串所在位置的内存地址。
'bl puts' 调用 puts 函数。它是一个 C 库函数,它将文本输出到控制台/终端。或者,可以使用 printf 代替。之所以使用 puts,是因为它对 CPU 来说更快,因为它不能接受所谓的字符串中的格式描述符。此外,puts 函数在控制台上自动加入换行。注意我们没有在 Hello Again 字符串中包含 "\n"。
'mov w0, wzr' 指令是给 CPU 一个零值,它将在退出程序时使用。稍后,CPU 将调用 C/C++ 退出函数来退出程序。每当你编写任何 .S(main:)类型的源文件时,最终的 ret 指令将使 CPU 结束程序并调用 C/C++ 退出函数。在 ret 指令之前,w0 应该总是设置为 0。
最后两条指令是所谓的尾声。基本上是序言的相反。
直接使用下面的语句去编译,C语言的编译器会自动处理汇编代码并进行库链接。
aarch64-linux-gnu-gcc -o hello hello.S
运行程序的方法和前面类似:
developer@ecs-cloud-host-ubuntu-img-make:~$ qemu-aarch64 -L /usr/aarch64-linux-gnu ./hello
Hello Again!
用户模式汇编程序的测试(调用系统调用)
我们也可以不使用C语言函数,直接使用QEMU模拟的系统调用。这和系统模式所模拟的裸机编程有所不同,系统模式是不提供任何系统调用的,你必须自己写串口输出代码或者使用编译好的BIOS文件。
下面是汇编代码:
.section .text
.global _start
_start:
// syscall write(int fd, const void *buf, size_t count)
mov x0, #1
adr x1, msg
mov x2, len
mov w8, #64 /*Syscall number for write for ARM64*/
svc #0
// syscall exit(int status)
mov x0, #0
mov w8, #93 /*Syscall number for exit for ARM64*/
svc #0
msg:
.asciz "Hello World!\n"
len = . - msg
QEMU 模拟所谓的系统调用。这些系统调用旨在模拟具有内置基本任务功能的计算机 BIOS。可以在程序中使用 svc 指令调用。在真正的硬件中,svc 指令将进入一个异常处理程序。在这个程序中,有代码(已经由程序早期编写)来处理 svc 的使用。
在 QEMU 中,这是完全不同的。你的程序在异常处理程序中写了什么并不重要。QEMU 将执行 svc 来模拟一个已经编写了某些指令的计算机 BIOS。简而言之,那里的例程在计算机 BIOS 中是通用的。因此,该例程接受某些参数以产生结果。你可以看出这是情况,因为在上述文件中,我们没有编写任何指令到任何异常处理程序。模拟系统调用的不好之处在于我们不能实际看到和调试 svc 异常。好的是,我们可以使用这些模拟器系统调用来执行基本任务。比如打印文本到控制台(终端),修改文件等。
系统调用 Write 会在控制台(终端)上打印消息。系统调用 Exit 退出程序。
有许多系统调用,每个系统调用都需要参数。在系统调用被“调用”(通过 svc 指令)之前,必须将正确的值放在正确的寄存器中。这种将正确的参数放在正确的寄存器中的概念被称为 ARM64 的调用约定。
代码中一些变量的解释:
- adr 设置 msg 标签所在位置的内存地址。
- msg: 是源文件中 Hello World 字符串所在的标签。字符串中的 "\n" 部分是你在控制台/终端输入新行时需要输入的。
- len = . - msg 这是用于计算字符串大小的汇编器指令。点(.)在这里代表这里。所以 '这里' 减去 msg 将设置字符串的字节量。
要运行程序,需要将其汇编成一个目标文件。然后使用链接器制作一个可执行文件。这可以通过以下两个终端命令完成..
aarch64-linux-gnu-as hello_world.s -o hello_world.o
aarch64-linux-gnu-ld hello_world.o -o hello_world
所以我们现在有一个可执行文件。我们可以用 QEMU 启动它:
qemu-aarch64 ./hello_world
得到如下输出:
developer@ecs-cloud-host-ubuntu-img-make:~$ qemu-aarch64 ./hello_world
Hello World!
因为安装了qemu-user-static,直接运行程序也是可以的。