跳到主要内容

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。

原因:. = <表达式> 按绝对地址解释。地址错误会使定位计数器回退,其后内容被放到错误位置。常见写法:

  1. RAM 内的裸常数偏移(. = 0x8000;)被当成绝对地址 0x8000;
  2. 经符号间接赋值(s = 0x0; . = s;)同样赋成绝对地址;
  3. .text 段首的 . = 0x0000; 在 Flash 起始地址非 0 的型号上,把向量表定位到区域之外。

解决方案:

  1. 移除 .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 */
  1. 段内偏移改用 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。

解决方案:按以下顺序检查:

  1. 定位语句位置是否正确:Flash 定位在 .text 内、RAM 定位在 .data 内,且在对应通配规则之前;
  2. 区域内的 . 赋值是否加了 ORIGIN;
  3. 通过 .map 搜索符号名,确认实际落点。

段名不一致或匹配规则缺少 * 时,输入段会被判为孤儿段并导致链接失败(见 6.4 Q1 链接报输入段未被脚本放置)。

Q3: RAM 占用超限如何压缩?​

现象:链接报 ram 区域溢出,或 RAM 占用接近上限。

原因:指定地址段产生 0 填充、.lpdata 保留区过大,或堆、栈预留过大。

解决方案:

  1. 确认 .data 区中指定地址段是否因不连续摆放产生了 0 填充(见 5.2 定义变量与函数到指定 RAM 地址);
  2. .lpdata 保持区如未接近 32K,按实际用量缩小;
  3. 减小堆栈大小(见 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 填充。

原因:

  1. .bss / .heap / .stack 缺少 (NOLOAD),未初始化段被当作已初始化内容写入 HEX;
  2. .lpdata 按上限填充,未使用的保持区被 0 填充并计入 HEX;
  3. GNU 脚本把 .data、.bss、堆栈合并在同一输出段并指定 AT(__text_end__),链接器为包括 .bss 和堆栈在内的整段分配 Flash 映像。

解决方案:

  1. 为 .bss / .heap / .stack 添加 (NOLOAD);
  2. 按实际用量缩小 .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 等错误;
  3. 按默认链接脚本拆分为独立输出段:
- .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++ 模式遍历构造函数表。

解决方案:

  1. 检查脚本是否保留 KEEP (*(SORT(.init_array.*))) 与 KEEP (*(.init_array*));缺失时 .init_array 可能被死段优化丢弃,无法落入 __init_class_start ~ __init_class_end 区间,startup 遍历空区间;
  2. 确认使用的 startup 为 C++ 版本,能在 __init_class_start ~ __init_class_end 之间遍历调用;
  3. 通过 .map 确认 __init_class_start ~ __init_class_end 区间非空。

Q3: 中断向量表位置为什么不对?​

现象:程序无法启动,向量表或 _start 不在 Flash 起始处。

原因:向量表预留区被自定义定位侵入、KEEP 规则未覆盖实际产物文件名,或入口符号 _start 缺失。

解决方案:

  1. .text 前 512 字节保留给向量表,不要用 . = 0x200; 等自定义定位侵入;否则 _start 会静默错位,链接不报错;
  2. 向量表 KEEP 规则覆盖实际产物文件名(vector.o,兼容 vector.obj / vector.c.obj / vector.asm.obj)。规则不命中时,向量表不再优先摆放,普通 .text 代码占据 0x0,链接不报错但程序无法启动;向量表混入其他文件时按 3.1 .text(代码段) 改用 .vector 段;
  3. 确认 _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

迁移检查清单:

  1. 区域内为 . 与符号赋值一律显式加 ORIGIN(...);预留空间改用相对推进 . = . + 长度;(见 4.1 定位计数器 . 的语义);
  2. .data 必须声明 AT > flash,.bss、.heap、.stack 拆分为独立输出段并标 (NOLOAD)(见 第 3 章 输出段布局详解);
  3. 脚本显式覆盖所有输入段(含调试段);未覆盖的输入段报错(见 4.2 孤儿段检查);
  4. 链接后核对 .map 与 HEX,确认关键符号地址、栈顶地址与 HEX 大小符合预期。

6.4 链接阶段问题​

Q1: 链接报输入段未被脚本放置?​

现象:链接报错,指出某个输入段未被脚本放置,报错格式如下:

kf32ld: error: unplaced orphan section '<输入段名>' from '<来源文件>'

原因:脚本未显式覆盖该输入段,链接器将其视为孤儿段。

解决方案:按报错中的输入段名在脚本对应输出段内补充匹配规则,使脚本覆盖所有输入段(见 4.2 孤儿段检查)。