网站Logo 网友马大帅的博客

ld链接文件的语法与配置

ughostx
5
2026-01-29

在 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_LOAD

2. 段标志

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 | less

2. 常见错误处理

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}

动物装饰