1. 引言与概述
1.1 文档说明
本文档旨在详细介绍 ChipON KF32 系列芯片在 LLVM/LLD 工具链体系下链接脚本(Linker Script,.ld 文件)的完整使用方法,包括内存区域配置(MEMORY)、段布局控制(SECTIONS)、各输出段的设计原理、与 GNU ld 的行为差异,以及常量定位、变量定位、低功耗数据保持、堆栈调整等常规使用方法,并附常见问题排查方案。
无论是使用默认脚本直接编译,还是需要为 Bootloader 双应用、指定地址常量/函数、低功耗保持变量等特殊应用调整脚本布局的用户,均可通过本文档掌握链接脚本的原理与修改方法。
1.2 链接脚本的作用
编译的最后一步由链接脚本控制:脚本定义的段规则决定链接器从各目标文件中读取哪些输入段、如何合并成输出段、最终生成什么内存布局的目标文件。默认脚本已覆盖常规应用的布局需求,通过调整脚本可以实现特殊的应用程序布局,例如:
- Bootloader 双应用程序:为引导程序与应用程序划分独立的 Flash 区域;
- 指定函数/常量的固定地址:将关键代码或数据放置到确定的 Flash/RAM 地址;
- 差异化应用的 RAM 变量定位:将变量放置到指定 RAM 地址,或利用低功耗保持区在唤醒后保留数据。
1.3 适用范围
本文档适用于所有使用 ChipON KF32 LLVM 工具链进行开发的用户。文中示例均以常用型号 KF32A158MQVSF(512K PFlash / 64K RAM / 32K DFlash) 为例;不同型号之间主要是资源大小与配置区域地址的差别,段规则部分完全一致,其他型号仅需按产品规格书调整 MEMORY 中的区域起始地址与长度(见 第 2 章 内存区域与布局控制)。
KF32 输出文件格式为 elf32。RAM 设计中部分地址空间为低功耗唤醒不丢失区域(本型号为前 32K,具体以产品规格书为准),可通过变型 startup(控制拷贝循环跳过 .lpdata 段)配合脚本实现(见 5.3 低功耗保持数据应用)。
1.4 术语与定义
- 链接脚本(Linker Script):文本格式的链接规则描述文件,控制输入段到输出段的合并方式与最终内存布局,ChipON 工程中为
.ld文件。 - 输入段(Input Section):编译产生的目标文件(
.o)中的段,如.text、.data、.rodata。 - 输出段(Output Section):链接脚本 SECTIONS 中定义的段,由一个或多个输入段合并而成,如脚本中的
.text、.data、.bss。 - VMA(Virtual Memory Address):虚拟地址,即程序运行时段所在的地址,通过
> region指定。 - LMA(Load Memory Address):装载地址,即段内容在烧录映像(HEX/BIN)中存放的地址,通过
AT > region指定。 - 定位计数器(Location Counter,
.):脚本中的特殊符号,表示当前布局位置。在=运算中出现的.即为定位计数器。 - 孤儿段(Orphan Section):脚本中未显式列出的输入段;使用含
SECTIONS的脚本时按错误处理。 - 死段优化(--gc-sections):链接器默认开启的优化,未被引用的代码/数据不分配空间;
KEEP修饰的输入段可豁免。 - NOLOAD:输出段修饰符,标记该段不装载(ELF 中为 NOBITS 类型),不占用 HEX 内容。
1.5 默认段归属
默认情况下,各类内容的段归属如下:
| 内容 | 段 | 空间 |
|---|---|---|
| 代码函数 | .text | flash |
| 全局初始化常量 / 局部常量 | .rdata / .rodata | flash |
| 全局已初始化变量 | .data | ram(初值装载在 flash .text 之后) |
| 全局未初始化变量 | .bss 或 COMMON | ram |
| RAM 中的函数/常量 | .indata / .inrdata / .inrodata | ram |
| C++ 全局构造/析构函数表 | .preinit_array / .init_array / .fini_array | flash |
1.6 脚本必需符号
以下符号为工具实现的组成部分,构成脚本与启动代码(startup)之间的接口,修改脚本时必须保留:
| 符号 | 含义 | 使用者 |
|---|---|---|
__text_end__ | 代码段结束 | startup 拷贝源 |
__data_start__ / __data_end__ | 已初始化数据段的运行地址范围 | startup 拷贝目标 |
__bss_start__ / __bss_end__ | 未初始化数据段的清零范围 | startup 清零循环 |
__allot_end__ | 堆区起始 | 堆初始化 |
__initial_sp | 初始栈顶指针(RAM 顶地址) | 向量表 / SP 初始化 |
__lpdata_start__ / __lpdata_end__ | 低功耗保持区范围 | 变型 startup 跳过拷贝 |
__vec_end__ | 中断向量表结束 | 布局标记 |
__init_class_start / __init_class_end | C++ 全局构造函数表范围 | startup C++ 模式逐个调用构造函数 |
- startup 代码依赖上述符号完成数据拷贝与清零,删除或改名将导致程序无法运行。
__data_start__必须位于 ram 中.data的起始处。- 若需保留 RAM 前 N 字节,将 MEMORY 中
ram的ORIGIN增加 N、LENGTH减少 N(见 第 2 章 内存区域与布局控制)。
1.7 程序映像的构成
程序生成时,ram 段的全局初始化部分通过 AT > flash 装载到 flash 中 .text 之后,即代码与全局初值共同构成完整的程序内容(可在 HEX 文件中看到)。startup 函数在 main 之前以 __text_end__ 为源地址、__data_start__ ~ __data_end__ 为目标地址完成数据拷贝,并清零 .bss 区。
.bss、.heap 与 .stack 段标记为 NOLOAD,属 NOBITS 段,不占用 HEX 内容,运行时由 startup 清零或留待使用,不会在 HEX 中写入无意义的 0 填充。