问题现象:重定义回调函数不生效

使用STM32 HAL库时,我们经常重定义__weak函数来实现中断回调。例如:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    // 自定义逻辑
}

但调试时发现该函数从未被调用,程序执行的是HAL库中的空实现。编译无报错,运行时却“静默失效”。这往往让开发者困惑不已。

原理:__weak与链接器的工作机制

__weak是编译器(如ARMCC、GCC)的扩展属性,用于声明弱符号。链接器处理符号时遵循以下规则:

  • 强符号(普通函数/变量)优先于弱符号。
  • 若存在多个强符号,链接报错(重复定义)。
  • 若只有弱符号,则选择其中一个(通常第一个遇到的)。
  • 若强符号与弱符号同名,强符号覆盖弱符号。

HAL库中回调函数定义为__weak void HAL_UART_RxCpltCallback(...),用户重定义后应产生强符号覆盖弱符号。但若链接器未将用户目标文件纳入链接,或链接顺序导致弱符号先被解析,则覆盖失败。

排查步骤:从编译到链接

1. 确认函数签名完全一致

  • 返回类型、参数类型、函数名必须与HAL库声明严格匹配(包括const修饰)。
  • 检查是否误写为static(静态函数不参与全局符号链接)。

2. 检查用户文件是否参与编译

  • 在IDE(Keil、STM32CubeIDE、IAR)中确认包含该函数的.c文件已加入工程并编译。
  • 查看编译输出,确保生成了对应的.o文件。

3. 查看map文件定位符号

  • 生成map文件(Keil: Options -> Listing -> Linker Listing;GCC: -Wl,-Map=output.map)。
  • 搜索函数名,观察其地址与来源。若地址指向HAL库的.o,说明用户强符号未生效。
  • 检查符号表中是否存在多个同名符号,以及链接器最终选择了哪个。

4. 调整链接顺序

链接器按命令行中目标文件的顺序解析符号。若HAL库的.a或.o排在用户文件之前,且弱符号先被引用,则可能直接使用弱定义。解决方法:

  • Keil MDK:在“Options for Target -> Linker”中取消勾选“Use Memory Layout from Target Dialog”,手动调整.o顺序,将用户文件放在HAL库之前。或使用--keep选项强制保留符号。
  • GCC/STM32CubeIDE:在链接脚本或Makefile中,将用户目标文件放在库文件之前。例如:
    $(CC) $(OBJS) -o $(TARGET).elf $(LDFLAGS) -lHAL
    
    确保$(OBJS)包含用户文件且位于-lHAL之前。
  • IAR:在“Linker -> Config”中调整文件顺序,或使用__weak替代方案。

5. 使用链接器选项强制覆盖

  • GCC: 添加-Wl,--allow-multiple-definition(不推荐,可能掩盖其他问题)。
  • 更安全的方法:使用__attribute__((used))确保用户函数不被优化掉,并检查链接脚本的KEEP段。

完整代码示例

以下以STM32CubeIDE(GCC)为例,展示正确重定义与链接配置。

用户文件 user_callback.c:

#include "main.h"

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        // 自定义处理
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

Makefile片段(确保用户文件在库之前):

OBJS = main.o user_callback.o stm32f1xx_hal_uart.o ...
LIBS = -lHAL
$(TARGET).elf: $(OBJS)
    $(CC) $(OBJS) $(LIBS) -o $@ $(LDFLAGS)

链接脚本检查(.ld文件): 确保.text段包含所有输入段,且未使用/DISCARD/丢弃用户函数。

注意事项

  • 避免使用static:重定义的回调函数必须为全局符号。
  • 检查优化等级:高优化等级(-O2/-O3)可能内联或删除未引用函数,使用__attribute__((used))防止。
  • CubeMX生成代码:若在CubeMX中配置了回调,生成的代码可能已包含弱定义,需手动删除或调整。
  • 多文件重定义:确保只有一个强符号,否则链接报错。
  • 调试技巧:在用户函数入口设断点,若未命中,结合map文件分析符号绑定。

通过以上步骤,可系统化定位并解决__weak函数重定义失效问题,确保自定义逻辑正确执行。