
1. Cortex-M4内存保护与故障处理机制概述在嵌入式系统开发尤其是涉及实时操作系统RTOS或多任务环境的项目中系统稳定性是压倒一切的首要目标。一个任务的无意越界写操作很可能导致另一个关键任务的数据被破坏进而引发整个系统的连锁崩溃这种问题在调试阶段往往难以复现和定位。Cortex-M4处理器提供的内存保护单元MPU和嵌套向量中断控制器NVIC中的故障处理机制正是为了解决这类“内存安全”的核心痛点而设计的硬件屏障。简单来说MPU就像一位严格的内存区域管理员。你可以将整个4GB的地址空间对于Cortex-M4而言划分成最多8个独立的“房间”区域并为每个房间制定详细的“访问规则”。例如你可以规定任务A只能进入并读写“堆栈区”这个房间任务B只能进入“共享数据区”房间且只能读不能写而“系统配置区”这个房间则禁止所有非特权访问。当任何代码无论有意还是无意试图违反这些规则时——比如任务A试图闯入“系统配置区”或者任务B试图在“共享数据区”乱涂乱画执行写操作——MPU会立即拉响警报触发一个最高优先级的异常即硬故障Hard Fault。此时系统的“黑匣子”开始工作。硬故障状态寄存器HFAULTSTAT、内存管理故障地址寄存器MMADDR、总线故障地址寄存器FAULTADDR等一系列专用寄存器会像飞机黑匣子一样瞬间冻结并记录下故障发生时的关键现场信息是哪种类型的违规违规访问的目标地址是什么是由哪个任务触发的这些信息是事后进行崩溃分析的唯一可靠依据。理解并善用这些寄存器意味着你能从系统崩溃的“死机”现象快速回溯到导致崩溃的那一行有问题的代码将调试从“玄学”变为可追溯、可分析的工程实践。本文将以TI的TM4C129x系列微控制器为具体载体深入剖析Cortex-M4的MPU配置寄存器组和硬故障状态寄存器组。我不会仅仅停留在数据手册的翻译层面而是结合我在汽车电控单元ECU和工业控制器开发中实际踩过的坑为你拆解每个关键比特位的真实含义、配置时的常见陷阱以及当红色警报硬故障响起时如何利用这些寄存器信息进行高效的问题定位。无论你是在基于FreeRTOS、µC/OS-III构建复杂的多任务应用还是在开发对可靠性要求极高的裸机系统这套机制都是你构建健壮嵌入式系统的基石。2. 硬故障Hard Fault状态寄存器深度解析当系统运行出现严重错误如访问非法内存、执行未定义指令或从总线返回错误响应时Cortex-M4内核会触发硬故障异常。硬故障的优先级为-1仅次于不可屏蔽中断NMI优先级-2这意味着它几乎可以抢占任何正在执行的任务或异常处理程序确保严重错误能被立即响应。然而仅仅知道发生了硬故障是远远不够的关键在于“为什么”和“在哪里”。硬故障状态寄存器组就是回答这两个问题的关键。2.1 HFAULTSTAT寄存器故障根源的第一现场硬故障状态寄存器HFAULTSTAT位于系统控制块SCB中地址偏移为0xD2C基址0xE000E000。它是一个32位的寄存器但只有少数几个比特位是有效的其余为保留位。这个寄存器只能由处于特权模式的代码访问。寄存器位域详解与实战意义位[31] DBG (Debug Event):描述此位为调试事件保留。在标准应用代码中你必须将其写为0读取值无意义。实战注意永远不要尝试在应用层设置此位。它是给调试器如JTAG/SWD在硬件断点、观察点触发时使用的。你的代码如果误操作此位可能导致不可预测的调试行为。位[30] FORCED (Forced Hard Fault):描述这是最需要关注的位之一。当它被置1时表示本次硬故障是由一个可配置优先级的故障如内存管理故障MemManage、总线故障BusFault、用法故障UsageFault**升级Escalation**而来。升级发生的条件该可配置故障的异常处理程序被禁用在NVIC中未使能。该可配置故障的异常处理程序已使能但其优先级不高于当前正在执行的处理程序优先级即无法抢占同时该故障又发生在当前处理程序内部。实战意义如果你在HFAULTSTAT中看到FORCED1那么根本原因不在硬故障本身而是其他故障。你必须立即去检查内存管理故障状态寄存器MFAULTSTAT和总线故障状态寄存器BFAULTSTAT它们会告诉你最初违规的细节例如是权限错误还是地址不对齐。这就像警察告诉你发生了严重事故硬故障但事故原因是另一辆车的违规内存/总线故障导致的。位[29:2] Reserved:保留位。按照ARM惯例对保留位执行“读-修改-写”操作时必须保持其原有值以确保与未来处理器的兼容性。位[1] VECT (Vector Table Read Fault):描述此位置1表示处理器在尝试读取异常向量表例如响应中断或异常时需要从中断向量表加载处理函数的入口地址时发生了总线故障。严重性这是一个非常严重的错误。因为向量表读取失败意味着处理器无法正确跳转到任何异常处理程序包括硬故障处理程序本身幸运的是Cortex-M4的设计确保了即使向量表读取失败硬故障处理程序仍然能被激活并且此位会被设置。调试价值当VECT1时堆栈中的程序计数器PC值指向的是被该异常抢占的那条指令。这有助于你理解在试图响应哪个中断或异常时发生了问题。常见原因包括向量表地址通过VTOR寄存器设置指向了非法的或不可执行的存储器区域。位[0] Reserved:保留位。操作特性HFAULTSTAT是一个“写1清除”Write-1-to-Clear, RW1C的寄存器。这意味着要清除某个状态位例如在分析完故障后你需要向该位写入1而不是0。向保留位或只读位写入1不会有任何效果。实操心得故障现场保存第一法则在你的硬故障处理函数中第一步且最重要的一步就是读取并保存HFAULTSTAT、MFAULTSTAT、BFAULTSTAT、MMADDR、FAULTADDR以及堆栈帧包括PC, LR, PSR等寄存器的值。最好将这些信息立刻存入一个全局的故障结构体或备份RAM中。因为紧接着你可能需要复位系统或者进行其他可能覆盖这些寄存器状态的操作。没有保存的故障现场就像没有证据的案发现场失去了所有调试价值。2.2 故障地址寄存器锁定罪魁祸首当MPU或总线控制器检测到违规访问时不仅会触发故障还会把引发故障的内存地址记录下来。这对于定位野指针、数组越界等问题至关重要。内存管理故障地址寄存器 (MMADDR, 偏移 0xD34)当发生MPU权限违规例如无特权模式尝试写入只读区域或用户模式访问特权区域或地址对齐错误时如果内存管理状态寄存器MFAULTSTAT中的MMARVALID位被置位那么MMADDR中就保存着触发这次内存管理故障的实际访问地址。关键细节对于非对齐访问引发的故障此寄存器保存的是实际触发故障的那个对齐访问的地址。由于一条非对齐的加载/存储指令可能被拆分成多个对齐的访问所以这个地址可能是目标访问范围内的任何一个地址。总线故障地址寄存器 (FAULTADDR, 偏移 0xD38)当发生总线错误例如访问一个不存在的外设地址或从存储器返回错误响应时如果总线故障状态寄存器BFAULTSTAT中的BFARVALID位被置位那么FAULTADDR中就保存着触发总线故障的地址。关键细节对于非对齐访问引发的总线故障此寄存器保存的是指令所请求的原始地址即使这个地址本身没有对齐。这与MMADDR的行为不同需要特别注意。实战场景对比 假设有一条指令LDR R0, [0x2000_0001]从非对齐地址0x2000_0001加载一个字。在启用MPU且该区域禁止非对齐访问的系统中如果是因为“非对齐访问”这个MPU规则被触发MMADDR可能记录0x2000_0000第一个对齐访问地址或0x2000_0004第二个对齐访问地址。如果是因为访问的地址0x2000_0001根本不存在于总线地址空间而触发总线错误FAULTADDR将记录0x2000_0001指令请求的原始地址。3. 内存保护单元MPU寄存器配置详解MPU是主动防御的武器而故障寄存器是事后分析的工具。要用好MPU必须透彻理解其配置寄存器。Cortex-M4的MPU最多支持8个独立区域每个区域可以单独设置基地址、大小和访问属性。3.1 MPU基础信息与控制寄存器在配置具体区域之前我们需要了解MPU的整体能力和全局开关。MPU类型寄存器 (MPUTYPE, 偏移 0xD90)这是一个只读寄存器用于查询MPU的硬件能力。DREGION (位[15:8])指示MPU支持的数据区域数量。对于TM4C1299NCZAD此值为0x08即支持8个区域。这是你进行区域规划的上限。IREGION (位[23:16])指示支持的指令区域数量。在Cortex-M4的统一MPU架构下此值始终为0x00。指令和数据共用这8个区域通过属性位来控制是否可执行。SEPARATE (位[0])指示MPU是统一的还是分离的。Cortex-M4为统一MPU此位为0。MPU控制寄存器 (MPUCTRL, 偏移 0xD94)这是MPU的总开关和策略控制中心。位[0] ENABLEMPU全局使能位。1使能MPU0禁用。重要在使能MPU (ENABLE1) 之前必须至少配置并启用一个区域MPUATTR.ENABLE1或者同时使能背景区域PRIVDEFEN1否则将使能后立即产生内存管理故障。位[1] HFNMIENA控制在硬故障、NMI和FAULTMASK处理程序中是否启用MPU。0默认在这些高优先级异常处理程序中MPU被强制禁用无论ENABLE位为何值。这确保了最高优先级的异常处理程序总能无障碍地访问内存常用于最关键的错误恢复或看门狗复位前保存现场。1在这些处理程序中MPU依然生效。这提供了更强的保护但要求这些高优先级处理程序本身的代码和数据也必须位于合法的MPU区域内。位[2] PRIVDEFEN特权模式默认内存映射使能。当ENABLE1且PRIVDEFEN1时为特权模式代码提供了一个“背景区域”。任何未被已启用MPU区域覆盖的内存地址特权模式访问将使用默认的内存映射属性通常可读、可写、可执行具有缓存和共享属性。这极大简化了特权级系统代码如操作系统内核、驱动程序的配置它们无需为所有用到的内存如整个代码区、外设区显式定义区域。对于非特权模式用户模式的访问如果地址不在任何已启用的MPU区域内则一定会产生内存管理故障。当ENABLE1但PRIVDEFEN0时所有访问无论特权与否都必须落在已启用的MPU区域内否则触发故障。这种模式提供了最严格的控制。当ENABLE0时MPU完全关闭系统使用默认内存映射此位无意义。配置策略选择PRIVDEFEN的取舍裸机或简单系统建议设置PRIVDEFEN1HFNMIENA0。这样你只需要为具体的用户任务或需要特殊保护的数据段配置MPU区域内核和中断处理可以自由运行。这是最常用、最省事的配置。高安全等级RTOS可以考虑设置PRIVDEFEN0并为内核代码、数据、堆栈以及外设都显式配置MPU区域。这实现了对特权代码的“最小权限”控制即使内核有bug其破坏范围也被限制在定义的区域内。但配置复杂度显著增加。汽车ASIL-D等功能安全应用通常要求HFNMIENA1确保即使在最高优先级异常处理中内存访问也受控避免故障处理程序本身的错误导致更大灾难。3.2 MPU区域配置三部曲配置一个MPU区域需要三个步骤通常通过三个寄存器完成选择区域编号、设置基地址、设置属性和大小。1. MPU区域编号寄存器 (MPUNUMBER, 偏移 0xD98)NUMBER (位[2:0])指定当前要配置或访问的MPU区域编号范围0-7。在写入MPUBASE或MPUATTR之前通常需要先在此寄存器中指定目标区域号。2. MPU区域基地址寄存器 (MPUBASE, 偏移 0xD9C)此寄存器用于设置选定区域的基地址并有一个巧妙的功能可以同时更新区域编号。ADDR (位[31:5])区域基地址的高位部分。基地址必须按区域大小对齐。例如一个128KB的区域大小2^17字节其基地址必须是128KB的整数倍即低17位为0。ADDR字段存放的就是基地址的[31:N]位其中N log2(区域大小)。低[4:0]位在寄存器中固定为0。REGION (位[2:0])区域编号。其行为受VALID位控制。VALID (位[4])这是一个只写位读取始终为0。如果向VALID位写入0则处理器使用MPUNUMBER寄存器中当前的区域编号并更新该编号对应区域的基地址。REGION字段的值被忽略。如果向VALID位写入1则处理器会同时做两件事首先将REGION字段的值写入MPUNUMBER寄存器更新当前区域编号然后使用这个新的区域编号并更新其基地址为ADDR字段指定的值。实战技巧利用VALID1的特性可以单次写操作完成切换区域并设置其基地址这在动态配置多个区域时能节省代码和执行时间。例如// 一次性将区域2的基地址设置为0x20010000并自动将当前区域号切换为2 MPU-RBAR (0x20010000 MPU_RBAR_ADDR_Msk) | (2 MPU_RBAR_REGION_Pos) | (1 MPU_RBAR_VALID_Pos);3. MPU区域属性与大小寄存器 (MPUATTR, 偏移 0xDA0)这是配置中最复杂的部分定义了区域的大小、访问权限和内存类型。ENABLE (位[0])区域使能位。1启用该区域0禁用。一个区域只有在ENABLE1且MPU全局使能MPUCTRL.ENABLE1后才生效。SIZE (位[5:1])区域大小字段。区域大小字节 2^(SIZE1)。例如SIZE 4(0b00100): 区域大小 2^(5) 32字节最小。SIZE 11(0b01011): 区域大小 2^(12) 4KB。SIZE 20(0b10100): 区域大小 2^(21) 2MB。SIZE 31(0b11111): 区域大小 2^(32) 4GB覆盖整个地址空间。SRD (位[15:8])子区域禁用位。每个区域大小256字节可以被均分为8个子区域。将某位置1则禁用对应的1/8子区域。这允许你在一个大区域内“挖洞”。如一个1MB的区域0x20000000-0x200FFFFF你可以禁用其中的第0和第7个子区域那么0x20000000-0x2001FFFF和0x200E0000-0x200FFFFF将不受此区域规则保护可能由其他区域或背景区域覆盖。注意对于小于128字节的区域不支持子区域SRD必须设置为0x00。AP (位[26:24])访问权限位。这是控制读/写/执行权限的关键。AP[2:0]特权模式访问用户模式访问描述000无访问无访问任何访问都触发权限故障001读/写无访问典型的特权-only数据区010读/写只读用户只读特权可写如只读数据共享011读/写读/写完全共享数据区100只读无访问-101只读只读典型的代码区或只读数据区110只读只读同101111读/写只读同010XN (位[28])指令执行禁止位。1表示该区域内的地址不可执行即不能从中取指。这对于将数据段如堆、栈标记为不可执行至关重要是防范某些类型缓冲区溢出攻击的基本硬件支持。TEX, C, B, S (位[21:16])内存属性位。这些位控制内存的类型、缓存和共享行为对于有Cache和多核/多主控的系统至关重要。TEX[2:0], C, B共同定义内存类型如设备内存、普通内存和缓存策略写通、写回、不可缓存等。具体编码需查阅ARM手册的“内存属性间接寄存器”表格。例如对于片上SRAM通常配置为可缓存、可缓冲的普通内存TEX0b001, C1, B1。S (位[18])共享位。1表示该内存区域是共享的在多处理器环境或DMA访问时需要正确设置以确保缓存一致性。4. MPU配置实战与故障排查流程理解了寄存器之后我们来看如何将它们组合起来完成一个典型的RTOS任务内存保护配置并构建一个完整的故障诊断流程。4.1 为RTOS任务配置MPU区域一个完整示例假设我们在FreeRTOS中有一个任务Task_A它需要以下内存空间私有栈1KB位于0x2000F000-0x2000F3FF需要读/写权限不可执行。任务代码段位于Flash中0x00020000-0x00023FFF16KB需要只读、可执行权限。访问一个共享数据区4KB位于0x2000C000-0x2000CFFF需要读/写权限不可执行。同时内核和其他特权代码需要访问所有外设和全部RAM。我们采用PRIVDEFEN1的策略。// 假设使用CMSIS-Core头文件寄存器定义如MPU-RBAR, MPU-RASR void MPU_ConfigForTaskA(void) { // 1. 禁用MPU在配置期间避免意外故障 MPU-CTRL 0; // 2. 配置区域0: Task_A私有栈 (1KB, RW, Non-Executable, Non-Shared, Normal Memory) MPU-RNR 0; // 选择区域0 MPU-RBAR 0x2000F000 MPU_RBAR_ADDR_Msk; // 基地址1KB对齐 MPU-RASR ((0x9 MPU_RASR_SIZE_Pos) | // SIZE9 (2^(10)1KB) (0x1 MPU_RASR_AP_Pos) | // AP001 (Privileged RW only) (MPU_RASR_XN_Msk) | // XN1 (不可执行) (0x0 MPU_RASR_TEX_Pos) | // TEX0, C1, B1 (Write-Back Cacheable) (1 MPU_RASR_C_Pos) | (1 MPU_RASR_B_Pos) | (0 MPU_RASR_S_Pos) | // S0 (非共享) (MPU_RASR_ENABLE_Msk)); // 启用区域 // 3. 配置区域1: Task_A代码段 (16KB, RO, Executable, Normal Memory) MPU-RNR 1; MPU-RBAR 0x00020000 MPU_RBAR_ADDR_Msk; // 16KB对齐地址 MPU-RASR ((0xD MPU_RASR_SIZE_Pos) | // SIZE13 (2^(14)16KB) (0x5 MPU_RASR_AP_Pos) | // AP101 (All Read-Only) (0x0 MPU_RASR_XN_Pos) | // XN0 (可执行) (0x0 MPU_RASR_TEX_Pos) | (1 MPU_RASR_C_Pos) | (0 MPU_RASR_B_Pos) | // B0 for Flash (通常写缓冲禁用) (0 MPU_RASR_S_Pos) | (MPU_RASR_ENABLE_Msk)); // 4. 配置区域2: 共享数据区 (4KB, RW, Non-Executable, Shared) MPU-RNR 2; MPU-RBAR 0x2000C000 MPU_RBAR_ADDR_Msk; MPU-RASR ((0xB MPU_RASR_SIZE_Pos) | // SIZE11 (2^(12)4KB) (0x3 MPU_RASR_AP_Pos) | // AP011 (Privileged/User RW) (MPU_RASR_XN_Msk) | (0x0 MPU_RASR_TEX_Pos) | (1 MPU_RASR_C_Pos) | (1 MPU_RASR_B_Pos) | (1 MPU_RASR_S_Pos) | // S1 (共享因为可能被DMA或其他核心访问) (MPU_RASR_ENABLE_Msk)); // 5. 使能MPU并启用特权模式背景区域 MPU-CTRL MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk; // 6. 确保内存屏障和指令同步使MPU配置立即生效 __DSB(); __ISB(); }关键操作解析配置顺序先RNR选择区域再设置RBAR和RASR。也可以使用RBAR的VALID位一步完成。地址对齐RBAR写入的地址必须按SIZE指定的大小对齐。编译器通常能保证对齐但手动计算时务必检查。属性组合Flash区域通常设置为“可执行、只读、可缓存但不可缓冲C1, B0”。SRAM数据区设置为“不可执行、可读/写、可缓存且可缓冲C1, B1”。共享数据区必须设置S1。屏障指令在使能MPUCTRL后必须使用__DSB()和__ISB()。__DSB()确保所有内存访问包括MPU配置写入完成__ISB()清空处理器流水线确保后续指令在新的MPU规则下获取。4.2 硬故障处理函数设计与诊断流程当硬故障发生时处理器会自动将关键寄存器压栈PC, LR, PSR等并跳转到硬故障向量。你的处理函数需要立刻保存现场信息。// 定义一个结构体来保存故障现场 typedef struct { uint32_t hfsr; // Hard Fault Status Register uint32_t cfsr; // Configurable Fault Status Register (包含MMFSR/BFSR/UFSR) uint32_t mmfar; // MemManage Fault Address Register uint32_t bfar; // Bus Fault Address Register uint32_t r0, r1, r2, r3, r12, lr, pc, psr; // 从堆栈中提取的寄存器 } FaultRecord_t; __attribute__((naked)) void HardFault_Handler(void) { __asm volatile( tst lr, #4 \n // 检查EXC_RETURN的位2判断使用的是MSP还是PSP ite eq \n mrseq r0, msp \n // 如果使用MSP将其存入r0 mrsne r0, psp \n // 如果使用PSP将其存入r0 ldr r1, HardFault_Handler_C \n // 跳转到C函数r0作为参数堆栈指针 bx r1 \n ); } void HardFault_Handler_C(uint32_t *stack_pointer) { FaultRecord_t fault; // 1. 立即捕获所有故障寄存器 fault.hfsr SCB-HFSR; fault.cfsr SCB-CFSR; // 这是一个组合寄存器包含MFSR/BFSR/UFSR fault.mmfar SCB-MMFAR; fault.bfar SCB-BFAR; // 2. 从堆栈帧中提取被中断时的寄存器上下文 // 堆栈帧结构: R0, R1, R2, R3, R12, LR, PC, PSR fault.r0 stack_pointer[0]; fault.r1 stack_pointer[1]; fault.r2 stack_pointer[2]; fault.r3 stack_pointer[3]; fault.r12 stack_pointer[4]; fault.lr stack_pointer[5]; // 这是EXC_RETURN值不是用户LR fault.pc stack_pointer[6]; // 发生故障时的指令地址 fault.psr stack_pointer[7]; // 3. 分析HFSR确定故障入口 if (fault.hfsr SCB_HFSR_FORCED_Msk) { // 由可配置故障升级而来 uint32_t mmfsr (fault.cfsr 0) 0xFF; // MemManage Fault Status uint32_t bfsr (fault.cfsr 8) 0xFF; // Bus Fault Status uint32_t ufsr (fault.cfsr 16) 0xFFFF; // Usage Fault Status if (mmfsr) { // 内存管理故障 if (mmfsr SCB_CFSR_MMARVALID_Msk) { // MMAR有效打印故障地址 // fault.mmfar 包含了非法地址 } // 检查其他MMFSR位IACCVIOL, DACCVIOL, MUNSTKERR, MSTKERR, MLSPERR } if (bfsr) { // 总线故障 if (bfsr SCB_CFSR_BFARVALID_Msk) { // BFAR有效打印故障地址 // fault.bfar 包含了总线错误地址 } // 检查其他BFSR位IBUSERR, PRECISERR, IMPRECISERR, UNSTKERR, STKERR, LSPERR } if (ufsr) { // 用法故障如未定义指令、非法状态、除零等 // 检查UFSR位UNDEFINSTR, INVSTATE, INVPC, NOCP, UNALIGNED, DIVBYZERO } } else if (fault.hfsr SCB_HFSR_VECTTBL_Msk) { // 向量表读取故障 // 检查VTOR寄存器设置和向量表所在内存区域的MPU/访问属性 } else { // 其他硬故障如调试事件升级 } // 4. 将故障记录存入非易失性存储或通过调试接口输出 // save_fault_record(fault); // 5. 根据安全策略决定下一步尝试恢复、软件复位或进入安全状态 // while(1); // 死循环等待看门狗复位 // NVIC_SystemReset(); // 软件复位 }4.3 常见MPU配置陷阱与调试技巧区域重叠与优先级MPU区域编号越小优先级越高。当两个区域重叠时高优先级区域编号小的属性覆盖低优先级区域。常见的错误是区域划分不精确导致意外重叠或者优先级设置错误导致预想的规则未生效。建议画一张内存映射图明确标出每个区域的起始、结束地址和属性。大小与对齐不匹配这是最常犯的错误。如果基地址没有按区域大小对齐MPU会忽略未对齐的低位导致实际保护的区域不是你期望的。例如你想保护0x20001000开始的1KB区域但错误地将SIZE设为10对应2KB那么基地址0x20001000不符合2KB对齐MPU会将其对齐到0x20000000结果保护了错误的2KB范围。调试技巧使用公式(base_addr % region_size) 0来验证对齐。启用MPU前未配置任何区域如果ENABLE1且PRIVDEFEN0但没有任何区域的ENABLE位被置1那么第一条指令取指就会触发内存管理故障。务必确保在全局使能MPU前至少有一个区域或背景区域被正确配置和启用。任务切换时MPU上下文忘记保存/恢复在RTOS中每个任务可能有自己独立的MPU区域设置。在任务调度器进行上下文切换时必须保存旧任务的MPU配置通常是区域1-7区域0可能用于内核并恢复新任务的MPU配置。忘记这一步会导致新任务在错误的内存保护下运行极易触发故障。误用XN位导致指令取指故障如果你将代码所在的Flash区域错误地标记为XN1不可执行那么处理器从该区域取指时会立即触发内存管理故障。症状是系统一启动就进入硬故障。排查检查为代码段配置的区域的AP应为只读和XN应为0位。共享内存配置错误被多个任务或DMA访问的内存区域必须正确配置S共享位和缓存策略。如果配置为“非共享可缓存”而另一个总线主控如DMA修改了该内存由于缓存一致性问题CPU可能读到旧数据。在多核系统中此问题更复杂。故障地址寄存器无效不是每次故障MMARVALID或BFARVALID都会置位。例如由权限错误AP位不符触发的MPU故障MMARVALID是有效的。但由“执行禁止”XN位触发的故障MMARVALID是无效的。此时你需要依赖堆栈中的PC值来判断试图从哪个地址取指。当遇到棘手的、间歇性的硬故障时一个有效的策略是使用MPU进行“区域隔离调试”。暂时将怀疑有问题的任务的内存区域尤其是栈和堆配置得比实际需要更小、更严格并确保其他区域不覆盖它。这样任何越界访问都会立刻触发精确的MPU故障并通过MMADDR给出准确的越界地址远比依赖栈溢出检测或数据破坏后的现象来推断要直接得多。