在 C 语言开发中,链接脚本(Linker Script)用于控制可执行文件或库的内存布局,尤其在嵌入式系统和底层开发中非常重要。
一、链接脚本的主要作用
1. 内存布局控制
指定代码段(.text)、数据段(.data)、BSS段(.bss)的存放位置
定义内存区域(RAM、ROM、Flash等)的起始地址和大小
2. 符号定义与管理
定义全局符号(如堆栈起始地址)
控制符号的可见性和导出
3. 特殊需求处理
启动代码的特定放置
中断向量表的精确定位
自定义段的创建和布局
二、基本语法结构
一个示例的链接配置文件如下:
ld
换行复制代码
1/* 最简单的链接脚本示例 */
2MEMORY
3{
4 ROM (rx) : ORIGIN = 0x08000000, LENGTH = 512K
5 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
6}
7
8SECTIONS
9{
10 /* 代码段 */
11 .text :
12 {
13 *(.vectors) /* 中断向量表优先 */
14 *(.text*) /* 所有代码 */
15 *(.rodata*) /* 只读数据 */
16 } > ROM
17
18 /* 初始化数据 */
19 .data : AT(ADDR(.text) + SIZEOF(.text))
20 {
21 _sdata = .; /* 数据段起始地址 */
22 *(.data*)
23 _edata = .; /* 数据段结束地址 */
24 } > RAM
25
26 /* 未初始化数据 */
27 .bss :
28 {
29 _sbss = .;
30 *(.bss*)
31 *(COMMON)
32 _ebss = .;
33 } > RAM
34
35 /* 堆栈定义 */
36 _estack = ORIGIN(RAM) + LENGTH(RAM);
37}1. 注释
ld
换行复制代码
1/* 多行注释 */
2// 单行注释(某些链接器支持)2. 命令格式
ld
换行复制代码
1命令名 参数 {
2 子命令;
3 符号 = 表达式;
4}二、核心关键字
1. MEMORY - 定义内存区域
ld
换行复制代码
1MEMORY
2{
3 名称 (属性) : ORIGIN = 起始地址, LENGTH = 长度
4}属性说明:
r- 可读w- 可写x- 可执行a- 可分配i- 初始化l- 与i相同!- 反转属性
示例:
ld
换行复制代码
1MEMORY
2{
3 ROM (rx) : ORIGIN = 0x0000, LENGTH = 256K
4 RAM (rwx) : ORIGIN = 0x4000, LENGTH = 64K
5 EEPROM (rw) : ORIGIN = 0xF000, LENGTH = 4K
6}2. SECTIONS - 定义输出段
ld
换行复制代码
1SECTIONS
2{
3 输出段名 [地址] : [AT(加载地址)]
4 {
5 内容描述
6 } [>区域] [AT>区域]
7}3. 输出段内容描述符
输入段选择器
ld
换行复制代码
1*(.text) /* 所有文件的.text段 */
2file.o(.data) /* 特定文件的.data段 */
3*(.text.*) /* 匹配所有.text.开头的段 */特殊命令
ld
换行复制代码
1KEEP(*(.init)) /* 强制保留,即使未引用 */
2PROVIDE(symbol = .) /* 定义符号,不重复定义 */
3SORT(CONSTRUCTORS) /* 按名称排序 */三、位置计数器 (.)
基本用法
ld
换行复制代码
1. = 0x1000; /* 设置当前位置 */
2. = ALIGN(4); /* 4字节对齐 */
3. += 0x100; /* 当前位置向后移动256字节 */常用表达式
ld
换行复制代码
1. = DEFINED(symbol) ? symbol : 0x1000; /* 条件设置 */
2. = ( . + 3 ) & ~3; /* 4字节对齐(手动) */四、符号定义
1. 赋值操作符
ld
换行复制代码
1symbol = expression; /* 简单赋值 */
2symbol .= expression; /* 相对赋值 */
3symbol += expression; /* 加法赋值 */
4symbol -= expression; /* 减法赋值 */
5symbol *= expression; /* 乘法赋值 */
6symbol /= expression; /* 除法赋值 */
7symbol <<= expression; /* 左移赋值 */
8symbol >>= expression; /* 右移赋值 */
9symbol |= expression; /* 或赋值 */
10symbol &= expression; /* 与赋值 */2. 特殊符号
ld
换行复制代码
1/* 预定义符号 */
2__executable_start /* 程序起始地址 */
3etext, _etext, etext /* .text段结束地址 */
4edata, _edata /* .data段结束地址 */
5end, _end /* BSS段结束地址 */
6
7/* 自定义符号 */
8_stack_start = .; /* 栈起始地址 */
9_stack_size = 0x400; /* 栈大小 */
10_heap_end = ORIGIN(RAM) + LENGTH(RAM); /* 堆结束地址 */五、函数和运算符
1. 地址相关函数
ld
换行复制代码
1ADDR(section) /* 获取段的运行时地址 */
2LOADADDR(section) /* 获取段的加载地址 */
3SIZEOF(section) /* 获取段的大小 */
4ALIGN(exp) /* 对齐到exp的倍数 */
5ALIGNOF(section) /* 获取段的对齐要求 */2. 符号相关函数
ld
换行复制代码
1DEFINED(symbol) /* 检查符号是否已定义 */
2MAX(exp1, exp2) /* 取最大值 */
3MIN(exp1, exp2) /* 取最小值 */3. 运算符优先级
1. () /* 括号 */ 2. ! ~ - + /* 一元运算符 */ 3. * / % /* 乘除 */ 4. + - /* 加减 */ 5. >> << /* 移位 */ 6. == != > < >= <= /* 关系运算 */ 7. & | ^ /* 位运算 */ 8. && || /* 逻辑运算 */ 9. ?: /* 条件运算 */ 10. = += -= *= /= /* 赋值 */
六、段属性控制
1. 段类型
ld
换行复制代码
1.text : { *(.text) } TYPE=PT_LOAD /* 可加载段 */
2.rodata : { *(.rodata) } TYPE=PT_LOAD2. 段标志
ld
换行复制代码
1.data : { *(.data) } :data /* 使用内存区域属性 */七、条件语句
IF-ELSE 语句
ld
换行复制代码
1SECTIONS
2{
3 .text : {
4 *(.text)
5 . = ALIGN(4);
6 IF (DEFINED(__USE_SOFT_FLOAT__)) {
7 *(.text.softfloat)
8 } ELSE {
9 *(.text.hardfloat)
10 }
11 } > ROM
12}八、包含指令
INCLUDE 指令
ld
换行复制代码
1/* 包含其他链接脚本 */
2INCLUDE memory.ld
3INCLUDE sections.ld
4
5/* 条件包含 */
6IF (MCU == "STM32F103")
7 INCLUDE stm32f103.ld
8ELSE
9 INCLUDE generic.ld
10ENDIF九、输出格式控制
OUTPUT_FORMAT
ld
换行复制代码
1OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
2OUTPUT_ARCH(arm)
3ENTRY(Reset_Handler) /* 指定入口点 */十、完整示例分解
ld
换行复制代码
1/* 1. 输出格式设置 */
2OUTPUT_FORMAT("elf32-littlearm")
3OUTPUT_ARCH(arm)
4ENTRY(Reset_Handler)
5
6/* 2. 内存区域定义 */
7MEMORY
8{
9 FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
10 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
11 CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
12}
13
14/* 3. 段定义 */
15SECTIONS
16{
17 /* 中断向量表 - 必须放在起始位置 */
18 .isr_vector :
19 {
20 . = ALIGN(4);
21 KEEP(*(.isr_vector))
22 . = ALIGN(4);
23 } > FLASH
24
25 /* 代码段 */
26 .text :
27 {
28 *(.text) /* .text段(代码) */
29 *(.text.*) /* .text.*段 */
30 *(.glue_7) /* ARM/Thumb胶水代码 */
31 *(.glue_7t) /* Thumb/ARM胶水代码 */
32 *(.eh_frame) /* 异常处理框架 */
33
34 /* 构造函数 */
35 KEEP(*(.init))
36 KEEP(*(.fini))
37
38 . = ALIGN(4);
39 _etext = .; /* 代码段结束地址 */
40 } > FLASH
41
42 /* 只读数据 */
43 .rodata :
44 {
45 *(.rodata)
46 *(.rodata.*)
47 . = ALIGN(4);
48 } > FLASH
49
50 /* ARM特定段 */
51 .ARM.extab :
52 {
53 *(.ARM.extab*)
54 *(.gnu.linkonce.armextab.*)
55 } > FLASH
56
57 .ARM.exidx :
58 {
59 __exidx_start = .;
60 *(.ARM.exidx*)
61 *(.gnu.linkonce.armexidx.*)
62 __exidx_end = .;
63 } > FLASH
64
65 /* 预初始化数组 */
66 .preinit_array :
67 {
68 PROVIDE_HIDDEN(__preinit_array_start = .);
69 KEEP(*(.preinit_array*))
70 PROVIDE_HIDDEN(__preinit_array_end = .);
71 } > FLASH
72
73 /* 初始化数组 */
74 .init_array :
75 {
76 PROVIDE_HIDDEN(__init_array_start = .);
77 KEEP(*(SORT(.init_array.*)))
78 KEEP(*(.init_array*))
79 PROVIDE_HIDDEN(__init_array_end = .);
80 } > FLASH
81
82 /* 结束数组 */
83 .fini_array :
84 {
85 PROVIDE_HIDDEN(__fini_array_start = .);
86 KEEP(*(SORT(.fini_array.*)))
87 KEEP(*(.fini_array*))
88 PROVIDE_HIDDEN(__fini_array_end = .);
89 } > FLASH
90
91 /* 初始化数据(从Flash加载到RAM) */
92 _sidata = LOADADDR(.data);
93
94 .data :
95 {
96 . = ALIGN(4);
97 _sdata = .;
98
99 *(.data)
100 *(.data.*)
101
102 . = ALIGN(4);
103 _edata = .;
104 } > RAM AT > FLASH
105
106 /* 未初始化数据 */
107 .bss :
108 {
109 . = ALIGN(4);
110 _sbss = .;
111
112 *(.bss)
113 *(.bss.*)
114 *(COMMON)
115
116 . = ALIGN(4);
117 _ebss = .;
118 } > RAM
119
120 /* 用户堆栈 */
121 ._user_heap_stack :
122 {
123 . = ALIGN(8);
124 PROVIDE(end = .);
125 PROVIDE(_end = .);
126 . = . + _Min_Heap_Size;
127 . = . + _Min_Stack_Size;
128 . = ALIGN(8);
129 } > RAM
130
131 /* 移除调试信息 */
132 /DISCARD/ :
133 {
134 libc.a(*)
135 libm.a(*)
136 libgcc.a(*)
137 }
138
139 /* 自定义段 */
140 .my_section :
141 {
142 __my_section_start = .;
143 KEEP(*(.my_section))
144 __my_section_end = .;
145 } > CCMRAM
146
147 /* 绝对符号定义 */
148 .abs_section :
149 {
150 __abs_start = ABSOLUTE(.);
151 *(.abs_section)
152 __abs_end = ABSOLUTE(.);
153 } > FLASH
154}
155
156/* 4. 全局符号定义 */
157PROVIDE(_estack = ORIGIN(RAM) + LENGTH(RAM));
158PROVIDE(_Min_Heap_Size = 0x200);
159PROVIDE(_Min_Stack_Size = 0x400);十一、调试技巧
1. 查看链接映射
bash
换行复制代码
1arm-none-eabi-ld -T script.ld -Map=output.map ...
//riscv-rtos
riscv32-unknown-elf-objdump -h RTOSDemo.elf | less
or
riscv32-unknown-elf-nm -n RTOSDemo.elf | less2. 常见错误处理
ld
换行复制代码
1/* 确保关键段不被优化 */
2KEEP(*(.vector_table))
3KEEP(*(.isr_vector))
4
5/* 处理未使用段 */
6/DISCARD/ : { *(.note.GNU-stack) }3. 版本兼容性
ld
换行复制代码
1/* GNU LD版本检查 */
2__LD_VERSION__ = 0x23800; /* 2.56.0 */
3
4/* 条件编译 */
5VERSION {
6 CURRENT {
7 local: *;
8 };
9}