Zephyr平台下绕过NULL指针异常保护读取0-0x3FF区域的方法咨询
问题背景
基于Nordic nRF52840(ARM Cortex-M)开发Zephyr项目,启用了以下空指针异常MPU保护配置:
CONFIG_NULL_POINTER_EXCEPTION_DETECTION_MPU=y CONFIG_CORTEX_M_NULL_POINTER_EXCEPTION=y CONFIG_CORTEX_M_NULL_POINTER_EXCEPTION_PAGE_SIZE=0x400
未开启CONFIG_USERSPACE。需对闪存0-0xC000地址段的bootloader做CRC32校验,但读取0-0x3FF地址段时触发MPU错误——上述配置通过MPU禁止了该区域的读写操作。
尝试过用__syscall调用crc32函数绕过保护,但该机制仅在CONFIG_USERSPACE启用时才会提升权限,当前直接调用无法解决问题;已添加CONFIG_APPLICATION_DEFINED_SYSCALL=y,也尝试给模块加-D__ZEPHYR_USER__编译宏,均无效。
可行解决方案
方案1:临时修改MPU权限后恢复
直接操作MPU寄存器,临时开放0-0x3FF区域的读取权限,完成CRC校验后恢复原有配置。这种方式无需修改内核配置,灵活性高。
示例代码(适配Cortex-M4/M4F):
#include <zephyr/arch/arm/aarch32/cortex_m/mpu.h> #include <zephyr/sys/crc.h> #include <stdint.h> #include <stddef.h> uint32_t calculate_bootloader_crc(void) { // 保存当前MPU寄存器状态 uint32_t saved_rnr = MPU->RNR; uint32_t saved_rbar = MPU->RBAR; uint32_t saved_rasr = MPU->RASR; // 临时禁用MPU,避免配置过程中触发异常 MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; // 配置临时MPU区域:允许读取0x00000000-0x000003FF(1KB) MPU->RNR = 0; // 选择未被内核使用的区域(可根据实际情况调整) MPU->RBAR = 0x00000000 | MPU_RBAR_VALID_Msk; // 基地址设为0,标记为有效区域 // 设置权限:只读,区域大小1KB,使用闪存属性,启用该区域 MPU->RASR = (MPU_RASR_AP_RO_RO << MPU_RASR_AP_Pos) | (MPU_RASR_SIZE_1KB << MPU_RASR_SIZE_Pos) | MPU_RASR_ATTRINDX(0) | MPU_RASR_ENABLE_Msk; // 重新启用MPU,保留特权模式默认区域访问权限 MPU->CTRL |= MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk; // 执行完整的CRC32校验(覆盖0-0xC000) uint32_t crc_result = crc32(0U, (const uint8_t *)0x00000000, 0xC000); // 恢复原有MPU配置 MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; MPU->RNR = saved_rnr; MPU->RBAR = saved_rbar; MPU->RASR = saved_rasr; MPU->CTRL |= MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk; return crc_result; }
注意:需确认所选MPU区域未被Zephyr内核占用,可参考Zephyr源码中MPU初始化逻辑确认可用区域。
方案2:使用内核内存访问接口读取受限区域
利用Zephyr提供的sys_mem_read()函数,该函数支持在特权态下安全访问受MPU限制的内存区域。逐块读取0-0x3FF的数据后再计算CRC,0x400以上区域可直接读取。
示例代码:
#include <zephyr/sys/mem_manage.h> #include <zephyr/sys/crc.h> #include <stdint.h> #include <stddef.h> #include <zephyr/sys/util.h> uint32_t calculate_bootloader_crc(void) { uint32_t crc_result = 0U; uint8_t read_buf[64]; // 可根据实际调整缓冲区大小 size_t offset; // 处理0-0x3FF受限区域,逐块读取 for (offset = 0; offset < 0x400; offset += sizeof(read_buf)) { size_t read_len = MIN(sizeof(read_buf), 0x400 - offset); if (sys_mem_read((uintptr_t)offset, read_buf, read_len) != 0) { // 读取失败,返回错误值 return UINT32_MAX; } crc_result = crc32(crc_result, read_buf, read_len); } // 处理0x400-0xC000不受限区域,直接计算CRC crc_result = crc32(crc_result, (const uint8_t *)0x400, 0xC000 - 0x400); return crc_result; }
该方案无需直接操作MPU寄存器,依赖内核提供的安全访问接口,代码更简洁。
方案3:调整空指针保护范围(需权衡安全性)
若业务场景允许,可修改Zephyr配置缩小空指针保护的覆盖范围,避免影响bootloader区域的读取。例如:
- 尝试将
CONFIG_CORTEX_M_NULL_POINTER_EXCEPTION_PAGE_SIZE调整为更小的值,但需确认Zephyr是否支持小于0x400的配置; - 自定义MPU区域配置,将0-0xC000标记为允许访问的闪存区域,同时保留其他地址的空指针保护。
此方案需要修改内核初始化逻辑,复杂度较高,且会缩小空指针保护的安全范围,需谨慎评估。
内容的提问来源于stack exchange,提问作者gulchrider

