在编写嵌入式C/C++程序时,我们会有多个源文件,编译后生成目标文件(.o文件),目标文件按 节(Section) 组织,每个节存储不同类型的数据:
- 代码(.text):函数体的二进制代码。
- 只读数据(.rodata):常量字符串、全局常量等。
- 已初始化的全局变量(.data):在定义时已经赋初值的全局变量或静态变量。
- 未初始化的全局变量(.bss):在定义时未赋初值的全局变量或静态变量,启动时会被清零。
- 其他特殊段:如中断向量表、构造/析构函数表等。
链接器脚本是链接器的配置文件,用于指导链接器如何将多个目标文件(.o文件)合并成最终的可执行文件。
下面将会以基于libopencm3的Cortex-M微控制器的通用链接器脚本libopencm3/lib/cortex-m-generic.ld为例,详细介绍链接脚本的内容。完整代码放在最后。
链接过程详解:
- 输入阶段
- 链接器读取:
- 多个目标文件(
.o)- 库文件(
.a)- 链接器脚本(通过
-T指定)。- 解析与合并
- 段合并:
根据脚本的SECTIONS规则,合并所有目标文件的同类段(如合并所有.text段)。- 符号解析:
检查未定义符号(如printf),从库中补充。- 地址分配
- 定位计数器:
动态标记当前地址,例如. = 0x10000;表示后续段从该地址开始存放。- 对齐处理:
ALIGN(4)确保段起始地址满足 4 字节对齐。- 生成输出
- 输出可执行文件(ELF 格式),包含:
- 合并后的段(如
.text、.data)- 符号表(解决所有符号地址)
- 重定位信息(需加载器处理)。
- 编译生成的可执行文件(二进制镜像)只包含ROM(Flash)中的内容,而RAM在烧录时是"空白"的
1. 链接器脚本核心结构
1.1 内存区域定义
cortex-m-generic.ld 中未提供内存区域定义,它要求用户在自己的连接器脚本中必须定义rom(代码存储器,如Flash)和ram(数据存储器,如SRAM)的区域:
/*
* This is a generic linker script for Cortex-M targets using libopencm3.
*
* Memory regions MUST be defined in the ld script which includes this one!
* Example:
MEMORY
{
rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
}
INCLUDE cortex-m-generic.ld /* 用户自定义连接器脚本中,需要包含通用模板 */
*/
1.2 入口点与强制符号
ENTRY(reset_handler):程序入口点为reset_handler
- reset_handler(复位中断服务函数):CPU上电后首先从中断向量表获取该函数地址,进入该函数以执行硬件初始化(如栈指针设置、时钟配置)、数据段加载(.data/.bss),最后跳转到用户main函数。
- ENTRY关键字定义程序入口点——生成的可执行文件从此处开始执行
EXTERN(vector_table):强制包含向量表,确保启动代码被链接。
- EXTERN关键字强制表示包含指定符号到输出文件中,避免该符号被优化掉。
/* Define the entry point of the output file. */
ENTRY(reset_handler)
/* Enforce emmition of the vector table. */
EXTERN (vector_table)
1.3 段(SECTION)组织
- 关键字
SECTIONS { ... }
- 作用:定义输出文件的内存布局
- 结构:包含多个输出段(最终生成的可执行文件或二进制文件中的逻辑分段,包含了代码、数据、堆栈信息等内容,按照链接器脚本的指示被放置到特定的内存地址中)的定义
- 输出段定义
.text : { ... } >rom
- 语法:.段名 : { 内容 } > 内存区域 (段名前面必须有点)
- 作用:将指定的输入段组合到输出段中
- 定义内部的关键元素:
-
.: 前位置计数器(链接地址)它表示当前输出段中正在填充的内存地址位置。当链接器处理输入文件时,位置计数器会随着每个字节的输出而自动递增。作用如下:
- 对齐操作
. = ALIGN(4);
→ 将当前位置计数器推进到下一个4字节对齐的地址
→ 例如:若当前是地址0x1001,执行后变为0x1004 - 记录起始/结束地址
__preinit_array_start = .;
→ 将当前位置值赋给符号 __preinit_array_start
→ 相当于声明:该段的起始地址 = 当前地址
- 对齐操作
-
*:通配符,匹配所有输入文件例:*(.vectors),匹配所有输入文件的
.vectors段,按链接顺序排列 -
(段名):输入对象文件中的段
-
SECTIONS
{
.text : { ... } >rom
.data : { ... } >ram AT >rom
.bss : { ... } >ram
}
2. 关键段解析
2.1 代码段(.text)
.text : {
*(.vectors) /* 中断向量表 */
*(.text*) /* 程序代码 */
*(.rodata*) /* 只读数据 */
. = ALIGN(4); /* 4字节对齐 */
} >rom
2.2 数据段
2.2.1 初始化数据(.data)
包含已初始化的全局/静态变量,以及需要在RAM中运行的函数
RAM ROM
┌───────────────────┐ ┌───────────────────┐
│ │ │ │
│ .text (代码) │ │ │
│ .rodata (常量) │ │ │
├───────────────────┤ ├───────────────────┤
│ .data 初始值 │ ←复制──┐ │ .data (变量) │
│ (原始数据) │ │ │ initialized=42 │
├───────────────────┤ └──┤ str="Hello" │
│ ... │ ├───────────────────┤
│ │ │ .bss │
└───────────────────┘ │ uninitialized=0 │
│ zero_init=0.0 │
| │
└───────────────────┘
.data : {
_data = .; /* 数据区起始符号 */
*(.data*) /* 已初始化数据 */
*(.ramtext*) /* RAM中运行的函数 */
_edata = .; /* 数据区结束符号 */
} >ram AT >rom /* 初始化值在ROM ,程序启动后把整个段复制到RAM中,形成所谓的“赋初值”*/
_data_loadaddr = LOADADDR(.data); /* ROM中初始化数据地址 */
2.2.1 未初始化数据(.bss)
包含所有初始值为0或未显式初始化的全局/静态变量
.bss : {
*(.bss*) /* 未初始化数据 */
*(COMMON) /* 公共变量 */
_ebss = .; /* BSS段结束 */
} >ram
2.3 C++特殊处理
.preinit_array : { ... } >rom /* 预初始化数组 */
.init_array : { ... } >rom /* 构造函数 */
.fini_array : { ... } >rom /* 析构函数 */
2.4 ARM异常处理
.ARM.extab : { ... } >rom /* 异常处理表 */
.ARM.exidx : { ... } >rom /* 异常索引表 */
3. 关键设计特性
3.1 地址对齐
. = ALIGN(4); /* 全脚本出现6次 */
- 保证所有段起始地址4字节对齐
- 满足Cortex-M架构的访问要求
3.2 符号导出
_etext = .; /* 代码结束地址 */
end = .; /* 堆起始地址 */
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); /* 栈顶指针 */
- 为启动文件提供关键地址信息
- 自动计算栈顶位置(RAM末尾)
3.3 优化策略
/DISCARD/ : { *(.eh_frame) } /* 丢弃C++异常帧 */
.noinit (NOLOAD) : { ... } /* 保留不被初始化的RAM数据 */
- 减少不必要的内存占用
- 支持快速启动需求
4. 启动流程详解
4.1 启动代码依赖符号
/* 启动文件(startup.c)关键代码片段 */
extern uint32_t _etext; /* .text段结束地址 */
extern uint32_t _data; /* .data段起始(RAM) */
extern uint32_t _edata; /* .data段结束(RAM) */
extern uint32_t _bss_start; /* .bss段起始 */
extern uint32_t _bss_end; /* .bss段结束 */
/* 初始化数据段 */
uint32_t *src = &_etext; /* ROM中初始化数据位置 */
uint32_t *dst = &_data; /* RAM中数据目标位置 */
while (dst < &_edata) *dst++ = *src++;
/* 清零BSS段 */
dst = &_bss_start;
while (dst < &_bss_end) *dst++ = 0;
4.2 数据加载机制
AT >rom语法生成LOADADDR(.data)符号- 启动代码将LOADADDR到LOADADDR+(edata-data)的ROM数据复制到_data到_edata的RAM区域
- 物理实现:芯片复位后由启动代码完成拷贝
4.3 栈初始化
/* 设置主栈指针 */
__set_MSP(*(uint32_t*)ORIGIN(ram) + LENGTH(ram));
_stack符号值 = RAM起始地址 + RAM长度- Cortex-M架构要求栈地址向下生长
5. 实际内存布局示例(STM32F103C8T6)
Memory Map (Hex)
----------------
0x08000000 +---------------+
| .vectors |
| .text |
| .rodata |
0x08001A00 |---------------| ← _etext
| .init_array |
| .fini_array |
0x08002000 +---------------+
| 未使用ROM区域 |
0x08010000 +---------------+
| |
0x20000000 +---------------+ ← RAM起始
| .data | ← _data
| |
0x20000100 +---------------+ ← _edata
| .bss | ← _bss_start
| |
0x20000200 +---------------+ ← _ebss
| 堆空间 | ← end
| |
| |
| 栈空间 |
| (向下生长) |
0x20001000 +---------------+ ← _stack (栈顶)
6. 完整代码
/*
* This is a generic linker script for Cortex-M targets using libopencm3.
*
* Memory regions MUST be defined in the ld script which includes this one!
* Example:
MEMORY
{
rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
}
INCLUDE cortex-m-generic.ld
*/
/* Enforce emmition of the vector table. */
EXTERN (vector_table)
/* Define the entry point of the output file. */
ENTRY(reset_handler)
/* Define sections. */
SECTIONS
{
.text : {
*(.vectors) /* Vector table */
*(.text*) /* Program code */
. = ALIGN(4);
*(.rodata*) /* Read-only data */
. = ALIGN(4);
} >rom
/* C++ Static constructors/destructors, also used for __attribute__
* ((constructor)) and the likes */
.preinit_array : {
. = ALIGN(4);
__preinit_array_start = .;
KEEP (*(.preinit_array))
__preinit_array_end = .;
} >rom
.init_array : {
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} >rom
.fini_array : {
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
} >rom
/*
* Another section used by C++ stuff, appears when using newlib with
* 64bit (long long) printf support
*/
.ARM.extab : {
*(.ARM.extab*)
} >rom
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >rom
. = ALIGN(4);
_etext = .;
/* ram, but not cleared on reset, eg boot/app comms */
.noinit (NOLOAD) : {
*(.noinit*)
} >ram
. = ALIGN(4);
.data : {
_data = .;
*(.data*) /* Read-write initialized data */
*(.ramtext*) /* "text" functions to run in ram */
. = ALIGN(4);
_edata = .;
} >ram AT >rom
_data_loadaddr = LOADADDR(.data);
.bss : {
*(.bss*) /* Read-write zero initialized data */
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >ram
/*
* The .eh_frame section appears to be used for C++ exception handling.
* You may need to fix this if you're using C++.
*/
/DISCARD/ : { *(.eh_frame) }
. = ALIGN(4);
end = .;
}
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));
本分析基于libopencm3的
cortex-m-generic.ld链接脚本
文件路径:lib/libopencm3/lib/cortex-m-generic.ld
936

被折叠的 条评论
为什么被折叠?



