Qemu 简单应用 电脑版发表于:2024/7/5 11:57 ![](https://img.tnblog.net/arcimg/hb/b3f08146d754401abe266c095a266fcb.png) >#Qemu 简单应用 [TOC] Qemu简单示范 ------------ tn2>创建一个工作目录`armv8easy`。 ```bash mkdir armv8easy cd armv8easy/ code . ``` tn2>给vscode安装相关ARM支持的汇编高亮插件。 ![](https://img.tnblog.net/arcimg/hb/4195ab11429f47fa926e41f4a15f472a.png) tn2>创建`start.S`汇编文件,以及Makefile文件,`launch.json`。 ![](https://img.tnblog.net/arcimg/hb/26d45b2b3770404186c782f348749a13.png) ```bash .globl start _start: mov x0,#1 ldr w1,=0x778899 add x0,x0,x1 mrs x3,CurrentEl reset_end: b reset_end ``` ```bash CC = aarch64-linux-gnu-gcc # 定义变量 CC,指定使用 aarch64-linux-gnu-gcc 作为 C 编译器 LD = aarch64-linux-gnu-ld # 定义变量 LD,指定使用 aarch64-linux-gnu-ld 作为链接器 CFLAGS = -g -O0 -nostdlib -nodefaultlibs # 定义变量 CFLAGS,包含编译选项:-g 调试信息,-O0 无优化,-nostdlib 禁用标准库,-nodefaultlibs 禁用默认库 start: start.o # 创建名为 start 的目标,依赖于 start.o 文件 $(LD) -o $@ $^ # 使用 LD 变量指定的链接器,将所有依赖文件($^,这里是 start.o)链接为可执行文件 $@ %.o: %.S # 创建一个模式规则,用于生成 .o 文件(依赖于同名的 .S 汇编文件) $(CC) $(CFLAGS) -c $< -o $@ # 使用 CC 变量指定的编译器和 CFLAGS 变量中的选项编译 .S 文件,并输出为 .o 文件 .PHONY: clean # 声明一个伪目标 .PHONY,用来标记 clean 不是一个实际的文件名 clean: # 创建名为 clean 的目标 -rm start.o # 使用 -rm 命令删除 start.o 文件,忽略文件不存在的错误 -rm start # 使用 -rm 命令删除 start 可执行文件,忽略文件不存在的错误 ``` ```bash { "version": "0.2.0", "configurations": [ { "name": "(gdb) 启动", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/start", "args": [], "stopAtEntry": true, "cwd": "${fileDirname}", "environment": [], "externalConsole": false, "MIMode": "gdb", // 如果远程登入到linux 服务器上面,路径不用写 /user/bin/gdb-multiarch "miDebuggerPath": "gdb-multiarch", "miDebuggerServerAddress": "localhost:1234", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } ``` tn2>使用`make`进行构建。 ![](https://img.tnblog.net/arcimg/hb/9347ef97718d44389d7666b018957ab7.png) tn2>发现缺少`gcc-aarch64-linux-gnu`,需要安装一下。 ```bash sudo apt-get update sudo apt-get install gcc-aarch64-linux-gnu ``` tn2>再次`make`成功了 ![](https://img.tnblog.net/arcimg/hb/99a97c4d423049a1a9e65a7794504008.png) tn2>接下来我们打开一个窗口运行这个命令,使用Qemu创建一个模拟器。 | 选项 | 含义 | |-------------------|----------------------------------------------------------------------| | `qemu-system-aarch64` | QEMU 模拟器命令行工具,用于模拟 ARM 64 位架构的系统。 | | `-machine virt` | 指定使用 `virt` 机器模型,用于模拟 ARM 虚拟平台。 | | `-cpu cortex-a57` | 指定使用 Cortex-A57 处理器核心。 | | `-nographic` | 禁用图形显示,所有输出发送到控制台终端。 | | `-kernel start` | 加载名为 `start` 的内核文件。 | | `-S` | 启动时暂停,等待调试器连接。 | | `-s` | 开启 GDB 服务器,监听默认端口 1234,等待 GDB 连接。 | ```bash qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -kernel start -S -s ``` tn2>按F5启动调试报错,找了一下`/usr/bin/gdb-multiarch`没有找到,所以需要安装。 ![](https://img.tnblog.net/arcimg/hb/de1deb2f22024b8d85dc2cf726abec61.png) ```bash sudo apt install gdb-multiarch -y ``` ![](https://img.tnblog.net/arcimg/hb/caf9790a96654420aaa930f296f82975.png) tn2>再次运行。 ![](https://img.tnblog.net/arcimg/hb/0505a25cba3143639f38f78ba7ca1c20.png) ![](https://img.tnblog.net/arcimg/hb/83c1846ab8f4425cab277ab74c58b709.png) tn2>我们可以看到它是兼容x32位的,并且能将当前CPU异常级(Current Exception Level,CurrentEL)的值加载到寄存器 x3 中。 而`gdb`是达不到这一层的,会直接报错。 ```bash .globl start _start: ldr x0,=0x11223344556677 ldr w1,=0x11223344556677 add x0,x0,x1 mrs x3,CurrentEl reset_end: b reset_end ``` ![](https://img.tnblog.net/arcimg/hb/0ebb54920c7047559c079dbf65955562.png) tn>通过修改代码我们会发现`w1`只能存储后8位,而`x0`是可以存储16位的。 tn2>通过`bl`跳转到的sum方法中,lr(x30)将会保存下一条`mrs x3,CurrentEl`的地址,当在`ret`执行后会将lr的地址赋值给`pc`。 ```bash .globl start _start: ldr x0,=0x11223344556677 ldr w1,=0x11223344556677 add x0,x0,x1 bl sum mrs x3,CurrentEl reset_end: b reset_end sum: mov x0,#0x33 ret ``` ![](https://img.tnblog.net/arcimg/hb/9902f103c9d94aee894d583330668b9c.png) ![](https://img.tnblog.net/arcimg/hb/d036c1cc2e404afcb7b6fd10f2e63f64.png) ![](https://img.tnblog.net/arcimg/hb/ad55ade610764aa68b7ca53ed7195a84.png) tn2>`xzr`特殊寄存器,永远返回`0`。 ```bash sum: mov x0,#0x33 mov x0,xzr ret ``` ![](https://img.tnblog.net/arcimg/hb/7b1a1ea87d19479ba233d90667ccbbfe.png) ![](https://img.tnblog.net/arcimg/hb/c4449f9a298d430b8c36440b190e0639.png) tn2>获取DAIF寄存器包含4个标志位,分别用于控制不同类型的中断。 获取SPSel寄存器用于选择当前栈指针。 ![](https://img.tnblog.net/arcimg/hb/638a6f9e439f4d489711b4b7a7f5eb3b.png) tn2>DAIF值为`0x3c0`,D, A, I, F四个屏蔽位都为0,这意味着调试异常、同步错误中断、普通中断和快速中断都没有被屏蔽。当前SPSel为1,通常表示在内核模式下。