STM32跨固件通过固定地址调用函数返回时触发HardFault问题咨询
问题描述
首先,抱歉我的英语水平有限,我是一名巴西开发者,目前正在同时学习英语和编程。
我正在开发基于STM32单片机的固件应用,存储地址划分为引导加载程序BL、固件FWA、固件FWB三个分区。为了节省Flash存储空间,FWB需要调用FWA中定义的函数,目前我遇到一个无法定位原因的问题:我可以成功进入目标函数并执行内部的所有操作,但当执行到return返回语句时就会触发HardFault错误。我已经使用分散加载文件配置了FWA和FWB的共享内存,分散加载文件配置如下:
LR_IROM1 0x08004000 0x00004000 { ; load region size_region ER_IROM1 0x08004000 0x00004000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 UNINIT 0x00001000 { ; No init data *(.noinit) } RW_IRAM2 0x20001000 0x00004000 { .ANY (+RW +ZI) //RW data } }
我的目标函数定义如下,已指定存放于固定Flash地址:
uint getInt(void) __attribute__((section(".ARM.__at_0x8006000")));//My fw was at 0x4000, total size is 0x4000, //so space is not the problem. uint getInt(void){ return 42; }
我在FWA中自测调用该函数的代码如下:
uint (*fun_ptr)(void) = (uint(*)(void))0x8006000; uint _get_int= fun_ptr(); uint result = _get_int();
补充测试情况:当使用如下方式调用时所有场景都可以正常运行,只有通过绝对地址赋值函数指针调用时会出现问题:
IntFunc p = getInt; uint ccccc = p(); uint aaaa = getInt();
如前文所述,我可以正常进入getInt()函数执行内部所有代码,只有执行到return 42语句时才会触发HardFault。
我已经找到了通过结构体封装函数指针的实现方案,可以正常运行,但我希望能了解当前问题的根本原因,避免后续再出现同类错误。可正常运行的实现方案如下:
main.h文件代码:
typedef struct bootloaderApi bootloaderApi; typedef unsigned int (*do_something_t)(void); struct bootloaderApi{ do_something_t do_something; };
Main.c文件代码:
const struct bootloaderApi api __attribute__((section(".ARM.__at_0x8005000"))) = {.do_something = &getInt}; //address 0x5000 to test.
调用代码:
struct bootloaderApi *apit = (struct bootloaderApi *)0x8005000; uint pa = api.do_something();
以上方案可以正常运行,pa可以正确返回数值42。
根本原因
- STM32使用的Cortex-M内核仅支持Thumb指令集,架构规范要求函数指针的最低位必须为1,用于标识当前处于Thumb执行状态。如果函数指针最低位为0,内核会尝试切换到不支持的ARM模式,直接触发硬件错误。
- 你直接将0x8006000强制转换为函数指针时,该地址最低位为0,不符合内核要求。调用函数时链接寄存器LR保存的返回状态位非法,执行return语句时内核检测到状态异常,触发HardFault。
- 结构体封装方案正常运行的原因:编译器处理
&getInt这类函数地址取值操作时,会自动将地址最低位设置为1,符合Thumb指令集要求,因此调用不会出错。
注:你提供的自测调用代码存在笔误,将函数指针调用后的uint返回值再次当成函数调用,本身也会触发非法地址访问,属于示例编写错误,不影响核心问题定位。
修复方案
- 方案1:给绝对地址手动设置Thumb标志位,赋值函数指针时改为
uint (*fun_ptr)(void) = (uint(*)(void))(0x8006000 | 1);即可正常调用。 - 方案2:继续使用已验证的结构体封装函数表方案,该方案是跨固件分区调用的工业级常用方案,可扩展性更强,新增函数只需修改结构体定义即可,无需手动管理每个函数的地址。
内容的提问来源于stack exchange,提问作者Cristhian Guedes
相关产品推荐
相关产品推荐

