PAYATHON 2026

Cortex-M0+ 调用 __udivsi3 后触发 HardFault,如何排查?

数据阿明

结论

__udivsi3 是 GCC 为目标架构提供的无符号 32 位除法运行库函数。你给出的链接映射显示,它位于 .text 段,地址是 0x000004e8,处于链接脚本定义的 FLASH 范围内。函数指令从 Flash 取指执行很常见,这本身不表示函数在向 Flash 写数据。

现有信息还不足以确定 HardFault 的具体原因。反汇编器将 0xff1ee8bd 标为 .word,单凭这一点也无法证明 CPU 正在执行写 Flash 指令。需要先确认故障时的 PC、LR 和异常状态寄存器,再检查对应地址的机器码和链接结果。

先确认故障现场

在 Cortex-M 上,HardFault 处理程序可以读取异常发生时压入栈中的寄存器。至少记录 PC、LR、xPSR,以及 SCB->CFSR 和 SCB->HFSR。这些信息有助于判断故障是否由无效指令、总线访问或异常返回等情况引起。如果 HardFault 是由可配置故障升级而来,还要检查 CFSR 中对应的状态位。

将现场中的 PC 与 ELF 文件的反汇编和链接映射进行对照:

arm-none-eabi-objdump -d -S myproj.out
arm-none-eabi-addr2line -e myproj.out -f -C 0x故障PC

单独检查 0x992 附近的指令,确认 CPU 实际到达的地址、指令边界和调用路径。反汇编输出中的 .word 表示该处被显示为一个字的数据。要判断 CPU 是否把它当作指令执行,还需结合目标架构、机器码和运行时 PC。

检查链接布局和运行时初始化

链接映射中的片段:

.text 0x000004e8 0x114 ...libgcc.a(_udivsi3.o)
0x000004e8 __aeabi_uidiv
0x000004e8 __udivsi3

这表明链接器将除法实现放进了 .text,并将它放在 Flash 区域。还要检查完整的 map 文件和 myproj.lds,确认 .text、.data、.bss 的地址和大小都在预期的内存区域内,输出段之间没有重叠或越界。

链接参数中包含 -nostartfiles 和 -nostdlib,因此不能只根据常见启动流程假定运行时初始化已经完成。应确认启动代码是否按照链接脚本的约定,将 .data 从 Flash 复制到 SRAM、清零 .bss,并完成必要的芯片初始化。初始化缺失可能引发其他故障,但现有片段无法说明它是否与这次 HardFault 有关。

除法函数为什么会被调用

如果目标配置没有可用的硬件整数除法指令,编译器会把整数除法转换为运行库调用。你的目标选项包含 -mcpu=cortex-m0plus、-march=armv6-m 和 -mfloat-abi=soft;映射文件也显示最终链接了 libgcc.a 中的 _udivsi3.o。因此,__udivsi3 出现在调用路径中是合理的。optimize("-O0") 不会自动将运行库函数移到 RAM,也无法修复由地址或初始化问题引起的故障。

如果需要让函数在 RAM 执行

通常不需要为了避免“写 Flash”而把 __udivsi3 移到 RAM,因为函数在 Flash 中执行并不等于向 Flash 写入。只有在明确要求代码在 RAM 中运行时,例如设备运行约束要求相关代码驻留 RAM,才需要调整链接布局。

一种做法是定义 RAM 执行段,并指定它在 Flash 中的加载地址。启动代码必须先将该段复制到 SRAM,之后才能调用其中的函数。概念示例如下,具体段名和语法应以项目的链接脚本为准:

.ramfunc :
{
    *(.ramfunc*)
} > SRAM AT > FLASH

函数可以通过 __attribute__((section(".ramfunc"))) 放入该段。不过,这只会影响明确标记的函数,不会自动将 libgcc.a 中的 __udivsi3 移入该段。要移动运行库实现,还需确认库对象的输入段名称、链接脚本的匹配规则和启动复制逻辑,并检查该对象依赖的其他函数是否也需要在 RAM 中。完成后,应通过 map 文件和反汇编确认最终 VMA 位于 SRAM、LMA 位于 Flash,并且程序在调用前已完成复制。

备注:内容仅供参考。