6. 常见问题排查 (FAQ)
6.1 脚本修改与布局问题
Q1: 链接为什么报定位计数器回退?
现象:链接阶段报错,指认脚本中的赋值行:
kf32ld: error: <链接脚本>:<行号>: unable to move location counter backward for: .data
- 报错行后常跟着若干条
will not fit in region溢出信息,属同一次定位错乱的连带输出; - 赋值行位于参与松弛(relaxation)的代码段内时,报输入段重叠:
kf32ld: error: relaxation: overlapping input sections in .text: …(.text._start) ends at 0x200 but …(.text.startup) starts at 0x100。
原因:. = <表达式> 按绝对地址解释。地址错误会使定位计数器回退,其后内容被放到错误位置。常见写法:
- RAM 内的裸常数偏移(
. = 0x8000;)被当成绝对地址 0x8000; - 经符号间接赋值(
s = 0x0; . = s;)同样赋成绝对地址; .text段首的. = 0x0000;在 Flash 起始地址非 0 的型号上,把向量表定位到区域之外。
解决方案:
- 移除
.text段首的绝对地址赋值,链接器自动从 Flash 起始地址放置向量表:
.text :
{
- . = 0x0000;
+ /* 省略:链接器默认从 ORIGIN(flash) 开始放置向量表 */
KEEP (*vector.o(.text*)) /* chip interrupt vector ,writed in file named vector.s or vector.c */
KEEP (*vector.obj(.text*)) /* compatible */
- 段内偏移改用
ORIGIN(...)基址运算,或改用相对推进写法(见 4.1 定位计数器.的语义):
__lpdata_start__ = ALIGN(4);
*(.lpdata*) /* Reset but Keep Space */
- /* . = 0x8000 ; */ /* 裸常数在 LLD 中被解释为绝对物理地址 0x8000 */
+ /* . = ORIGIN(ram) + 0x8000 ; */ /* 显式加上 ORIGIN(ram) 基址,确保留在 RAM 区域内 */
__lpdata_end__ = ALIGN(4);
Q2: 指定段为什么没有落在期望地址?
现象:链接成功,但源码中通过 section 指定的变量或常量没有出现在期望地址。
原因:定位语句不在对应通配规则之前,或区域内赋值漏加 ORIGIN。
解决方案:按以下顺序检查:
- 定位语句位置是否正确:Flash 定位在
.text内、RAM 定位在.data内,且在对应通配规则之前; - 区域内的
.赋值是否加了ORIGIN; - 通过
.map搜索符号名,确认实际落点。
段名不一致或匹配规则缺少 * 时,输入段会被判为孤儿段并导致链接失败(见 6.4 Q1 链接报输入段未被脚本放置)。
Q3: RAM 占用超限如何压缩?
现象:链接报 ram 区域溢出,或 RAM 占用接近上限。
原因:指定地址段产生 0 填充、.lpdata 保留区过大,或堆、栈预留过大。
解决方案:
- 确认
.data区中指定地址段是否因不连续摆放产生了 0 填充(见 5.2 定义变量与函数到指定 RAM 地址); .lpdata保持区如未接近 32K,按实际用量缩小;- 减小堆栈大小(见 5.4 修改堆与栈大小)。
Q4: 栈指针为什么会指向 Flash 空间?
现象:链接通过,但 __initial_sp 为 0x10000;SP 未指向 RAM,函数调用与中断压栈实际在 Flash 上写入,程序跑飞或反复复位。
原因:LENGTH(ram) 是 RAM 长度,不是结束地址;.stack 的位置由 __Stack_Size__ 决定,与 __initial_sp 无关,因此链接阶段不报错。
解决方案:栈顶 __initial_sp 必须为 RAM 起始地址加区域长度:
- __initial_sp = LENGTH(ram);
+ __initial_sp = ORIGIN(ram) + LENGTH(ram);
6.2 段内容问题
Q1: HEX 文件为什么异常变大?
现象:HEX 明显变大,或包含大量 0 填充。
原因:
.bss/.heap/.stack缺少(NOLOAD),未初始化段被当作已初始化内容写入 HEX;.lpdata按上限填充,未使用的保持区被 0 填充并计入 HEX;- GNU 脚本把
.data、.bss、堆栈合并在同一输出段并指定AT(__text_end__),链接器为包括.bss和堆栈在内的整段分配 Flash 映像。
解决方案:
- 为
.bss/.heap/.stack添加(NOLOAD); - 按实际用量缩小
.lpdata的偏移。该偏移上限为 RAM 大小(本型号 64K):推到. = ORIGIN(ram) + 0x10000;时后续段没有空间,链接报section .data virtual address range overlaps with .stack、section '.heap' will not fit in region 'ram': overflowed by 1024 bytes等错误; - 按默认链接脚本拆分为独立输出段:
- .data : AT(__text_end__)
+ .data :
{
__data_start__ = .;
KEEP (*(.ramvector*))
...
__data_end__ = .;
- /* .bss 与堆栈配置仍在段内 */
- } > ram
+ } > ram AT > flash
+
+ .bss (NOLOAD): /* 纯 RAM,不占用 Flash 镜像空间 */
+ {
+ __bss_start__ = .;
+ *(COMMON*)
+ *(.bss*)
+ __bss_end__ = .;
+ } > ram
+
+ .heap (NOLOAD): /* 堆,不占用 Flash 镜像空间 */
+ {
+ __allot_end__ = .;
+ ...
+ } > ram
+
+ .stack (NOLOAD): /* 栈,锚定 RAM 顶部 */
+ {
+ ...
+ __initial_sp = ORIGIN(ram) + LENGTH(ram);
+ } > ram
Q2: C++ 全局构造函数为什么没有被调用?
现象:C++ 全局对象的构造函数没有执行。
原因:脚本未 KEEP 保留 .init_array,条目被死段优化丢弃;或 startup 未按 C++ 模式遍历构造函数表。
解决方案:
- 检查脚本是否保留
KEEP (*(SORT(.init_array.*)))与KEEP (*(.init_array*));缺失时.init_array可能被死段优化丢弃,无法落入__init_class_start~__init_class_end区间,startup 遍历空区间; - 确认使用的 startup 为 C++ 版本,能在
__init_class_start~__init_class_end之间遍历调用; - 通过
.map确认__init_class_start~__init_class_end区间非空。
Q3: 中断向量表位置为什么不对?
现象:程序无法启动,向量表或 _start 不在 Flash 起始处。
原因:向量表预留区被自定义定位侵入、KEEP 规则未覆盖实际产物文件名,或入口符号 _start 缺失。
解决方案:
.text前 512 字节保留给向量表,不要用. = 0x200;等自定义定位侵入;否则_start会静默错位,链接不报错;- 向量表 KEEP 规则覆盖实际产物文件名(
vector.o,兼容vector.obj/vector.c.obj/vector.asm.obj)。规则不命中时,向量表不再优先摆放,普通.text代码占据0x0,链接不报错但程序无法启动;向量表混入其他文件时按 3.1 .text(代码段) 改用.vector段; - 确认
_start存在且与ENTRY(_start)一致;缺失时仅报cannot find entry symbol _start警告,产物仍生成但入口地址未设置。
Q4: 代码中存在 rand() 调用或 64 位变量时,全局变量取值为什么不对?
现象:链接通过,但程序运行时所有带初值的全局变量取值都不对,整体错开 4 字节。
原因:代码中存在带初值的 64 位变量,或调用了 rand() 时,全局变量初值的存放地址都会按 8 字节对齐(rand() 的库内随机数状态变量为带初值的 64 位变量)。.text 的大小不是 8 字节的整数倍时,代码段与全局变量初值之间会多出 4 字节空隙;启动代码仍从代码段末尾开始拷贝初值,取到的位置比实际初值靠前,整段数据随之错位。
解决方案:修改链接脚本,将 .text 段末尾、__text_end__ 上方的 . = ALIGN(4); 改为 . = ALIGN(8);:
/*--------------------------------------------------*/
. = ALIGN(4);
*(.rdata*) /* global const variable space */
*(.rodata*) /* auto const variable space */
/*--------------------------------------------------*/
- . = ALIGN(4);
+ . = ALIGN(8);
__text_end__ = .;
/*record: global variable value of initialization,size .data before __data_end__ */
} > flash
.data 内有更大对齐要求的变量(如 __attribute__((aligned(16))))时,把 ALIGN(8) 改为与之一致(如 ALIGN(16))。
6.3 工具链兼容问题
Q1: GNU 工程的链接脚本可以直接使用吗?
不能直接使用。 脚本语法大体兼容,但 . 语义、装载地址、NOLOAD 与孤儿段的处理行为不同(见 第 4 章 与 GNU ld 的行为差异)。需要按以下三类问题逐项修改:
| 旧脚本写法 | 后果 |
|---|---|
.data 用 AT(__text_end__) 声明装载地址,且 .bss 与堆栈合并在同一输出段内 | 链接不报错,但 .bss / 堆栈也被分配 Flash 装载映像,HEX 明显膨胀 |
.data 未声明 AT > flash(装载地址 = 运行地址) | 默认脚本去掉 AT > flash 后,kf32ld 链接通过,但 kf32objcopy 生成 HEX 阶段报错:__data_start__, __data_end__, __bss_start__, and __bss_end__ must be defined in section .data;旧脚本的合并段形态下链接与 HEX 均能生成,但 HEX 中不含 .data 初值映像 |
__initial_sp = LENGTH(ram);、. = 长度; 等区域内赋值未加 ORIGIN | 链接阶段报 unable to move location counter backward for: .heap_stack / .data |
迁移检查清单:
- 区域内为
.与符号赋值一律显式加ORIGIN(...);预留空间改用相对推进. = . + 长度;(见 4.1 定位计数器.的语义); .data必须声明AT > flash,.bss、.heap、.stack拆分为独立输出段并标(NOLOAD)(见 第 3 章 输出段布局详解);- 脚本显式覆盖所有输入段(含调试段);未覆盖的输入段报错(见 4.2 孤儿段检查);
- 链接后核对
.map与 HEX,确认关键符号地址、栈顶地址与 HEX 大小符合预期。
6.4 链接阶段问题
Q1: 链接报输入段未被脚本放置?
现象:链接报错,指出某个输入段未被脚本放置,报错格式如下:
kf32ld: error: unplaced orphan section '<输入段名>' from '<来源文件>'
原因:脚本未显式覆盖该输入段,链接器将其视为孤儿段。
解决方案:按报错中的输入段名在脚本对应输出段内补充匹配规则,使脚本覆盖所有输入段(见 4.2 孤儿段检查)。