STM32 HAL库中__weak函数被用户重定义后链接脚本仍保留原符号的排查方法
一、问题现象与背景
在STM32 HAL库开发中,我们经常重定义__weak函数来实现自定义回调,例如:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 用户代码
}
但有时会发现:用户函数明明已经定义,运行时却仍进入HAL库的默认空实现。用arm-none-eabi-nm查看ELF,发现两个符号同时存在,或者链接脚本(.ld)中保留了原弱符号。这类问题通常与__weak机制、链接器符号解析顺序、链接脚本的KEEP/EXCLUDE规则有关。
二、原理讲解:__weak与链接器符号解析
2.1 __weak的本质
__weak是编译器属性(GCC下为__attribute__((weak))),它告诉链接器:该符号是弱符号。当存在同名强符号时,链接器优先选择强符号;若只有弱符号,则使用弱符号。
HAL库中大量回调函数定义为:
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
2.2 链接器符号解析规则
- 强符号只能定义一次,重复定义会报错。
- 弱符号可被强符号覆盖。
- 若多个弱符号同名,链接器通常选择第一个遇到的。
-
链接脚本中的
KEEP、EXCLUDE_FILE、PROVIDE等指令会影响符号的保留与解析。
2.3 常见误判原因
- 用户函数未加
__weak,但被放在不同编译单元,链接顺序导致弱符号先被解析。 - 链接脚本中使用了
KEEP(*(.text.HAL_UART_RxCpltCallback)),强制保留库中弱符号。 - 使用了
-ffunction-sections和--gc-sections,但链接脚本未正确丢弃弱符号段。 - 用户函数被编译器优化掉(未调用或
static)。 - 多个弱符号同名,链接器选择了库中的版本。
三、排查步骤
步骤1:确认符号表
使用arm-none-eabi-nm查看符号类型:
arm-none-eabi-nm -C build/Project.elf | grep HAL_UART_RxCpltCallback
-
W表示弱符号,T表示强符号。若同时出现W和T,说明强符号存在,但需确认最终地址。
步骤2:分析map文件
在链接选项中添加-Wl,-Map=output.map,然后搜索符号:
grep -n "HAL_UART_RxCpltCallback" output.map
观察该符号被分配到哪个段、来自哪个目标文件。若来自stm32f4xx_hal_uart.o,说明弱符号被保留。
步骤3:反汇编确认调用地址
arm-none-eabi-objdump -d build/Project.elf | grep -A5 "HAL_UART_RxCpltCallback"
查看中断处理函数中调用的地址是否指向用户函数。
步骤4:检查链接脚本
重点检查:
- 是否有
KEEP指令强制保留库函数段。 - 是否使用了
EXCLUDE_FILE排除用户目标文件。 -
PROVIDE是否覆盖了符号。
示例问题链接脚本片段:
.text :
{
KEEP(*(.text.HAL_UART_RxCpltCallback))
*(.text*)
}
上述KEEP会强制保留弱符号,导致用户强符号无法覆盖。
步骤5:检查编译选项
- 确保用户文件参与编译且未被
--gc-sections丢弃。 - 确保用户函数非
static,且声明与HAL库一致。 - 检查
-ffunction-sections是否开启,若开启需配合--gc-sections。
四、完整代码示例
4.1 用户重定义回调
/* user_callback.c */
#include "main.h"
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// 用户处理逻辑
HAL_UART_Transmit(&huart1, (uint8_t*)"OK\r\n", 4, 100);
}
}
4.2 正确的链接脚本片段
.text :
{
*(.text*)
*(.rodata*)
} > FLASH
避免使用KEEP强制保留HAL库弱符号段。
4.3 编译与链接命令示例
arm-none-eabi-gcc -c -ffunction-sections -fdata-sections user_callback.c -o user_callback.o
arm-none-eabi-gcc -Wl,--gc-sections -Wl,-Map=output.map -T stm32f4.ld *.o -o Project.elf
五、注意事项
-
不要对HAL库弱符号使用
KEEP,除非你明确需要保留默认实现。 - 用户重定义函数必须与HAL库原型完全一致(参数、返回值、调用约定)。
- 若使用C++,注意
extern "C"避免名称修饰。 - 开启
--gc-sections时,确保用户函数被引用,否则可能被丢弃。 - 使用
__attribute__((used))可防止函数被优化掉。 - 定期检查map文件,确认符号来源,是排查链接问题的黄金手段。
通过以上方法,可系统性地定位并解决__weak函数重定义后链接脚本仍保留原符号的问题,确保回调按预期生效。