ARM Cortex-M3下GNU LD链接脚本LONG()未按预期放置值问题排查
我在GNU ld链接脚本中添加了用于存储校验和的MEMORY定义:
_CRC_Value = 0x12345678; MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 80K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 524285 /* 范围到0x0807FFFB,按理说应该设为524284,但那样.map文件里的偏移就不对 */ CHECKSUM (rw) : ORIGIN = 0x807FFFC, LENGTH = 4 }
并定义了如下段:
.crc : { _Check_Sum = .; /* 把地址设为CHECKSUM区域的起始地址 */ LONG(_CRC_Value); } >CHECKSUM
我的.map文件显示内容似乎对齐正确(除非我理解有误):
.crc 0x0807fffc 0x4 0x0807fffc _Check_Sum = . 0x0807fffc 0x4 LONG 0x12345678 _CRC_Value
我曾在main.c中定义该符号:
uint32_t _Check_Sum __attribute__((section(".crc")));
不过后来将其注释掉了,因为链接器实际已定义该符号,无需重复定义。但无论是否注释,调试结果一致:
Expression | Type | Value | Address _Check_Sum | uint32_t | 1450704896 | 0x807fffc
该值1450704896对应0x56780000,看起来像是2字节对齐问题,但我找不到原因。我曾认为内存边界计算有误,但当我将FLASH的LENGTH设为524284(我认为正确的值)时,.map文件显示所有内容偏移至0x807FFFB地址,且.crc段变为5字节。
我使用的是STM32F101 MCU(基于ARM Cortex-M3)。我认为这并非字节序问题,但也可能考虑不周。我尝试在.ld脚本的不同位置放置该段,唯一变化是.map文件中增加了ALIGN信息,显示位置计数器的下一个地址为0x08080000,这是正确的。
有没有人知道我遗漏了什么?
1. 先修正FLASH长度的计算逻辑
STM32F101的512KB FLASH总字节数是524288,起始地址0x8000000,结尾地址是0x8000000 + 524288 = 0x8080000。要把CHECKSUM放在最后4字节(0x807FFFC~0x807FFFF),FLASH区域的长度应该设为524288 - 4 = 524284,对应范围是0x8000000~0x807FFFB,这个才是正确的。之前设524285会导致FLASH区域覆盖到0x807FFFC,和CHECKSUM区域重叠,这是引发地址分配异常的根源。
2. 为什么会出现0x56780000的错误值?
Cortex-M3是小端字节序,但这里不是字节序问题——你看到的0x56780000是因为数据只写入了低16位,高16位被清0。核心原因是FLASH区域结尾未做4字节对齐,链接器为了避免地址冲突,偷偷调整了.crc段的起始地址,但没保证4字节对齐,导致4字节的LONG值只成功写入低2字节,高2字节因地址不对被截断。
当FLASH长度设为524284时,0x8000000 + 524284 = 0x807FFFC,刚好是4字节对齐地址,CHECKSUM区域的起始地址0x807FFFC合法,不会出现偏移问题。
3. 链接脚本的具体调整
- 把FLASH的LENGTH改为524284,确保和CHECKSUM区域无重叠且地址对齐;
- 在.crc段定义里显式加4字节对齐指令,强制链接器保证地址合规:
.crc : ALIGN(4) { _Check_Sum = .; LONG(_CRC_Value); } >CHECKSUM
- 不要在main.c里重复定义
_Check_Sum,链接器已通过.crc段定义该符号,重复定义属于未定义行为,必须避免。
4. 验证要点
修改后重新编译,查看.map文件确认:
- .crc段起始地址为0x807fffc,长度4字节,无区域重叠;
- 调试读取
_Check_Sum的值应为0x12345678(小端存储下内存字节顺序是0x78、0x56、0x34、0x12,但读取uint32_t变量时会自动转换为主机序,显示为0x12345678)。
内容的提问来源于stack exchange,提问作者Hunter Ritchie

