STM32位置无关代码全局指针偏移问题:寻求main前修复方案
解决STM32双分区架构下PIC全局指针的预main修复问题
问题分析
- 已在STM32上实现位置无关代码(PIC),向量表与GOT完成修补,但全局函数指针
ptrmyAdd(指向.text段的myAdd函数)、全局字符串指针myString(指向.rodata段的"foo bar"字符串)仅在无Flash偏移时正常,偏移后因未补充正确偏移量失效 - 手动在
main()中重新初始化指针可适配任意偏移,但需求是在main()之前完成修复 - 限制条件:.text段无法移至RAM;遍历.data段更新疑似Flash地址存在风险;系统采用双应用分区架构,Bootloader会将应用下载到空闲分区,启动偏移不固定
解决方案
1. 基于启动文件的预main钩子修复
STM32启动文件(如startup_stm32xxxx.s)在进入main()前会执行.data/.bss初始化等流程,可在此阶段插入指针修复逻辑:
- 在启动文件中找到
__main调用前的位置,添加自定义修复函数调用:bl FixGlobalPointers - 实现修复函数:
#define LINK_BASE_ADDRESS 0x08000000 // 链接脚本中指定的默认Flash基地址 // 获取当前应用的运行偏移量:通过当前函数地址与链接基地址的差值计算 static uint32_t GetAppOffset(void) { return (uint32_t)&GetAppOffset - LINK_BASE_ADDRESS; } // 全局指针声明 extern int (*ptrmyAdd)(int, int); extern const char* myString; // 链接时的原始指针值(需在链接脚本中导出) extern int (*__original_ptrmyAdd)(int, int); extern const char* __original_myString; void FixGlobalPointers(void) { uint32_t offset = GetAppOffset(); // 给原始指针加上运行时偏移 ptrmyAdd = (int(*)(int,int))((uint32_t)__original_ptrmyAdd + offset); myString = (const char*)((uint32_t)__original_myString + offset); } - 修改链接脚本,导出原始指针符号:
链接时会把指针的无偏移原始值存入Flash的.data加载区,运行时读取该值并加上偏移量,即可更新全局指针。.data : { ... __original_ptrmyAdd = .; LONG(ptrmyAdd); __original_myString = .; LONG(myString); ... } >RAM AT>FLASH
2. 利用GCC构造函数属性自动执行修复
若使用GCC编译器,可为修复函数添加__attribute__((constructor))属性,让编译器自动在main()前调用:
#define LINK_BASE_ADDRESS 0x08000000 // 全局指针声明 extern int (*ptrmyAdd)(int, int); extern const char* myString; extern int myAdd(int, int); extern const char __myString[]; // 直接引用字符串常量的符号 __attribute__((constructor)) static void FixGlobalPointers(void) { uint32_t offset = (uint32_t)&FixGlobalPointers - LINK_BASE_ADDRESS; // 直接取函数/字符串的链接地址,加上运行偏移 ptrmyAdd = (int(*)(int,int))((uint32_t)&myAdd + offset); myString = (const char*)((uint32_t)__myString + offset); }
注意:需确保
myAdd和字符串常量的符号在链接时可见,避免优化导致符号丢失。
3. 重构为GOT自动处理的PIC指针
若允许修改代码结构,可强制全局指针通过GOT访问,让编译器自动处理偏移:
- 编译时添加
-fPIC和-mno-pic-data-is-text-relative(ARM平台)编译选项 - 此时编译器会自动将全局指针的访问路由到GOT表,而你已经完成了GOT的修补,指针会自动适配运行偏移,无需手动修复
关键注意事项
- 偏移量计算必须基于当前运行地址:通过函数自身地址(PC相关值)与链接基地址的差值计算,避免硬编码分区地址,适配任意分区
- 链接脚本修改需准确,确保原始指针符号正确导出,避免加载时地址错误
- 构造函数属性仅在GCC等兼容编译器中有效,若使用其他编译器,优先选择启动文件钩子方案
内容的提问来源于stack exchange,提问作者Marcin Kłos
相关产品推荐
相关产品推荐

