概要
本文提供了一些运行QEMU的ESP特定说明。有关QEMU的一般使用问题,请参阅官方文档:https://www.qemu.org/documentation/.
编译 QEMU
准备工作
在此之前,请查看有关构建先决条件的QEMU文档。如果你在Linux主机上构建QEMU,你可以参考 这篇文章.
请确保您的系统上安装了libgcrypt ( Ubuntu 中的libgcrypt-devel
, Arch 中的 libgcrypt
, macOS Homebrew 中的libgcrypt
).
配置
要生成 ninja
构建文件,我们需要先配置项目,可以使用以下命令:
./configure --target-list=xtensa-softmmu
--enable-gcrypt
--enable-slirp
--enable-debug --enable-sanitizers
--enable-sdl
--disable-strip --disable-user
--disable-capstone --disable-vnc
--disable-gtk
为了减少第一次编译时间,可以添加的 --disable
选项。可启用或禁用的选项的完整列表可以通过 ./configure --help
命令获取。
If you need a graphical interface for the virtual machine, make sure to provide --enable-sdl
or --enable-gtk
or both.
如果您需要虚拟机的图形界面,请添加 --enable-sdl
或 --enable-gtk
配置,或两者都添加。
编译
成功配置项目后,可以使用 ninja
来构建它:
ninja -C build
编译可能需要几分钟,具体取决于之前启用或禁用的组件。编译完成后,会创建build/qemu-system-xtensa
可执行文件。
编译ESP-IDF程序进行仿真
ESP32目标的QEMU已经准备就绪,它已经包括位于真实芯片ROM上的第一级引导加载程序,主要负责初始化外围设备,如UART,更重要的是SPI Flash。还必须包含第二阶段引导加载程序和要运行的程序。
因此,在本节中,我们将创建一个flash映像,该映像结合了(第二阶段)引导加载程序、分区表和要运行的应用程序。这可以使用esptool.py merge_bin
命令完成,该命令在esptool.py
3.1或更高版本中受支持。假设ESP IDf项目刚刚成功编译,以下命令将创建该镜像:
cd build
esptool.py --chip esp32 merge_bin --fill-flash-size 4MB -o flash_image.bin @flash_args
这里,flash_args
是ESP-IDF构建系统在构建目录中生成的一个文件,它包含二进制文件的名称和相应的闪存地址列表。merge_bin
命令获取此列表并创建整个闪存镜像 --fill-flash-size 4MB
参数指定了总闪存大小。
QEMU中的ESP32目标支持大小为2、4、8和16MB的闪存,创建任何其他大小的镜像都会导致错误。
注意
对于ESP-IDF中的 “Secure Boot” 功能,我们建议对flash bootloader使用单独的命令,因此flash_args
文件没有相应的条目。但是,您可以修改flash_args
文件,为bootloader.bin
添加条目,如下所示:
0x1000 bootloader/bootloader.bin
也可以使用esptool.py
将应用程序加载到QEMU中,但QEMU需要以正确的捆绑模式启动。
运行 QEMU
不加载GDB
如果你不需要调试客户应用程序,你可以在不加载GDB的情况下执行QEMU:
代码语言:javascript复制build/qemu-system-xtensa -nographic
-machine esp32
-drive file=flash_image.bin,if=mtd,format=raw
其中flash_image.bin
是之前生成的SPI闪存镜像。
使用GDB服务器,等待连接
如果需要调试客户应用程序,可以使用-s -S
选项执行QEMU。这个命令告诉QEMU在初始化虚拟机之后不要启动CPU。它将等待来自GDB客户端的连接:
build/qemu-system-xtensa -nographic -s -S
-machine esp32
-drive file=flash_image.bin,if=mtd,format=raw
其中flash_image.bin
是之前生成的SPI闪存镜像。
然后,要连接GDB客户端,请使用以下命令:
代码语言:javascript复制xtensa-esp32-elf-gdb build/app-name.elf
-ex "target remote :1234"
-ex "monitor system_reset"
-ex "tb app_main" -ex "c"
最后一行在客户应用程序的app_main
函数中设置断点,并用c
启动虚拟CPU。如果您需要在任何其他函数中设置断点,或者不需要直接启动CPU,请调整最后一行。
硬件加密支持
从IDF 4.1开始,默认启用以下硬件加密功能:AES、SHA、RSA。 所有这些都在QEMU中实现了ESP32目标。但是,请注意,SHA模拟目前不支持不同SHA类型的并发操作。
以太网口支持
ESP-IDF中添加了对Opencores以太网MAC的支持。
- 运行以太网示例时,启用
CONFIG_EXAMPLE_CONNECT_ETHERNET
和CONFIG_EXAMPLE_USE_OPENETH
.。 - 运行自定义应用程序时,启用
CONFIG_ETH_USE_OPENETH
并初始化以太网驱动程序,如示例 /common_components/protocol_example.common/connect.c 中所示(查找esp_eth_mac_new_openeth
)。
启动QEMU时,使用open_eth
网络设备。
用户模式网络
例如,要在用户模式下启动网络(仅TCP/UDP,模拟设备位于NAT之后),请在QEMU命令行中添加以下选项:
代码语言:javascript复制-nic user,model=open_eth
一些ESP项目(特别是运行TCP侦听器)可能需要设置端口转发,
代码语言:javascript复制-nic user,model=open_eth,id=lo0,hostfwd=tcp:127.0.0.1:PORT_HOST-:PORT_GUEST
(例如,asio-echo服务器默认在2222上设置服务器,因此hostfwd=tcp:127.0.0.1:22222-:2222
允许从主机访问 nc localhost 2222
)
指定引导模式
要指定所需的 strapping 模式, 在运行QEMU时需要添加以下参数:
代码语言:javascript复制-global driver=esp32.gpio,property=strap_mode,value=0x0f
这将设置 GPIO_STRAP
寄存器的值。
- 使用
0x12
作为闪存启动模式(默认) - 将
0x0f
用于仅UART下载模式(因为SDIO部分未实现)
Specifying eFuse storage
Add extra arguments to the command line:
代码语言:javascript复制-drive file=qemu_efuse.bin,if=none,format=raw,id=efuse
-global driver=nvram.esp32.efuse,property=drive,value=efuse
The first argument creates a block device backed by qemu_efuse.bin
file, with identifier efuse
. The second line configures nvram.esp32.efuse
device to use this block device for storage.
The file must be created before starting QEMU:
代码语言:javascript复制dd if=/dev/zero bs=1 count=124 of=/tmp/qemu_efuse.bin
124 bytes is the total size of ESP32 eFuse blocks.
Note
Specifying eFuse storage is mandatory to test out any platform security features like “Secure Boot” or “Flash Encryption”.
Emulating ESP32 ECO3
For the application to detect the emulated chip as ESP32 ECO3, the following virtual efuses must be set:
- CHIP_VER_REV1
- CHIP_VER_REV2
Here is the corresponding efuse file (in hexadecimal, produced using xxd -p
):
000000000000000000000000008000000000000000001000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
00000000
To convert this (efuse.hex
) back to binary, run xxd -r -p efuse.hex qemu_efuse.bin
.
Alternatively, these bits can be set using espefuse:
代码语言:javascript复制espefuse.py --port=socket://localhost:5555 burn_efuse CHIP_VER_REV1
espefuse.py --port=socket://localhost:5555 burn_efuse CHIP_VER_REV2
Disabling the watchdogs
By default, Timer Group watchdog timers are emulated, and TG0 WDT is enabled at reset. It is sometimes useful to disable these watchdog timers. This can be done by adding the following to the command line:
代码语言:javascript复制-global driver=timer.esp32.timg,property=wdt_disable,value=true
This disables the emulation of TG watchdog timers. Even if the application configures them, they will not fire.
The RTC watchdog timer is not emulated yet, so it doesn’t need to be disabled.
Using esptool.py and espefuse.py to interact with QEMU
Start QEMU:
代码语言:javascript复制build/qemu-system-xtensa -nographic
-machine esp32
-drive file=flash_image.bin,if=mtd,format=raw
-global driver=esp32.gpio,property=strap_mode,value=0x0f
-drive file=qemu_efuse.bin,if=none,format=raw,id=efuse
-global driver=nvram.esp32.efuse,property=drive,value=efuse
-serial tcp::5555,server,nowait
The final line redirects the emulated UART to TCP port 5555 (QEMU acts as a server).
Type q and press Enter at any time to quit.
Run esptool.py:
代码语言:javascript复制esptool.py -p socket://localhost:5555 flash_id
Flashing with idf.py
also works:
export ESPPORT=socket://localhost:5555
idf.py flash
Or, run espefuse.py:
代码语言:javascript复制espefuse.py --port socket://localhost:5555 --do-not-confirm burn_custom_mac 00:11:22:33:44:55
Note: esptool
can not reset the emulated chip using the RTS signal, because the state of RTS is not transmitted over TCP to QEMU. To reset the emulated chip, run system_reset
command in QEMU console (started at step 1).
Specifying ROM ELF file
If -kernel
and -bios
arguments are not given, ESP32 (rev. 3) ROM code will be loaded. This ROM code binary is included in the repository. To specify the ROM code ELF file to load, pass the filename with a -bios <filename>
argument.
Using flash encryption
Self-encryption workflow
In the IDF application, enable CONFIG_SECURE_FLASH_ENC_ENABLED
through menuconfig
, and build it
Build the flash image as per the instructions from the Compiling the ESP-IDF program to emulate section.
Create qemu_efuse.bin
as highlighted in the Specifying eFuse storage section.
Execute qemu-system-xtensa
using the following command:
build/qemu-system-xtensa -nographic -machine esp32
-drive file=/path/to/qemu_efuse.bin,if=none,format=raw,id=efuse
-global driver=nvram.esp32.efuse,property=drive,value=efuse
-drive file=/path/to/flash_image.bin,if=mtd,format=raw
-global driver=timer.esp32.timg,property=wdt_disable,value=true
Adding PSRAM
QEMU “memory size” option can be used to enable PSRAM emulation. By default, no PSRAM is added to the machine. You can add 2MB or 4MB PSRAM using -m 2M
or -m 4M
command line options, respectively.
Note that PSRAM MMU is not emulated yet, so things like bank switching (himem
in IDF) do not work.
Using SD cards
QEMU emulates SD/MMC host controller used in ESP32. To add an SD card to the system, create an image and pass it to QEMU.
Create a raw image file, for example, 64 MB:
代码语言:javascript复制$ dd if=/dev/zero bs=$((1024*1024)) count=64 of=sd_image.bin
Add the following argument when running QEMU:
代码语言:javascript复制-drive file=sd_image.bin,if=sd,format=raw
If you need to create a large SD card image, it is recommended to use sparse cqow2
images instead of raw ones. Consult QEMU manual about qemu-img
tool for details.
Only one SD card is supported at a time. You can use either slot 0 or slot 1 of the SD/MMC controller in the application code.
Enabling graphical user interface (GUI)
The ESP32 QEMU implementation implements a virtual RGB panel, absent on the real hardware, that can be used to show graphical interface. It is associated to a virtual frame buffer that can be used to populate the pixels to show. It is also possible to use the target internal RAM as a frame buffer.
To enable the graphical interface, while keeping the serial output in the console, use the following command line:
代码语言:javascript复制build/qemu-system-xtensa
-machine esp32
-drive file=flash_image.bin,if=mtd,format=raw
-display sdl
-serial stdio
If gtk
backend was enabled when compiling QEMU, it is possible to replace -display sdl
with -display gtk