
问题描述
有客户在使用 STM32CubeU5 包下的 SBSFU 示例代码(STM32Cube_FW_U5_V1.2.0\Projects\B-U585I-IOT02A\Applications\SBSFU),移植过程中发现程序运行不起来,如下是打印信息 :
图 1 系统置 log 信息 同时客户反馈做了些代码修改: 原先 Secure App 使用的是 SRAM3, 现在改为 SRAM1 原先 Non-Secure App 使用的是 SRAM1, 现在改为 SRAM3 如此修改后程序运行不起来了。
把 SBSFU_Boot, SBSFU_Appli_Secure, SBSFU_Appli_NonSecure, SBSFU_Loader_Secure, SBSFU_Loader_NonSecure 依次编译(其中SBSFU_Appli_Secure 和 SBSFU_Appli_NonSecure 这两个工程的编译优化关闭,以方便调试)并通过运行 regression.sh, SBSFU_UPDATE.sh 脚本烧录固件到 MCU 后,从打印信息中确实能看到问题重现。与客户描述的现象一模一样。 从打印信息看,程序运行 SBSFU_Boot 工程代码并有相应打印信息,但不确定是否已经跳转到 SBSFU_Appli_Secure 代码,于是使用 STM32CubeProgrammer 的 hot plug模式查看程序当前 PC 值 :
图 2 当前 PC 值 可知,当前 PC 值为 0x0C01 B486。 再分别查看 SBSFU_Appli_Secure 和 SBSFU_Appli_NonSec 的 BuildAnalyzer 窗口 :
图 3 SBSFU_Appli_Secure 的 Build Analyzer 窗口 其所占 FLASH 范围为 0x0C01 A400 起始地址的 14.12KB 代码空间,使用了 SRAM1的 192KB 空间。
图 4 SBSFU_Appli_Secure 的向量表位置 进一步查看可知 SBSFU_Appli_Secure 的向量表位置是 0x0C01 A400。 再查看下 SBSFU_Appli_NonSecure 工程的 BuildAnalyzer 窗口:
图 5 SBSFU_Appli_NonSecure 的 Build Analyzer 窗口 可知其所占 FLASH 起始于 0x0802 0400, 使用 SRAM3 这个 512KB 的空间。进一步查 SBSFU_Appli_NonSecure 工程的向量表 :
图 6 SBSFU_Appli_NonSecure 向量表位置 可知 SBSFU_Appli_NonSecure 工程的向量表 位于 0x0802 0400. 当前 PC 值为 0x0C01 B486,可知位于 SBSFU_Appli_Secure 范围内。同时,在图 2 中,当前
IPSR 值为 7. 结合编程手册 PM0264:
图 7 IPSR 定义 可知,IPSR=0x7, 表示当前出现了 SecureFault 中断。 当前暂可得知这些信息,接下来需要调试下。 由于程序从 SBSFU_Boot 跳转到 SBSFU_Appli_Secure 时,会去检查 header 相关信息,以证实其真实有效后才会跳转过去。这会影响接下来的调试。为方便调试,我们可修改选项字节让程序直接从 SBSFU_Appli_Secure 入口处开始执行。 如之前所述,SBSFU_Appli_Secure工程的向量表位于0x0C01 A400, 于是通过STM32CubeProgrammer修改 SECBOOTADD0 的值为 0x0C01 A400 :
图 8 修改 SECBOOTADD0 如此一来,MCU每次复位都是从0x0C01A400上启动了,这样方便我们调试SBSFU_Appli_Secure 代码。 接下来像调试常规 TrustZone 工程一样,联调 SBSFU_Appli_Secure, SBSFU_Appli_NonSecure: 在 SBSFU_Appli_Secure 的 main()函数处设置一个断点。经确定,程序会在这里停住。这说明程序确实有运行 SBSFU_Appli_Secure 工程,这跟预想的一致。然后在SBSFU_Appli_NonSecure 工程内的 Reset_Handle()函数内设置一个断点,经调试,程序也会在这里停住:
图 9 程序有运行到 NonSecure App 可见程序确实也有运行到 SBSFU_Appli_NonSecure。 在 SecureFault 内设置一个断点,让程序在这里停下来:
图 10 程序最终触发了 SecureFault 如上图,查看 SAU->SFSR 寄存器,可知 INVEP 被置位。在 PM0264文档中查看INVEP 的定义:
图 11 SAU->SFSR.INVEP 定义 可知访问的目标资源属性不对,进一步调试,触发此SecureFault发生在SBSFU_Appli_NonSecure 工程代码刚启动,还没运行到 main() 函数之前,在运行 Reset_Handle() 内的__PROGRAM_START() 期间出现了问题,那么此函数是做什么的呢? 它是一个 cmsis 函数,位于 cmsis_gcc.h 文件中:
如上代码所示,它就做了两件事,将位于 flash 上的数据拷贝到 SRAM3 中 (对应全局变量初始化过程)。将全部未带初始值的全局变量赋初值 0. 它们所涉及到的变量均位于SRAM3 内。因此,我们再检查下它的 MPCBB 安全属性配置:
图 12 SRAM3 对应的 MPCBB3 的安全属性配置 如上可见,SRAM3 的 MPCBB3 各个位(MPCBB3_SECCFGRx)仍然为 1(Secure), 这明显不对,NonSecure 工程用到的 SRAM 对应的属性应该为非安全才对。于是找到SBSFU_Appli_Secure 工程内的 GTZC MPCBB3 配置代:
这段配置 MPCBB3 的代码明显有些问题,我们的目标是需要将 MPCBB3 所有安全属性对应位均配置为 0,因此,修改并简化 unsecure_sram3()函数:
修改后的代码直接将 SRAM3 内所有 block 设置 NonSec 属性,再次验证代码,结果从打印信息看到程序已经完全恢复正常工作了。
图 13 Non-Secure App 正常运行 至此,Non-Secure App 已经正常运行,至少说明,Secure App 和 Non-Secure 这两个程序本身加在一块运行并无太大的问题。 由于之前修改了选项字节 SECBOOTADD0,所以现在要修改回去让整个系统重头开始运行测试下。再次运行 regression.sh 和 SBSFU_UPDATE.sh 脚本烧录固件,让程序再次从最原始位置(0x0C0 6000)开始运行:
图 14 运行 log 如上,从打印信息可以看到,程序从 boot 上启动,并最终已经跳转到 Non-Secure App 中去了,但是并没有看到线程的打印信息。 于是查看客户的 Non-Secure App 的线程相关代码。在代码中,总共创建了两个测试线程。一个线程负责每 3 秒发送一次事件,另一个线程则接收事件并打印。但明显看到两个线程并没有为其分配安全侧的栈,于是为其添加(红色部分为添加的代码):
修改代码后再次测试,结果这次修改并没有改善效果。问题依旧存在。但由于这个基于 trustzone 的 FreeRTOS,在 NS APP 侧创建任务时,为了防止资源冲突,必须利用驻守在安全侧的 FreeRTOS 内核代码为每个任务分配 stack. 这个是肯定要添加的代码。这里添加代码并没有错。接下来需要进一步联调下 Secure App 和 Non-Secure App 代码。 如下图所示,通过不复位的 attach 上去的联调方法,等进入到调试模式下后,点击下暂停按键,让程序暂停住:
图 15 触发了 ASSERT 结果发现,原来触发了断言,FreeRTOS 为 hand_evt_task 分配安全侧 stack 时,其分配的 SecureContext 安全上下文为 0,与预期值不符。顺着 xSecureContext 摸瓜上去,进一步发现程序调用 pvPortMalloc 为 hand_evt_task 任务分配安全侧的 stack 空间时返回空指针。于是接着看为什么会分配失败。最终摸瓜摸到 FreeRTOS 安全侧的 heap初始化的结果有问题: 在 prvHeapInit()函数内部:
如上图所示,对于代码 172 行,明明 uxAddress 值为 0x30002908,而pxFirstFreeBlock 值为 0x30000118,两者相减后的值赋给pxFirstFreeBlock->xBlockSize, 结果赋完值后它依旧为 0. 有点怪异! 代码运行到这里的时候,已经是运行 Secure App 的代码,当前 CPU 处于 secure 状态。它的内存对应的是SRAM1。 我修改了下代码,直接在代码中给 pxFirstFreeBlock->xBlockSize 赋值 1024. 再次运行,结果发现 pxFirstFreeBlock->xBlockSize 依旧为 0!真是太怪异了! 然后突然发现,pxFirstFreeBlock 的值是 0x30000118,它是 SRAM1 的首个block。那么 SRAM1 的各个 block 的安全属性当前又是什么状态呢?于是查看 MPCBB1:
图 16 MPCBB1 的安全属性配置 如上图所示,MPCBB1_SECCFGR0.SEC0=0, 也就是说,SRAM1 的首个 block(512个字节)为非安全属性。 但是,当前的情况下,FreeRTOS 内核正在为 hand_evt_task 任务分配安全侧的 stack. 按理就是安全的,也就是说 MPCBB1_SECCFGR0.SEC0=1 才对。那么哪里的代码配置这个 MPCBB1_SECCFGR0.SEC0=0 的呢? 通过搜索代码和在调试下监视 MPCBB1 寄存器的变化,最终发现在 Boot 工程代码中:
将这行代码改成: GTZC_MPCBB1_S->SECCFGR[0] = 0xffffffff; 再次测试,果然程序能够恢复正常。 从上述代码可知,这里有个宏 TFM_ERROR_HANDLER_NON_SECURE 控制这个配置,它的原意是当系统产生 Error_Handler()时,代码会转去调用位于 SRAM1 上的 NonSecure APP 代码进行处理。很显然,修改后的客户代码并不需要这样处理,因此,更好的处理方式是关闭宏 TFM_ERROR_HANDLER_NON_SECURE: 同时,boot 程序在跳出去之前会清空 NonSecre App 对应的内存 SRAM1, 这里应该换成清空 SRAM3:
改完后,最终所有代码均运行正常。
图 17 最终修改完后的运行效果 至此,此问题被最终定位和解决。
原文链接:https://blog.csdn.net/2501_92678806/article/details/149594315 |
经验分享 | 利用TIMER测量信号周期及占空比的精度话题
经验分享 | Flash 全片自检过程中巧用 Linker 自定义变量
经验分享 | 高精度定时器如何实现 PWM 零满占空比的连续切换
经验分享 | STM32G474 勘误手册中 SRAM Write Error 详解
经验分享 | 定时器触发ADC时可能遇到的几种情形
实战经验 | 1小时在STM32MPU上运行YOLOv8——训练篇
经验分享 | STM32CubeMX 生成时钟获取函数的分析
经验分享 | STM32双定时器+ADC+DMA实战案例
经验分享 | STM32U5系列TIMER+DMA+DAC应用演示
经验分享 | 基于STM32片内信号的ADC应用演示