链接文件
参考链接
嵌入式IDE(1):IAR中ICF链接文件详解和实例分析_icf文件-CSDN博客
概述
链接文件主要用于划分存储区域,不同芯片的存储配置不同,数据的放置需要根据芯片的存储来配置,标准段为
| Section | Kind | Description | Memory Location | Example |
|---|---|---|---|---|
| .text | ro code | Holds the program code | ROM | void Function(void) |
| .rodata | const | Holds constant data | ROM | const uint32 variable = 0 |
| .textrw | inited | Holds _ramfunc declared program code | RAM | _ramfunc void Function(void) |
| .textrw_int | Holds initializers for the .textrw declared section | ROM | ||
| .intvec | ro code | Holds the reset vector table and exception vectors | ROM | |
| .noinit | uninit | Holds _no_init static and global variables. | RAM | __no_init uint32 variable |
| .bss | zero | Holds zero-initilialized static and global variables | RAM | uint32 variable = 0 |
| .data | inited | Holds static and global initialized variables | RAM | uint32 variable = 1 |
| .data_init | Holds initial values for data sections | ROM | ||
| CSTACK | uninit | Holds the stack | RAM | auto variables and parameters |
| HEAP | Holds the heap used for dynamically allocated data | RAM | standard libry function malloc |
而芯片存储区域可能如下

[!tip]
如果在代码中想要将某些变量或者某个函数放置到指定的位置,那么首先需要先确定变量类型,变量类型将决定这个变量可以放在哪些位置,比如普通的变量就要放在可读可写的位置(SRAM),而一些常量则要放在只读位置(.text)中,如果变量类型的操作权限和存储地址支持的操作权限一致,则可以放置,否则就放不了。
LD
LD 文件是 GNU 工具链(如 GCC)使用的链接器脚本,用于指定如何将程序的各个部分(如代码段、数据段等)放置到目标设备的内存中。LD 脚本的语法非常灵活,支持详细的内存区域分配、段定义以及符号管理。
基本结构
LD 链接脚本主要由以下几部分组成:
- 全局声明和变量:设置内存区域和段的分配。
- 段定义(SECTIONS):定义程序的各个段(如
.text,.data,.bss等)在内存中的位置。 - 符号管理:可以设置一些符号(如
__start、__end)的地址。
内存区域定义
使用 MEMORY 关键字来定义目标设备的内存区域。每个内存区域指定起始地址(ORIGIN)和长度(LENGTH)。
1 | MEMORY |
FLASH区域指定为可读 (r) 和可执行 (x)。RAM区域指定为可读 (r) 和可写 (w)。
段定义
SECTIONS 用来定义程序的各个段及其如何映射到内存中。每个段由其名称(如 .text、.data 等)定义,并可以指定要放置的内存区域。
1 | SECTIONS |
*()用于包含所有具有相应段名的输入部分。例如,*(.text)包含所有.text段的内容。> MEMORY_REGION用于将段放置到指定的内存区域。
特殊符号定义
可以在链接器脚本中定义特殊符号,来标记某些特定的位置(如段的开始和结束)。
1 | SECTIONS |
.是当前地址指针,它会随着段内容的放置而移动。
栈和堆定义
通常会为栈和堆分配特殊的内存区域。
1 | SECTIONS |
输入文件与输出文件
可以指定输入的目标文件和输出文件的格式。
1 | OUTPUT_FORMAT("elf32-littlearm") |
OUTPUT_FORMAT用于指定生成的输出文件格式。INPUT用于指定链接器输入的目标文件。
常用符号
LD 链接器脚本中常用的符号通常用于标记程序段的位置、内存地址或控制程序的执行。以下是常见的符号及其用途:
.
.表示当前的内存地址。在脚本中使用来指示段的起始或结束位置。
1 | __text_start = .; // 当前地址是 .text 段开始 |
__start_ 和 __end_
标记段的起始和结束地址。
1 | __start_text = .; // .text 段开始 |
_start
程序的入口地址。
1 | _start = 0x08000000; // 程序入口 |
__heap_start 和 __heap_end
标记堆内存的起始和结束地址。
1 | __heap_start = .; // 堆开始 |
__stack_start 和 __stack_end
标记栈的起始和结束地址。
1 | __stack_start = .; // 栈开始 |
ENTRY
指定程序的入口点。
1 | ENTRY(_start) |
_etext、_edata、_end
标记
1 | .text |
1 | .data |
和程序结束位置。
1 | _etext = .; // .text 段结束 |
_stack
指定栈的起始地址。
1 | _stack = 0x20020000; // 栈起始地址 |
_heap_size
设置堆的大小。
1 | _heap_size = 0x1000; // 堆大小为 4KB |
示例
1 | /* 定义内存区域 */ |
SCT
在Keil中,.sct 文件是一个链接控制文件,用于定义如何链接目标文件、库和其他资源,以及如何分配内存空间。它主要用于Keil MDK(Microcontroller Development Kit)中的ARM编译器(armcc)和链接器(armlink)。
基本结构
.sct 文件通常包含以下几个部分:
- 内存区域定义:定义程序使用的内存区域(例如 Flash、RAM)。
- 输入文件:列出输入文件(例如目标文件、库文件等)。
- 分配符号:定义全局变量或函数的符号分配。
- 段定义:定义如何将各个代码段(例如
.text、.data)映射到内存区域。
1 | // Keil .sct 文件示例 |
常见命令为:
define memory:定义内存区域及其大小和属性。place in:将段放置到指定的内存区域中。input:指定要链接的输入文件(目标文件或库文件)。define symbol:定义符号(如栈顶、堆限制等)。reserve:保留特定的内存区域,用于栈、堆等。
-
内存区域定义:
1
2
3
4
5define memory
{
ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128K
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 20K
}这部分定义了两个内存区域:
ROM和RAM。ROM区域具有读取和执行权限(rx),RAM区域具有读取和写入权限(rw)。同时,ORIGIN表示该区域的起始地址,LENGTH表示该区域的大小。 -
输入文件:
1
2
3
4
5
6
7
8
9entry Reset_Handler
include "startup.s"
select
{
input "main.o"
input "startup.o"
input "myLib.a"
}这部分定义了要链接的输入文件,
entry Reset_Handler表示程序的入口点,通常是复位处理程序。include和select用于引入文件或库。 -
符号定义:
1
2define symbol __StackTop = 0x20005000
define symbol __HeapLimit = 0x20006000这部分定义了栈顶(
__StackTop)和堆的边界(__HeapLimit)符号,通常用来标记栈和堆在内存中的位置。 -
段映射到内存:
1
2
3
4
5
6
7
8
9place in ROM
{
sections .text, .rodata
}
place in RAM
{
sections .data, .bss
}这里,
.text和.rodata(只读数据段)被放置在ROM中,.data和.bss(未初始化数据段)被放置在RAM中。 -
内存保留:
1
reserve RAM (0x20000000, 0x20005000)
这部分保留了
RAM区域中的特定内存区域,通常用于栈和堆。
常用符号
在Keil的.sct链接控制文件中,使用了一些字母和符号来表示不同的内存区域、段、属性等。这些符号通常用于描述内存布局和链接器如何处理各个段。
内存区域和段的标识符
- ROM:通常表示“只读存储器”(Read-Only Memory)。在嵌入式系统中,ROM区域通常存放程序代码和常量数据(如常量字符串、初始化数据等)。
- ROM 区域的权限:通常是
rx(读取和执行权限),即代码被加载到ROM中后会被执行。
- ROM 区域的权限:通常是
- RAM:表示“随机访问存储器”(Random Access Memory)。在嵌入式系统中,RAM区域用于存储程序的运行时数据、栈、堆等。
- RAM 区域的权限:通常是
rw(读取和写入权限),即数据可以被读取和修改。
- RAM 区域的权限:通常是
- ER_RO:指只读区域(Read-Only)。通常用于存放不需要修改的代码和数据(如程序代码、只读常量等)。在该区域中的数据是不可更改的。
- ER_RW:指读写区域(Read/Write)。用于存放程序中需要修改的数据(如全局变量、静态变量等)。
- ER_RW_1:可能是特定应用中的第二个读写区域,特别用于某些特殊用途的数据(如未初始化的全局变量)。
- PATCH_TABLE:指补丁表区域,通常用于存放运行时需要的补丁或配置数据。
- HOST_DATA:指主机数据区域,通常用于存放一些动态链接的库文件中的数据(如
.lib文件)。
属性标识符
+FIRST:表示该文件或符号被放置在指定段的开头。这个标识符通常用于指定入口点(如RESET处理程序)所在的位置,确保它是该段的第一个元素。+RO:表示只读数据。用于指定该段中的数据是只读的,例如代码段或常量数据段。+ZI:表示“零初始化”数据(Zero Initialized)。这些变量在程序启动时会被初始化为零。它们通常存放在.bss段(未初始化数据段)。+RW:表示“读写”数据。这些变量会在程序运行时被读写,通常存放在.data段中。NOINIT:表示“不初始化”的数据,通常是未初始化的数据,如某些保留内存。它们不会被链接器初始化,因此在启动时会保持未定义的值。UNINIT:表示未初始化数据的区域,这些数据不会在程序启动时进行初始化。通常与NOINIT类似,但也可能指定特定的内存区域用于存放这类数据。
其他关键字和函数
ImageLength:这是一个用于计算内存区域大小的宏或函数。它返回指定段的字节长度。比如,ImageLength(PATCH_TABLE)会返回PATCH_TABLE区域的大小。ScatterAssert:这是一个用于断言内存布局是否符合要求的命令。如果通过ImageLength计算得出的内存区域总大小超过了某个限制,ScatterAssert会抛出一个错误。这有助于确保程序的内存使用不会超出目标设备的内存限制。
常见的符号解释
*:表示“匹配所有”。例如,*(+RO)表示匹配所有只读数据,*(ram_code)表示匹配所有名为ram_code的段或符号。+:表示包含特定属性的数据或段。例如,+RW表示数据段包含读写权限。
文件参数
在 Keil .sct 链接控制文件中,文件后面的括号部分(如 startup.o (RESET, +FIRST) 或 input "main.o" (symbol))用于指定额外的属性、符号和链接器行为。这些括号内的内容通常有特定的含义,用来控制文件、段或符号在内存中的位置、优先级或其他特性。
符号标识符(Symbols)
这类参数指定特定的符号在文件中的处理方式。最常见的符号标识符是 RESET,它用于标记程序的复位入口点。
-
RESET:指定该文件包含复位处理程序,并将其作为程序启动时的入口点。这个符号通常指向复位处理代码的起始位置。1
startup.o (RESET)
这表示
startup.o文件包含复位处理程序,并且该符号会作为程序的入口点。
段位置控制符(Position Control)
这些参数用于控制段在内存中的位置,影响文件的排列顺序,通常用于控制文件或段的顺序。
-
+FIRST:指示将该文件或符号放在当前段的最前面,通常用于复位程序或初始化代码。1
startup.o (RESET, +FIRST)
这表示将
startup.o文件中的RESET符号放置在段的开头位置。 -
+LAST:指示将该文件或符号放在当前段的末尾位置。这个符号较少使用,但有时可以用来将特定文件或符号放置到段的末尾。
段类型属性(Section Attributes)
这些属性指定段的访问权限或其他属性。常见的段属性有:
-
+RO:表示该段是只读的,通常用于放置代码或只读数据(如.text,.rodata)。1
main.o (+RO)
这表示将
main.o中的段标记为只读,并放置在只读内存区域。 -
+RW:表示该段是可读写的,通常用于放置变量、堆栈等数据。1
main.o (+RW)
这表示将
main.o中的段标记为读写数据,并放置在读写内存区域。 -
+ZI:表示零初始化数据,这些数据会在程序启动时被初始化为零,通常用于.bss段。1
data.o (+ZI)
这表示将
data.o中的段标记为零初始化数据。
自定义符号与特性
-
NOINIT:指示该段的数据不会被初始化,通常用于.bss段之外的特殊区域。1
data.o (NOINIT)
这表示
data.o中的数据不会进行初始化。 -
UNINIT:类似于NOINIT,表示数据未初始化。1
main.o (UNINIT)
这表示
main.o中的数据段未初始化。
内存区域标识符(Memory Region Identifiers)
这些参数用于将符号或段与特定的内存区域关联,例如 ROM、RAM 等。
-
ROM或RAM:指示文件或符号属于某个内存区域。通常在.sct文件的定义部分会定义这些内存区域。1
main.o (ROM)
这表示将
main.o中的段放置在ROM区域。
示例
以以下段落为例:
1 | ER_RO 0x10002000 |
- ER_RO:表示一个只读区域(Read-Only Region),将数据放置在该区域。
- 0x10002000:该区域的起始地址。
- startup_fr1010.o (RESET, +FIRST):将
startup_fr1010.o文件中的RESET启动代码放置在该段的开头,并指定它为程序的第一个代码段。 *(+RO):将所有的只读数据(如.rodata段)放置在这个段中。
[!important]
文件的排列顺序并不是简单地“文件顺序”,而是由 段(sections)和 内存区域(memory regions)决定的。链接器会根据每个文件中的段类型来决定它们在内存中的布局。
- 文件内部的段排序:每个文件(如
main.o、startup.o和myLib.a)都会包含若干段(如.text、.data、.rodata、.bss等)。链接器会根据这些段的类型,将每个段放置在合适的内存区域(如 ROM、RAM 等)中。- 文件的顺序:如果两个文件包含相同类型的段(如
.text或.data),则它们在内存中的顺序==会遵循.sct文件中input或select语句中文件的顺序==。这意味着,链接器会按照文件在select块中的顺序处理这些文件,但文件内部的段仍然会按照它们的类型和目标区域被放置。- 静态库
myLib.a的处理:静态库(如myLib.a)包含了多个目标文件。链接器会按照静态库中的文件顺序,将其中的段与其他输入文件的段合并,通常按照静态库中的顺序依次处理。- 段的合并:如果多个文件的相同类型段(如多个文件的
.text段)被加载到同一个内存区域(如 ROM 区域),则这些段的内容会被合并在一起。在这种情况下,文件的顺序会影响段的最终顺序。
ICF
ICF(IAR Configurable File)是IAR专有的配置文件格式,主要用来描述内存布局、段的分配、堆栈的定义等内容。ICF文件通过对内存的精确控制来优化应用程序的内存使用。
基本结构
ICF文件通常由几个主要部分组成:
- 内存定义(Memory)
- 区域划分(Region)
- 数据分配(Placement)
内存定义
可以使用define memory声明芯片的物理内存范围。
1 | define memory <名称> with size = <大小>; |
示例:
1 | define memory mem with size = 4G; // 定义4GB地址空间(覆盖所有可能地址) |
可以使用define block定义特殊内存块(如堆、栈)。
1 | define block <名称> with alignment = <对齐>, size = <大小> { <段或符号> }; |
示例:
1 | define block CSTACK with alignment = 8, size = 0x1000 { }; // 栈(8字节对齐,4KB) |
区域划分
可以使用define region定义一个具体的物理存储区域(如 FLASH、RAM)。
1 | define region <名称> = mem:[from <起始地址> to <结束地址>] [attrs = <属性>]; |
- 属性(attrs):
readonly:只读(代码、常量)readwrite:可读写(变量)execute:可执行(代码)
1 | define region ROM_region = mem:[from 0x10000 to 0x3FFFF] attrs = readonly; |
说明:
mem:表示在define memory中定义的空间;from和to指明区域的起止地址;- 也可使用
size:define region RAM = mem:[from 0x20000000 size 0x4000];
在定义内存之前,可以使用define symbol定义一个符号常量(仅链接器使用)来使代码通俗易懂。
1 | define symbol Start = 0x400; |
使用 define exported symbol定义并导出符号(可在 C 中 extern 使用)。
1 | define exported symbol __ICFEDIT_region_RAM_start__ = 0x20000000; |
导出的符号常用于:
- 启动文件;
- 堆栈初始化;
- 应用与 bootloader 通信。
示例
1 | define region FLASH = mem:[from Start size Size]; |
数据分配
使用place可以将代码/数据分配到指定区域。
1 | place at address mem:<绝对地址> { <段或块> }; // 固定地址 |
-
标准段(section)(由编译器自动生成):
段名 用途 典型内容 .text代码段(可执行指令) 函数代码、中断向量表 .rodata只读数据(常量) const全局变量、字符串常量.data已初始化的全局/静态变量(需从Flash加载到RAM) 非 const的初始化变量.bss未初始化的全局/静态变量(RAM清零) static int x;(未显式初始化).noinit不初始化的变量(RAM保留原始值) __no_init修饰的变量.stack栈空间(由链接器分配) 局部变量、函数调用帧 .heap堆空间(动态内存) malloc()分配的变量 -
自定义段(如
.my_section)
常见语法形式:
| 语法 | 说明 |
|---|---|
place in <region> |
将段放入指定区域(位置由链接器决定) |
place at start of <region> |
放在区域起始处 |
place at end of <region> |
放在区域末尾 |
place at address mem:<addr> |
放在绝对地址 |
1 | place in ROM_region { readonly }; // 所有只读数据放入ROM |
段类型与匹配方式为:
| 类型 | 说明 |
|---|---|
readonly |
只读段(代码、常量) |
readwrite |
可读写段(全局变量) |
section .name |
指定具体段名 |
section .ANY (+RO, +RW, +ZI) |
按属性匹配所有符合条件的段 |
UNINIT / noinit |
未初始化的段(不会在启动时清零或复制) |
示例:
1 | readwrite section .bss, section .noinit; |
有时候避免不了初始化控制:
1 | initialize by copy { readwrite }; // 初始化变量(从Flash拷贝到RAM) |
示例:
1 | /* 将代码段放入 Flash */ |
示例
1 | /*###ICF### Section handled by ICF editor, don't touch! ****/ |
常见错误
| 错误信息 | 原因 | 解决办法 |
|---|---|---|
section placement failed |
区域太小或重叠 | 调整 region 大小或顺序 |
undefined symbol |
未定义导出符号 | 添加 define exported symbol |
| 数据未初始化 | 段属性错误 | 检查是否应为 readwrite 段 |
| 拷贝错误 | .data 的 load 区未定义 |
确认 Flash 与 RAM 之间的复制关系 |