尧图精选

QEMU仿真Cortex-M33:零硬件搭建MPU验证环境

🕒 发布时间:2026/9/27 1:15:31 📁 来源:尧图网络
搞嵌入式开发的兄弟应该都有这种体会——想认真研究Cortex-M33这种带TrustZone和MPU的新内核第一步就很难迈STM32L5、NXP LPC55S6这些板子少说几百块还要配仿真器更麻烦的是MPU这类和内存权限相关的代码一旦配错就是直接HardFault真机上定位问题特别费劲。其实QEMU早就把ARM官方MPS2开发板模拟得很好了尤其是mps2-an505和mps2-an521这两个Cortex-M33目标CPU行为、NVIC中断控制器、SysTick、MPU这些内核关键路径都仿真得很到位完全可以用来看懂ARMv8-M的MPU工作机制、验证裸机代码、调试权限异常而且全程不需要一块实体板卡。这篇文章我就带你从零搭一套QEMU MPS2 AN505的仿真环境不依赖任何开发板和调试器手把手把启动代码、链接脚本、UART驱动、MPU初始化全部跑通最后用一个“故意写只读内存”的例子把MemManage异常打出来。想搞懂ARMv8-M MPU的朋友、做TrustZone隔离验证的朋友、准备移植RTOS特别是要开MPU保护的朋友都可以直接参考这套工程或者说直接抄作业。1. 方案选型为什么是QEMU MPS2而不是一块真开发板1.1 MPS2板卡在QEMU里的位置QEMU对ARM Cortex-M系列MCU的支持主要分两类一类是模拟具体的芯片型号比如某些STM32系列外设往往只覆盖了最常见的UART、GPIO很多时候一查文档会发现“这个型号支持不完整”另一类是模拟ARM官方的评估/教学开发板比如MPS2、Musca、Musca-B1这些板卡的模拟成熟度普遍更高因为QEMU社区长期拿它们做CI测试和功能验证。MPS2全称是ARM MPS2 FPGA Prototyping Board本身是一块面向嵌入式设计和验证的FPGA板板上有多个可切换的“AN”镜像对应不同内核。QEMU里和Cortex-M33直接相关的有两个machineQEMU machine内核特点mps2-an505Cortex-M33单核经典的M33教学/验证平台mps2-an521Cortex-M33双核支持TrustZone、双核调试这两个目标在QEMU里已经稳定存在很多年用的都是ARMv8-M架构非常适合学习带MPU、带TrustZone的现代Cortex-M处理器。相比之下如果非要拿某个厂商的M33芯片模型来学MPU外设没模拟全倒是小事最怕的是QEMU版本更新之后machine名和内存映射变了网上的教程对不上号那才叫一个难受。MPS2系列就没有这个问题社区资料相对多行为也比较稳定。1.2 这套仿真环境的边界在哪里很多人会问QEMU仿真出来的M33和真实芯片差多少我的实测结论是CPU内核层面——包括指令执行、异常模型、中断优先级、SysTick、MPU、TrustZone的SAU——非常接近真实行为尤其是MPU这类由硬件直接检查的机制QEMU会按照ARM架构手册的规则去模拟该触发MemManage就触发MemManage不会因为“仿真”就打折扣。但外设层面就要降低预期了。QEMU的mps2-an505只实现了板载的部分外设比如CMSDK UART、定时器、GPIO的一些关键寄存器很多外设只是放了一个“unimplemented device”占位读操作返回0、写操作直接忽略。如果你要做的项目重度依赖某个外设的细节时序那还是得回到真机上去调。如果只是学MPU、学Cortex-M33编程模型、跑RTOS内核这套仿真环境不仅够用效率还比真机高——重启快、加打印方便、GDB随便打断点根本不心疼。怎么确认你自己的QEMU支持哪些machine装好QEMU之后跑一条命令就行qemu-system-arm -machine help | grep mps2能看到mps2-an505和mps2-an521就说明这一步没问题。2. Cortex-M33的MPU到底怎么配RBAR、RLAR、MAIR一次讲清2.1 ARMv8-M MPU和ARMv7-M MPU有什么不同如果你之前玩过Cortex-M3或者M4对ARMv7-M的MPU应该不陌生它由RBAR和RASR两个寄存器定义一个区域RBAR写基地址RASR里塞了一大堆属性包括Region大小、AP访问权限、TEX、Cacheable/Bufferable、XN不可执行等等。一个区域的所有属性都被揉在一个寄存器里算SIZE字段的时候还得记住“2的(N1)次方”这种规则位操作一不小心就写错改一个属性还可能误伤其他位。到了Cortex-M33也就是ARMv8-M架构MPU的设计思路变了区域仍然最多8个但每个区域变成了RBAR RLAR两个寄存器内存属性被挪到了MAIR寄存器里统一管理。用我自己的话说ARMv7-M是“每个人兜里揣一张写满信息的身份证”ARMv8-M则是“身份证只写编号具体权限去中央系统查”。这种改动的好处非常明显内存属性的定义是全局复用的多个区域可以引用同一个属性编号不需要每个区域都重复写一遍缓存策略和权限位代码上更好维护也更容易让硬件做并行检查。另一个容易被忽略的点是ARMv8-M的MPU区域大小不再需要单独指定而是由Base地址和Limit地址直接算出来的。在ARMv7-M时代你经常要算“64KB是Size几”到了ARMv8-M你只需要保证Base和Limit都32字节对齐区域范围自然就是两者之间。2.2 RBAR、RLAR、MAIR三个寄存器的分工先看RBAR它的作用是指定区域的起始地址同时携带一个“有效位”和“区域编号”。在32位模式下RBAR的bit[31:5]是基地址bit[4]是VALIDbit[3:0]是REGION。写入时如果VALID置1MPU会把配置加载到当前MPU_RNR选中的区域同时把区域编号锁存到REGION字段。RLAR则是区域的结束地址bit[31:5]是Limit地址bit[3:1]是AttrIndx也就是MAIR里的属性索引bit[0]是ENABLE。一个区域要真正生效必须RBAR和RLAR都写完而且ENABLE必须是1。MAIR寄存器负责存放内存属性Cortex-M33有MAIR0和MAIR1两个寄存器每个寄存器拆成8个字节每个字节对应一个属性条目。MAIR里通常会编码内存类型Normal、Device、缓存策略Write-Back、Write-Through、Non-cacheable、共享属性以及访问权限AP。这个设计很像查表RBAR/RLAR只告诉MPU“这段地址是从哪到哪、用哪条属性”具体属性长什么样去MAIR里查。用一个不太严谨但很好懂的例子类比整个MPU就像小区停车场的管理系统MAIR停车场的管理条例比如“月租车可以停在A区”“访客车只能停在B区”。RBAR告诉你某个区域从哪个门开始。RLAR告诉你区域到哪个门结束。MPU_RNR当前正在配置哪个区域。处理器每次访问内存时硬件会把访问地址跟所有region的范围做比对。如果命中了某个region就按那个region的属性决定“能不能读”“能不能写”“能不能取指”如果没命中任何region就靠MPU_CTRL里的PRIVDEFENA决定是允许访问还是立即触发MemManage。2.3 配置MPU的标准流程配置MPU的流程并不复杂我建议按以下顺序操作可以避免一半以上的“怪问题”先把MPU关掉也就是MPU_CTRL的ENABLE位置0防止配置过程中出现半生效状态。配置MAIR0/MAIR1把需要的内存属性写进属性表。用MPU_RNR选中要配置的region编号。写RBAR设定基地址VALID位置1。写RLAR设定Limit地址、属性索引最后ENABLE位置1。所有region配置完后把MPU_CTRL打开ENABLE位置1按需打开PRIVDEFENA。有一点必须特别提醒RLAR的Limit地址必须32字节对齐而且指定的是“允许访问的最大地址”。很多人从ARMv7-M转过来习惯性地认为Limit传的区域结束地址比如64KB区域就想传0x0000FFFF结果发现上电就进Fault。正确的做法是传0x0000FFE0因为0xFFFF不对齐硬件不认。这个细节在我第一次写的时候也踩了后面会专门讲到。3. 从零搭建MPS2 AN505裸机工程工具链、启动代码、串口Hello World3.1 安装QEMU和ARM交叉工具链环境我用的是Ubuntu 22.04安装命令很简单sudo apt update sudo apt install -y qemu-system-arm gcc-arm-none-eabi gdb-multiarch装完验证一下版本qemu-system-arm --version arm-none-eabi-gcc --versionUbuntu仓库里的QEMU版本可能不是最新但没关系mps2-an505这个machine早就合入了主分支6.2以上版本都能用。如果你非要体验最新QEMU也可以去官网下源码编译或者直接用发行版仓库里的新版包不影响下面的操作。调试器方面gdb-multiarch和arm-none-eabi-gdb都行我后面示例用gdb-multiarch。工程我建议建一个干净的目录比如叫mps2-m33里面放四个文件mps2-m33/ ├── Makefile ├── mps2.ld ├── startup.s └── main.c后续所有代码都围绕这四个文件展开。3.2 工程结构和链接脚本MPS2 AN505在QEMU里的内存布局其实非常简单上电后就是直接从SRAM执行代码没有真正的Flash。AN505的SSRAM分为多个块最常用的是从0x00000000开始的SRAM区我这里规划如下FLASH区0x00000000长度0x10000放向量表和代码只读。RAM区0x00010000长度0x30000放数据、BSS、堆栈。之所以把可执行代码放在0x00000000是为了让中断向量表符合ARM Cortex-M的启动要求——向量表第一项是初始栈指针第二项是Reset_Handler入口M33会从这里开始取指。链接脚本这么写ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN 0x00000000, LENGTH 0x10000 RAM (rwx) : ORIGIN 0x00010000, LENGTH 0x30000 } SECTIONS { .isr_vector : { KEEP(*(.isr_vector)) } FLASH .text : { *(.text*) *(.rodata*) } FLASH .data : { _sdata .; *(.data*) _edata .; } RAM AT FLASH _sidata LOADADDR(.data); .bss : { _sbss .; *(.bss*) *(COMMON) _ebss .; } RAM _estack ORIGIN(RAM) LENGTH(RAM); }这里把.data段的加载地址放在FLASH运行地址放在RAM就需要启动代码在main之前把数据从Flash拷贝到RAM同时把BSS段清零。这个动作没有编译器帮你做必须自己写在启动文件里。栈顶_estack我直接定在RAM末尾也就是0x00040000。给一个完整的栈空间后面的实验完全够用。3.3 启动代码与UART驱动startup.s里面要完成几件事定义向量表、实现Reset_Handler、拷贝.data、清零.bss、跳转main另外再补几个异常处理函数的弱定义。Cortex-M33属于ARMv8-M向量表前几项是固定的.syntax unified .cpu cortex-m33 .thumb .section .isr_vector, a .align 2 .word _estack .word Reset_Handler .word NMI_Handler .word HardFault_Handler .word MemManage_Handler .word BusFault_Handler .word UsageFault_Handler .section .text .thumb_func .global Reset_Handler Reset_Handler: ldr r0, _sdata ldr r1, _edata ldr r2, _sidata copy_loop: cmp r0, r1 bge zero_bss ldr r3, [r2], #4 str r3, [r0], #4 b copy_loop zero_bss: ldr r0, _sbss ldr r1, _ebss movs r2, #0 zloop: cmp r0, r1 bge go_main str r2, [r0], #4 b zloop go_main: bl main b . .thumb_func .weak NMI_Handler .weak HardFault_Handler .weak MemManage_Handler .weak BusFault_Handler .weak UsageFault_Handler .thumb_func NMI_Handler: HardFault_Handler: MemManage_Handler: BusFault_Handler: UsageFault_Handler: b .这个启动文件的做法是裸机最标准的套路先拷贝.data再清零.bss最后进入main。注意最后几个异常处理函数我用.weak声明又在同一个文件里给了默认死循环实现这样C代码里如果定义了同名的强符号链接器就会用C文件里的那个非常方便。UART驱动部分MPS2 AN505在QEMU里用的是CMSDK UART基地址是0x40004000。这个UART比较简单常用寄存器如下偏移寄存器作用0x00DATA收发数据0x04STATEbit0是TXBF发送忙标志bit1是RXBF接收标志0x08CTRLbit0使能TXbit1使能RX0x10BAUDDIV波特率分频值QEMU里这颗UART的时钟是25MHz计算波特率分频的公式是BAUDDIV UARTCLK / (16 * baud)如果要跑115200就是25_000_000 / (16 * 115200) ≈ 13.56取14所以UART初始化只需要几条语句。发送字符时先轮询STATE的TXBF等发送缓冲区空了再写DATA即可。3.4 编译运行先看到Hello Worldmain.c这一步先不碰MPU只做UART初始化和一行打印确保整个工具链、启动文件、链接脚本是正确的#include stdint.h #define UART0_BASE 0x40004000UL #define UART_DATA (*(volatile uint32_t *)(UART0_BASE 0x00)) #define UART_STATE (*(volatile uint32_t *)(UART0_BASE 0x04)) #define UART_CTRL (*(volatile uint32_t *)(UART0_BASE 0x08)) #define UART_BAUDDIV (*(volatile uint32_t *)(UART0_BASE 0x10)) static void uart_init(void) { UART_BAUDDIV 14; UART_CTRL 0x3; /* TX enable | RX enable */ } static void uart_putc(char c) { while (UART_STATE 0x1) { } UART_DATA c; } static void uart_puts(const char *s) { while (*s) { uart_putc(*s); } } int main(void) { uart_init(); uart_puts(Hello from Cortex-M33 on QEMU MPS2-AN505\r\n); while (1) { } }Makefile我写的是最朴素的一版方便你看清楚编译参数CROSS ? arm-none-eabi- CC $(CROSS)gcc LD $(CROSS)gcc OBJCOPY $(CROSS)objcopy CFLAGS -mcpucortex-m33 -mthumb -Wall -Wextra -O2 -g -nostdlib -ffreestanding LDFLAGS -T mps2.ld -nostdlib -nostartfiles -Wl,--gc-sections OBJS startup.o main.o all: main.elf main.bin main.elf: $(OBJS) mps2.ld $(LD) $(LDFLAGS) -o $ $(OBJS) %.o: %.s $(CC) $(CFLAGS) -c -o $ $ %.o: %.c $(CC) $(CFLAGS) -c -o $ $ main.bin: main.elf $(OBJCOPY) -O binary $ $ clean: rm -f *.o *.elf *.bin run: main.elf qemu-system-arm -machine mps2-an505 -nographic -monitor none -kernel main.elf跑一下make clean make run终端里应该会看到Hello from Cortex-M33 on QEMU MPS2-AN505到这里最小仿真环境已经通了。没有真实开发板没有仿真器就是一个纯软件的M33运行环境。QEMU窗口会一直挂在那里因为程序是个死循环按CtrlA然后按X可以退出QEMU。4. MPU实战加入只读区域并触发MemManage Fault4.1 用PRIVDEFENA简化MPU初始化现在开始加MPU。我一贯的原则是验证一个新机制时配置越简单越好先把链路跑通再谈复杂的多区域划分。ARMv8-M的MPU_CTRL寄存器有几个关键位bit名称作用bit0ENABLE全局使能MPUbit1HFNMIENA在NMI和HardFault期间是否强制使能MPUbit2PRIVDEFENA使能背景区域允许特权模式访问未命中任何region的地址PRIVDEFENA这个位特别适合学习阶段使用。如果把它置1没有命中region的地址默认允许特权模式访问这样我们只需要配置一个“只读”区域就能在代码区制造一个写保护陷阱而栈、外设、系统控制空间都还能正常工作不会因为漏配某个外设导致莫名其妙地蹦Fault。我规划的测试逻辑很简单配置一个Region覆盖0x00000000开始的64KB代码区。区域属性设置为Normal内存、特权模式只读。开启MPU同时开启PRIVDEFENA。在main里故意向0x00000000写一个值触发MemManage。在MemManage_Handler里打印一行错误信息然后死循环。这样既能证明MPU真的在起作用又能把故障类型直观地打出来。4.2 故意写只读内存验证MPU生效Cortex-M33的MPU寄存器在系统控制空间地址如下寄存器地址MPU_CTRL0xE000ED94MPU_RNR0xE000ED98MPU_RBAR0xE000ED9CMPU_RLAR0xE000EDA0MAIR00xE000EDC0MAIR10xE000EDC4这里我给出一份可以直接用的MPU初始化代码同时为了不让寄存器位操作太隐晦我用宏定义了CMSIS风格的属性构造方式#define MPU_BASE 0xE000ED90UL #define MPU_CTRL (*(volatile uint32_t *)(MPU_BASE 0x04)) #define MPU_RNR (*(volatile uint32_t *)(MPU_BASE 0x08)) #define MPU_RBAR (*(volatile uint32_t *)(MPU_BASE 0x0C)) #define MPU_RLAR (*(volatile uint32_t *)(MPU_BASE 0x10)) #define MAIR0 (*(volatile uint32_t *)(0xE000EDC0UL)) #define MAIR1 (*(volatile uint32_t *)(0xE000EDC4UL)) #define ARM_MPU_AP_RW 3 #define ARM_MPU_AP_RO 5 #define ARM_MPU_ATTR_DEVICE 0x00 #define ARM_MPU_ATTR_NORMAL_NC 0x04 #define ARM_MPU_ATTR(ap, sh, memattr) \ (((ap) 0x7U) | (((sh) 0x1U) 3) | (((memattr) 0xFU) 4)) static void mpu_init(void) { /* 先关闭MPU避免配置过程中产生不一致状态 */ MPU_CTRL 0; /* Attr0: Normal内存非缓存特权模式只读 */ MAIR0 ARM_MPU_ATTR(ARM_MPU_AP_RO, 0, ARM_MPU_ATTR_NORMAL_NC); MAIR1 0; /* Region0: 0x00000000 ~ 0x0000FFE064KB使用Attr0 */ MPU_RNR 0; MPU_RBAR (0x00000000UL ~0x1FUL) | (0UL 0) | (1UL 4); MPU_RLAR (0x0000FFE0UL ~0x1FUL) | (0UL 1) | (1UL 0); /* 开启MPU同时使能PRIVDEFENA */ MPU_CTRL (1UL 0) | (1UL 2); }这里有两个位操作要重点解释。RBAR的bit[4]是VALID写1表示“这次写入对齐到RNR选择的region并立即使能”所以(1UL 4)是必须的。RLAR的bit[3:1]是AttrIndx因为MAIR0里Attr0放在第0个字节所以这里属性索引写0也就是(0UL 1)。RLAR的bit[0]是ENABLE置1后region才真正生效。然后是64KB区域为什么Limit要写0x0000FFE0而不是0x0000FFFF。因为ARMv8-M要求Limit地址32字节对齐0xFFFF不满足对齐条件硬件会直接忽略这个配置或者行为未定义。0xFFE0是64KB范围内最后一个32字节对齐的地址从0x00000000到0x0000FFE0这个区域就是0~64KB没错。4.3 用GDB确认故障原因和现场接下来把main函数改一下在里面调用mpu_init然后故意写只读区域void MemManage_Handler(void) { uart_puts(\r\n[MemManage] MPU fault triggered!\r\n); while (1) { } } int main(void) { uart_init(); uart_puts(Hello from Cortex-M33 on QEMU MPS2-AN505\r\n); mpu_init(); uart_puts(MPU enabled. Try to write read-only region...\r\n); /* 故意向代码区0x00000000写入数据预期触发MemManage */ *(volatile uint32_t *)0x00000000UL 0xDEADBEEFUL; uart_puts(This line should never be printed\r\n); while (1) { } }这里因为MemManage_Handler在startup.s里是弱符号C文件里定义了一个同名的强符号链接器会优先用C文件的实现所以不用改动startup.s。重新编译运行make clean make run输出应该是Hello from Cortex-M33 on QEMU MPS2-AN505 MPU enabled. Try to write read-only region... [MemManage] MPU fault triggered!如果看到这条错误信息说明MPU的只读保护已经生效了。程序在向0x00000000写入的时候被MPU拦下来触发了MemManage异常然后进入我们自己写的异常处理函数。为了确认这不是碰巧我们再用GDB把现场扒开看看。先启动QEMU的调试模式make debug我通常会在Makefile里加一个debug目标debug: main.elf qemu-system-arm -machine mps2-an505 -nographic -monitor none \ -S -gdb tcp::1234 -kernel main.elf然后用gdb-multiarch连上去gdb-multiarch -q build/main.elf (gdb) target remote :1234 (gdb) b MemManage_Handler (gdb) continue程序会在进入MemManage_Handler时停下来。这时候可以看关键寄存器(gdb) info registers pc lr msp (gdb) x/wx 0xE000ED280xE000ED28是CFSR寄存器也就是可配置故障状态寄存器。Cortex-M33的MemManage fault状态在CFSR的最低字节MMFSR里bit0 IACCVIOL指令访问违规。bit1 DACCVIOL数据访问违规。如果x/wx 0xE000ED28读出来bit1是1就说明这个MemManage异常是数据访问违规引起的正好对应我们向只读区域写0xDEADBEEF的操作(gdb) x/wx 0xE000ED28 0xe000ed28: 0x000000020x00000002就是bit1置位DACCVIOL100%确认是数据访问违规。这个排查思路比你单纯看“程序卡了”要靠谱得多以后在任何Cortex-M33板子上遇到MPU问题都能用。5. 常见坑串口没输出、MPU一开就挂、GDB连不上5.1 高频问题速查我把实际折腾过程中遇到过的、以及周围朋友问过最多的问题整理成一个速查表现象最可能的原因解决办法QEMU启动后终端无任何输出串口地址写错或没加-nographic确认UART0基地址是0x40004000启动命令加-nographic程序一使能MPU就进HardFault没有使能PRIVDEFENA或外设/栈地址没有匹配任何region配置对应region或临时把PRIVDEFENA置1写了RLAR但region就是不生效Limit地址没有32字节对齐64KB区域的Limit写0x0000FFE0不要写0x0000FFFF向量表第一项SP不对程序跑飞链接脚本的_estack符号没定义对确认_estack ORIGIN(RAM) LENGTH(RAM)GDB连不上QEMU没加-S或端口不对启动命令加-S -gdb tcp::1234再target remote编译链接报undefined reference to _sdata启动文件和链接脚本符号不一致检查startup.s里引用的符号和ld脚本里定义的是否同名修改C代码后运行结果没变make clean后重新编译裸机工程容易漏掉依赖关系先clean一次看看其中MPU一开就挂这个问题新手遇到得最多。其实原因往往很简单使能MPU之前你所有内存访问都不受限制一旦打开MPU如果PRIVDEFENA是0那么任何没被region覆盖的地址都会被拒绝访问。这时不光外设访问会触发异常连函数调用要用的栈都可能踩在未覆盖区域里自然一开就挂。所以我强烈建议初学时先把PRIVDEFENA打开把背景区域放行只在你关心的地址段上做限制等逻辑清楚了再收紧权限。5.2 调试手段QEMU日志和GDB远程调试除了GDBQEMU自己还提供了很多调试日志选项强烈建议掌握。比如qemu-system-arm -machine mps2-an505 -nographic -monitor none \ -kernel main.elf -d guest_errors-d guest_errors会把guest访问未实现外设、非法内存访问的细节打印出来。如果程序里访问了QEMU没模拟的寄存器或者访问了未映射地址终端上会直接告诉你“Guest wrote to invalid address”之类信息省去很多盲猜的时间。还有一个更狠的调试参数-d in_asm这个会把每条执行的指令反汇编打印出来适合看程序到底“死”在哪个指令上。不过输出量非常大一般配合-D log.txt把日志写到文件里再慢慢看。GDB远程调试也是必会的技能。除了上面说的在MemManage_Handler打断点还可以这样操作(gdb) info registers (gdb) x/8wx 0x00000000 (gdb) x/8i $pc配合QEMU的monitor info mtree可以看整个系统的内存树(qemu) info mtree这样能确认UART0、MPU寄存器、SRAM这些地址在QEMU里是否真的存在。这个方法在排查“为什么访问某个地址会挂”的时候特别好用。5.3 还能往哪折腾TrustZone、RTOS、ARM64用户态这套环境跑通之后你可以做的事情还有很多。如果你用的是mps2-an521还可以体验Cortex-M33的TrustZone特性在QEMU里切到非安全世界测试SAU和MPU的联动。不要觉得TrustZone离你很远现在不少物联网安全方案都是基于M33做的而QEMU是少数能让你在不需要开发板的情况下完整验证这套机制的免费工具。如果你在移植RTOS比如FreeRTOSM33的port里通常就有MPU支持。你可以先把内核裸跑起来再打开MPU保护观察任务切换时region重配置是否正常。这类问题在真机上调试非常痛苦但在QEMU里你随时可以打断点甚至能把MPU配置打出来逐字段核对。再往外扩展QEMU也可以用来模拟ARM64的用户态程序比如在x86的Ubuntu上直接跑交叉编译的aarch64 Linux可执行文件如果你想模拟带网络的完整ARM虚拟机也可以把虚拟机网卡接到宿主机的tap0网桥上做二层网络调试。说到底QEMU不止是“一个单片机模拟器”它是一套完整的硬件虚拟化工具链思维打通之后M33、ARM64、RISC-V的路子都是相通的。我个人实际测下来的体会是QEMU模拟M33的MPU行为跟真机几乎一致这是它最大的价值所在特别适合理解“权限保护”这类硬件机制但它终归不是万能的外设模型简化比较多做应用开发还是得留一块真实板子在手边。最后再分享一个小技巧每次改完MPU配置先别急着上真机先跑一遍QEMU再用-d guest_errors看一眼有没有异常访问能帮你挡掉至少一半的低级错误。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →