GCC编译(7)链接脚本LinkerScripts
Author: Once Day Date: 2026年2月25日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 编译构建工具链_Once-Day的博客-CSDN博客
参考文章:
- GNU LD Linker Scripts
- 链接脚本(Linker Scripts)语法和规则解析
- 链接脚本的作用及格式
- GCC之编译(7)Linker链接脚本_gcc 链接脚本-CSDN博客
文章目录
- GCC编译(7)链接脚本LinkerScripts
-
-
-
- 1. LinkerScripts概述
- 2. LinkerScripts简单示例
- 3. 基础命令
- 4. 符号赋值
- 5. SECTIONS命令
- 6. MEMORY命令
- 7. PHDRS命令
- 8. 符号版本命令
- 9. 链接器脚本中的表达式
- 10. 内置函数
-
-
1. LinkerScripts概述
在 ELF 体系下,object file 是链接器处理的基本单元,既可以是编译产生的可重定位文件(.o),也可以是共享库或最终可执行文件。可重定位文件以 section 为组织核心,保存代码、数据、符号与重定位信息;而可执行文件在装载阶段则以 segment 为单位由加载器解析。链接器的职责是在多个输入 object file 之间解析符号、合并同名 section,并依据链接脚本生成新的输出布局。
section 是逻辑划分,典型如 .text、.data、.bss、.rodata。每个 section 具有属性标志(如 SHF_ALLOC、SHF_EXECINSTR),决定其是否参与装载及访问权限。segment 则来源于 ELF 中的 Program Header,由一个或多个属性相近的 section 聚合而成,例如一个 PT_LOAD 段通常同时覆盖 .text 与 .rodata。简言之,section 面向链接,segment 面向运行时加载。
内存布局的核心概念是 VMA 与 LMA。VMA(Virtual Memory Address)表示程序运行时访问该段内容所使用的虚拟地址;LMA(Load Memory Address)表示该内容在镜像文件或物理存储中的加载地址。两者在桌面系统中往往一致,但在嵌入式场景下常常分离,例如代码在 Flash 中加载、数据在 RAM 中运行:
SECTIONS {
.text 0x08000000 : { *(.text) }
.data 0x20000000 : AT(0x08010000) { *(.data) }
}
此处 .data 的 VMA 为 0x20000000,而 LMA 位于 Flash 地址 0x08010000。启动代码需在复位后将 .data 从 LMA 拷贝到 VMA。
链接过程可以抽象为从输入 section 到输出 section,再到运行时 segment 的映射关系:
#mermaid-svg-YpNV4KuELmgWrdHV{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YpNV4KuELmgWrdHV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YpNV4KuELmgWrdHV .error-icon{fill:#552222;}#mermaid-svg-YpNV4KuELmgWrdHV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YpNV4KuELmgWrdHV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YpNV4KuELmgWrdHV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YpNV4KuELmgWrdHV .marker.cross{stroke:#333333;}#mermaid-svg-YpNV4KuELmgWrdHV svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YpNV4KuELmgWrdHV p{margin:0;}#mermaid-svg-YpNV4KuELmgWrdHV .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-YpNV4KuELmgWrdHV .cluster-label text{fill:#333;}#mermaid-svg-YpNV4KuELmgWrdHV .cluster-label span{color:#333;}#mermaid-svg-YpNV4KuELmgWrdHV .cluster-label span p{background-color:transparent;}#mermaid-svg-YpNV4KuELmgWrdHV .label text,#mermaid-svg-YpNV4KuELmgWrdHV span{fill:#333;color:#333;}#mermaid-svg-YpNV4KuELmgWrdHV .node rect,#mermaid-svg-YpNV4KuELmgWrdHV .node circle,#mermaid-svg-YpNV4KuELmgWrdHV .node ellipse,#mermaid-svg-YpNV4KuELmgWrdHV .node polygon,#mermaid-svg-YpNV4KuELmgWrdHV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YpNV4KuELmgWrdHV .rough-node .label text,#mermaid-svg-YpNV4KuELmgWrdHV .node .label text,#mermaid-svg-YpNV4KuELmgWrdHV .image-shape .label,#mermaid-svg-YpNV4KuELmgWrdHV .icon-shape .label{text-anchor:middle;}#mermaid-svg-YpNV4KuELmgWrdHV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YpNV4KuELmgWrdHV .rough-node .label,#mermaid-svg-YpNV4KuELmgWrdHV .node .label,#mermaid-svg-YpNV4KuELmgWrdHV .image-shape .label,#mermaid-svg-YpNV4KuELmgWrdHV .icon-shape .label{text-align:center;}#mermaid-svg-YpNV4KuELmgWrdHV .node.clickable{cursor:pointer;}#mermaid-svg-YpNV4KuELmgWrdHV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YpNV4KuELmgWrdHV .arrowheadPath{fill:#333333;}#mermaid-svg-YpNV4KuELmgWrdHV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YpNV4KuELmgWrdHV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YpNV4KuELmgWrdHV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YpNV4KuELmgWrdHV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YpNV4KuELmgWrdHV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YpNV4KuELmgWrdHV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YpNV4KuELmgWrdHV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YpNV4KuELmgWrdHV .cluster text{fill:#333;}#mermaid-svg-YpNV4KuELmgWrdHV .cluster span{color:#333;}#mermaid-svg-YpNV4KuELmgWrdHV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YpNV4KuELmgWrdHV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YpNV4KuELmgWrdHV rect.text{fill:none;stroke-width:0;}#mermaid-svg-YpNV4KuELmgWrdHV .icon-shape,#mermaid-svg-YpNV4KuELmgWrdHV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YpNV4KuELmgWrdHV .icon-shape p,#mermaid-svg-YpNV4KuELmgWrdHV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YpNV4KuELmgWrdHV .icon-shape rect,#mermaid-svg-YpNV4KuELmgWrdHV .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YpNV4KuELmgWrdHV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YpNV4KuELmgWrdHV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YpNV4KuELmgWrdHV :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Input .o Sections
Linker Script Mapping
Output Sections
Program Headers
Runtime Segments
链接脚本通过 SECTIONS、MEMORY、AT() 等语句精确控制上述映射,使开发者能够定义代码与数据的排列顺序、地址分布及加载策略。这种机制在操作系统内核、Bootloader 以及多存储介质嵌入式系统中尤为关键。
2. LinkerScripts简单示例
链接器脚本本质是面向 ld 的领域专用语言,其语法结构较为简洁,但表达能力极强。每条语句要么是控制类关键字(如 SECTIONS、MEMORY、ENTRY),要么是符号赋值表达式,例如对位置计数器 . 进行修改。语句之间以分号分隔,空白字符仅起到分隔作用。字符串通常可直接书写,若文件名包含逗号等特殊字符,则需使用双引号包裹,语法规则与 C 类似。
脚本支持 C 风格注释 /* … */,在语法上等同于空格,因此可以安全插入到任意语句之间。这种设计使脚本既保持可读性,又便于版本控制审查。在大型嵌入式工程中,常通过注释说明内存布局与硬件地址空间的映射关系,从而避免魔数难以理解的问题。
核心结构通常围绕 SECTIONS 展开,用于定义输出节的排列与地址。位置计数器 . 表示当前输出地址,修改它相当于显式跳转布局位置。例如:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
上述脚本首先将 .text 放置在 0x10000,随后将位置计数器跳转至 0x8000000,再依次布置 .data 与 .bss。*(.text) 表示收集所有输入文件中的 .text 段并合并到当前输出段。链接器在此阶段完成符号重定位与地址分配。
从执行流程看,链接器读取脚本后建立内部地址空间模型,再按脚本顺序合并输入节并更新位置计数器:
#mermaid-svg-uaJF3C9yk4BOx2bv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uaJF3C9yk4BOx2bv .error-icon{fill:#552222;}#mermaid-svg-uaJF3C9yk4BOx2bv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uaJF3C9yk4BOx2bv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uaJF3C9yk4BOx2bv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uaJF3C9yk4BOx2bv .marker.cross{stroke:#333333;}#mermaid-svg-uaJF3C9yk4BOx2bv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uaJF3C9yk4BOx2bv p{margin:0;}#mermaid-svg-uaJF3C9yk4BOx2bv .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster-label text{fill:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster-label span{color:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster-label span p{background-color:transparent;}#mermaid-svg-uaJF3C9yk4BOx2bv .label text,#mermaid-svg-uaJF3C9yk4BOx2bv span{fill:#333;color:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv .node rect,#mermaid-svg-uaJF3C9yk4BOx2bv .node circle,#mermaid-svg-uaJF3C9yk4BOx2bv .node ellipse,#mermaid-svg-uaJF3C9yk4BOx2bv .node polygon,#mermaid-svg-uaJF3C9yk4BOx2bv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uaJF3C9yk4BOx2bv .rough-node .label text,#mermaid-svg-uaJF3C9yk4BOx2bv .node .label text,#mermaid-svg-uaJF3C9yk4BOx2bv .image-shape .label,#mermaid-svg-uaJF3C9yk4BOx2bv .icon-shape .label{text-anchor:middle;}#mermaid-svg-uaJF3C9yk4BOx2bv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uaJF3C9yk4BOx2bv .rough-node .label,#mermaid-svg-uaJF3C9yk4BOx2bv .node .label,#mermaid-svg-uaJF3C9yk4BOx2bv .image-shape .label,#mermaid-svg-uaJF3C9yk4BOx2bv .icon-shape .label{text-align:center;}#mermaid-svg-uaJF3C9yk4BOx2bv .node.clickable{cursor:pointer;}#mermaid-svg-uaJF3C9yk4BOx2bv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uaJF3C9yk4BOx2bv .arrowheadPath{fill:#333333;}#mermaid-svg-uaJF3C9yk4BOx2bv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uaJF3C9yk4BOx2bv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uaJF3C9yk4BOx2bv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uaJF3C9yk4BOx2bv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uaJF3C9yk4BOx2bv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uaJF3C9yk4BOx2bv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster text{fill:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv .cluster span{color:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uaJF3C9yk4BOx2bv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uaJF3C9yk4BOx2bv rect.text{fill:none;stroke-width:0;}#mermaid-svg-uaJF3C9yk4BOx2bv .icon-shape,#mermaid-svg-uaJF3C9yk4BOx2bv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uaJF3C9yk4BOx2bv .icon-shape p,#mermaid-svg-uaJF3C9yk4BOx2bv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uaJF3C9yk4BOx2bv .icon-shape rect,#mermaid-svg-uaJF3C9yk4BOx2bv .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uaJF3C9yk4BOx2bv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uaJF3C9yk4BOx2bv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uaJF3C9yk4BOx2bv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Parse Script
Initialize Location Counter
Map Input Sections
Assign VMA/LMA
Emit ELF Output
这种机制使开发者能够精确控制镜像布局,例如将代码与数据放入不同物理区域,或在裸机环境下构建固定地址的启动映像。
3. 基础命令
链接脚本除 SECTIONS 外,还提供若干基础命令,用于控制入口符号、输入文件、目标格式以及内存模型。这些命令通常位于脚本顶层,与段布局共同构成完整的链接描述。合理使用它们,可以替代大量命令行选项,使构建过程具备可重复性与可移植性。
设置程序入口点通常使用 ENTRY(symbol)。该命令指定 ELF 头中的 e_entry 字段,决定装载后 CPU 跳转的首地址。若未显式指定,链接器会按默认规则查找 _start。例如:
ENTRY(_start)
在裸机或自定义启动流程中,入口符号可能来自启动汇编文件,必须确保该符号在最终符号表中可见,否则会产生未定义引用。
处理输入文件可通过 INPUT()、GROUP()、SEARCH_DIR() 等命令完成。INPUT() 等价于在命令行指定目标文件;GROUP() 用于解决静态库的循环依赖,链接器会在组内多轮解析;SEARCH_DIR() 则扩展库搜索路径。例如:
SEARCH_DIR("/opt/lib");
GROUP(libc.a libm.a)
这些命令本质上影响符号解析顺序与库的重定位决策。
处理目标文件格式主要依赖 OUTPUT_FORMAT() 与 OUTPUT_ARCH()。前者指定生成文件的格式(如 elf64-x86-64),后者指定体系结构,从而约束重定位类型与指令集。例如:
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
这在交叉编译或生成特定 ABI 映像时尤为关键。
为内存区域分配别名通常结合 MEMORY 与 REGION_ALIAS() 使用。MEMORY 定义物理存储区属性,REGION_ALIAS() 为不同逻辑段提供统一引用名,便于多平台复用脚本:
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx): ORIGIN = 0x20000000, LENGTH = 128K
}
REGION_ALIAS("REGION_TEXT", FLASH)
其他常见命令还包括 INCLUDE() 用于拆分脚本模块、EXTERN() 强制保留符号、PROVIDE() 定义弱符号默认值等。这些机制使链接脚本不仅描述布局,还能参与符号决策与构建策略控制。
4. 符号赋值
链接脚本中的符号赋值本质是对地址表达式求值,并将结果写入符号表。语法上支持与 C 一致的赋值与复合赋值运算符,例如 =, +=, &= 等,但其语义始终围绕地址计算展开。最常见的操作对象是位置计数器 .,它表示当前输出节的 VMA,因此常用于记录段结束地址或进行对齐控制。
例如在段内部定义 _etext = .;,表示将 .text 结束时的地址写入符号 _etext。而 _bdata = (. + 3) & ~3; 则通过位运算实现 4 字节对齐。这类表达式在裸机系统中用于构造数据复制边界或堆栈起始地址:
.text : {
*(.text)
_etext = .;
}
_bdata = ALIGN(4);
.data : { *(.data) }
对于 ELF 目标,若希望符号仅在本目标文件内部可见且不导出到动态符号表,可使用 HIDDEN(symbol = expression)。该语法会将符号标记为 STV_HIDDEN,从而避免与共享库产生符号冲突。这在构建运行时辅助边界符号时尤为重要。
在某些场景下,仅当符号未被其他目标文件定义时才希望提供默认值,可以使用 PROVIDE(symbol = expression)。链接器只有在符号未解析时才会创建该定义,避免覆盖用户实现。例如为应用程序提供弱默认栈顶:
PROVIDE(_stack_top = ORIGIN(RAM) + LENGTH(RAM));
需要注意,链接脚本中定义的“符号”并非高级语言变量,而是一个地址标签,没有存储单元。源代码中通常通过声明为外部数组或指针的方式访问其地址:
extern char _etext[];
extern char _bdata[];
size_t text_size = _bdata – _etext;
这里访问的是符号地址本身,而不是符号“所存储的值”。理解这一点对于正确处理启动代码和段复制逻辑至关重要。
5. SECTIONS命令
SECTIONS 是链接脚本的核心结构,它定义了从输入节到输出节的映射规则,以及输出节在虚拟地址空间中的排列方式。链接器在解析完所有输入文件后,会按脚本中 SECTIONS 的描述构建新的节表与程序头,从而决定最终 ELF 的 VMA、LMA 与段权限属性。
其基本语法是一个命令块,其中每条 sections-command 都参与输出布局控制。常见形式包括符号赋值、ENTRY 指定入口、输出节描述以及覆盖(OVERLAY)定义。由于位置计数器 . 在该作用域内可直接使用,将符号定义与节描述放在同一区域,有助于在逻辑上贴近实际布局,增强脚本可读性与可维护性。
典型的输出节描述形式如下:
SECTIONS
{
. = 0x10000;
.text : {
*(.text)
_etext = .;
}
.data : {
_sdata = .;
*(.data)
_edata = .;
}
}
此结构体现了两个关键机制:其一,输入节通过通配符模式聚合到指定输出节;其二,位置计数器在每次放置内容后递增,从而驱动后续地址分配。符号赋值与节布局天然耦合。
若脚本中完全省略 SECTIONS,链接器会采用默认策略:按输入文件中节首次出现的顺序生成同名输出节,并从地址零开始顺序排布。这种行为适用于宿主系统的常规可执行程序,但在裸机或多存储区域系统中往往不满足需求,因此显式定义 SECTIONS 成为精确控制内存映像的必要手段。
从内部流程看,链接器会依次执行如下步骤:
#mermaid-svg-VKOXJsbJL82pjNR4{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VKOXJsbJL82pjNR4 .error-icon{fill:#552222;}#mermaid-svg-VKOXJsbJL82pjNR4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VKOXJsbJL82pjNR4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VKOXJsbJL82pjNR4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VKOXJsbJL82pjNR4 .marker.cross{stroke:#333333;}#mermaid-svg-VKOXJsbJL82pjNR4 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VKOXJsbJL82pjNR4 p{margin:0;}#mermaid-svg-VKOXJsbJL82pjNR4 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster-label text{fill:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster-label span{color:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster-label span p{background-color:transparent;}#mermaid-svg-VKOXJsbJL82pjNR4 .label text,#mermaid-svg-VKOXJsbJL82pjNR4 span{fill:#333;color:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 .node rect,#mermaid-svg-VKOXJsbJL82pjNR4 .node circle,#mermaid-svg-VKOXJsbJL82pjNR4 .node ellipse,#mermaid-svg-VKOXJsbJL82pjNR4 .node polygon,#mermaid-svg-VKOXJsbJL82pjNR4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VKOXJsbJL82pjNR4 .rough-node .label text,#mermaid-svg-VKOXJsbJL82pjNR4 .node .label text,#mermaid-svg-VKOXJsbJL82pjNR4 .image-shape .label,#mermaid-svg-VKOXJsbJL82pjNR4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-VKOXJsbJL82pjNR4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VKOXJsbJL82pjNR4 .rough-node .label,#mermaid-svg-VKOXJsbJL82pjNR4 .node .label,#mermaid-svg-VKOXJsbJL82pjNR4 .image-shape .label,#mermaid-svg-VKOXJsbJL82pjNR4 .icon-shape .label{text-align:center;}#mermaid-svg-VKOXJsbJL82pjNR4 .node.clickable{cursor:pointer;}#mermaid-svg-VKOXJsbJL82pjNR4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VKOXJsbJL82pjNR4 .arrowheadPath{fill:#333333;}#mermaid-svg-VKOXJsbJL82pjNR4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VKOXJsbJL82pjNR4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VKOXJsbJL82pjNR4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VKOXJsbJL82pjNR4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VKOXJsbJL82pjNR4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VKOXJsbJL82pjNR4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster text{fill:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 .cluster span{color:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VKOXJsbJL82pjNR4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VKOXJsbJL82pjNR4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-VKOXJsbJL82pjNR4 .icon-shape,#mermaid-svg-VKOXJsbJL82pjNR4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VKOXJsbJL82pjNR4 .icon-shape p,#mermaid-svg-VKOXJsbJL82pjNR4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VKOXJsbJL82pjNR4 .icon-shape rect,#mermaid-svg-VKOXJsbJL82pjNR4 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VKOXJsbJL82pjNR4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VKOXJsbJL82pjNR4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VKOXJsbJL82pjNR4 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Collect Input Sections
Apply SECTIONS Mapping
Assign Addresses via Location Counter
Generate Output Section Table
Build Program Headers
SECTIONS 实际上是链接阶段地址空间建模的描述语言,其表达能力决定了最终程序在物理与虚拟空间中的组织形态。
6. MEMORY命令
MEMORY 命令用于显式描述目标系统的物理内存布局,是嵌入式与裸机场景中不可或缺的机制。默认情况下,链接器假定地址空间连续且无限制,而 MEMORY 则为其建立可用区域模型,使链接阶段具备容量约束与属性检查能力。链接器不会自动重排节以适配区域,而是在超出容量时给出错误或警告。
其语法以区域为单位定义内存块,包括名称、属性、起始地址与长度。例如:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
其中 (rx) 表示可读可执行,(rwx) 表示可读写可执行。这些属性用于匹配输出节的访问特性,若节属性与区域不兼容,链接器将报错。
MEMORY 本身并不分配节,真正的映射发生在 SECTIONS 中,通过 >region 语法绑定输出节到指定内存块。例如:
SECTIONS
{
.text : { *(.text) } > FLASH
.data : { *(.data) } > RAM AT > FLASH
}
此处 .text 被放置到 FLASH,而 .data 的运行地址在 RAM,加载地址在 FLASH。链接器据此计算 VMA/LMA,并生成相应的程序头。
内部流程可理解为两阶段约束系统:首先解析 MEMORY 建立区域表;随后在处理 SECTIONS 时,将位置计数器限制在指定区域范围内,并检查剩余空间。
#mermaid-svg-4NsA9BOVsUu71Lgh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4NsA9BOVsUu71Lgh .error-icon{fill:#552222;}#mermaid-svg-4NsA9BOVsUu71Lgh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4NsA9BOVsUu71Lgh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4NsA9BOVsUu71Lgh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4NsA9BOVsUu71Lgh .marker.cross{stroke:#333333;}#mermaid-svg-4NsA9BOVsUu71Lgh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4NsA9BOVsUu71Lgh p{margin:0;}#mermaid-svg-4NsA9BOVsUu71Lgh .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster-label text{fill:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster-label span{color:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster-label span p{background-color:transparent;}#mermaid-svg-4NsA9BOVsUu71Lgh .label text,#mermaid-svg-4NsA9BOVsUu71Lgh span{fill:#333;color:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh .node rect,#mermaid-svg-4NsA9BOVsUu71Lgh .node circle,#mermaid-svg-4NsA9BOVsUu71Lgh .node ellipse,#mermaid-svg-4NsA9BOVsUu71Lgh .node polygon,#mermaid-svg-4NsA9BOVsUu71Lgh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4NsA9BOVsUu71Lgh .rough-node .label text,#mermaid-svg-4NsA9BOVsUu71Lgh .node .label text,#mermaid-svg-4NsA9BOVsUu71Lgh .image-shape .label,#mermaid-svg-4NsA9BOVsUu71Lgh .icon-shape .label{text-anchor:middle;}#mermaid-svg-4NsA9BOVsUu71Lgh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4NsA9BOVsUu71Lgh .rough-node .label,#mermaid-svg-4NsA9BOVsUu71Lgh .node .label,#mermaid-svg-4NsA9BOVsUu71Lgh .image-shape .label,#mermaid-svg-4NsA9BOVsUu71Lgh .icon-shape .label{text-align:center;}#mermaid-svg-4NsA9BOVsUu71Lgh .node.clickable{cursor:pointer;}#mermaid-svg-4NsA9BOVsUu71Lgh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4NsA9BOVsUu71Lgh .arrowheadPath{fill:#333333;}#mermaid-svg-4NsA9BOVsUu71Lgh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4NsA9BOVsUu71Lgh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4NsA9BOVsUu71Lgh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4NsA9BOVsUu71Lgh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4NsA9BOVsUu71Lgh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4NsA9BOVsUu71Lgh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster text{fill:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh .cluster span{color:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4NsA9BOVsUu71Lgh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4NsA9BOVsUu71Lgh rect.text{fill:none;stroke-width:0;}#mermaid-svg-4NsA9BOVsUu71Lgh .icon-shape,#mermaid-svg-4NsA9BOVsUu71Lgh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4NsA9BOVsUu71Lgh .icon-shape p,#mermaid-svg-4NsA9BOVsUu71Lgh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4NsA9BOVsUu71Lgh .icon-shape rect,#mermaid-svg-4NsA9BOVsUu71Lgh .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4NsA9BOVsUu71Lgh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4NsA9BOVsUu71Lgh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4NsA9BOVsUu71Lgh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Parse MEMORY Regions
Build Region Table
Assign Sections via SECTIONS
Check Region Overflow
Emit ELF with VMA/LMA
通过这种方式,MEMORY 提供了静态资源验证能力,使链接阶段即可发现内存溢出问题,而无需依赖运行时检测。
7. PHDRS命令
在 ELF 体系中,Program Header(程序头)描述的是“如何装载”,而非“如何组织”。它以 segment 为单位定义加载地址、文件偏移、内存大小及访问权限。运行时加载器依据这些条目建立内存映射,例如将 PT_LOAD 类型段映射为可执行或可写区域。可通过 objdump -p a.out 查看程序头内容。
默认情况下,ld 会根据输出节属性自动生成合理的程序头,例如将可执行节合并为一个 PT_LOAD 段、将可写数据放入另一个段,并生成 PT_PHDR、PT_INTERP 等辅助条目。然而在内核、Bootloader 或多镜像系统中,往往需要精确控制段边界、对齐方式或加载顺序,此时可使用 PHDRS 命令显式定义程序头。
PHDRS 允许定义段名、类型及标志位,例如:
PHDRS
{
text PT_LOAD FLAGS(5); /* R + X */
data PT_LOAD FLAGS(6); /* R + W */
}
SECTIONS
{
.text : { *(.text) } :text
.data : { *(.data) } :data
}
其中 FLAGS(5) 表示 PF_R | PF_X。输出节通过 :phdr_name 绑定到指定程序头,从而精确控制节到段的映射关系。若脚本中出现 PHDRS,链接器将不再自动生成其他程序头,必须完整定义所需条目。
需要注意,PHDRS 仅在生成 ELF 输出时生效,对于 binary 或 srec 等格式会被忽略。其内部处理流程大致如下:
#mermaid-svg-0QcbHi6RXL907jmE{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0QcbHi6RXL907jmE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0QcbHi6RXL907jmE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0QcbHi6RXL907jmE .error-icon{fill:#552222;}#mermaid-svg-0QcbHi6RXL907jmE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0QcbHi6RXL907jmE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0QcbHi6RXL907jmE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0QcbHi6RXL907jmE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0QcbHi6RXL907jmE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0QcbHi6RXL907jmE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0QcbHi6RXL907jmE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0QcbHi6RXL907jmE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0QcbHi6RXL907jmE .marker.cross{stroke:#333333;}#mermaid-svg-0QcbHi6RXL907jmE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0QcbHi6RXL907jmE p{margin:0;}#mermaid-svg-0QcbHi6RXL907jmE .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0QcbHi6RXL907jmE .cluster-label text{fill:#333;}#mermaid-svg-0QcbHi6RXL907jmE .cluster-label span{color:#333;}#mermaid-svg-0QcbHi6RXL907jmE .cluster-label span p{background-color:transparent;}#mermaid-svg-0QcbHi6RXL907jmE .label text,#mermaid-svg-0QcbHi6RXL907jmE span{fill:#333;color:#333;}#mermaid-svg-0QcbHi6RXL907jmE .node rect,#mermaid-svg-0QcbHi6RXL907jmE .node circle,#mermaid-svg-0QcbHi6RXL907jmE .node ellipse,#mermaid-svg-0QcbHi6RXL907jmE .node polygon,#mermaid-svg-0QcbHi6RXL907jmE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0QcbHi6RXL907jmE .rough-node .label text,#mermaid-svg-0QcbHi6RXL907jmE .node .label text,#mermaid-svg-0QcbHi6RXL907jmE .image-shape .label,#mermaid-svg-0QcbHi6RXL907jmE .icon-shape .label{text-anchor:middle;}#mermaid-svg-0QcbHi6RXL907jmE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0QcbHi6RXL907jmE .rough-node .label,#mermaid-svg-0QcbHi6RXL907jmE .node .label,#mermaid-svg-0QcbHi6RXL907jmE .image-shape .label,#mermaid-svg-0QcbHi6RXL907jmE .icon-shape .label{text-align:center;}#mermaid-svg-0QcbHi6RXL907jmE .node.clickable{cursor:pointer;}#mermaid-svg-0QcbHi6RXL907jmE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0QcbHi6RXL907jmE .arrowheadPath{fill:#333333;}#mermaid-svg-0QcbHi6RXL907jmE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0QcbHi6RXL907jmE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0QcbHi6RXL907jmE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0QcbHi6RXL907jmE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0QcbHi6RXL907jmE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0QcbHi6RXL907jmE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0QcbHi6RXL907jmE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0QcbHi6RXL907jmE .cluster text{fill:#333;}#mermaid-svg-0QcbHi6RXL907jmE .cluster span{color:#333;}#mermaid-svg-0QcbHi6RXL907jmE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0QcbHi6RXL907jmE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0QcbHi6RXL907jmE rect.text{fill:none;stroke-width:0;}#mermaid-svg-0QcbHi6RXL907jmE .icon-shape,#mermaid-svg-0QcbHi6RXL907jmE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0QcbHi6RXL907jmE .icon-shape p,#mermaid-svg-0QcbHi6RXL907jmE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0QcbHi6RXL907jmE .icon-shape rect,#mermaid-svg-0QcbHi6RXL907jmE .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0QcbHi6RXL907jmE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0QcbHi6RXL907jmE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0QcbHi6RXL907jmE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Parse PHDRS
Create Program Header Table
Bind Output Sections
Compute Offsets & Alignment
Emit ELF Headers
通过显式控制程序头,可以确保加载器按预期方式映射内存区域,避免因默认段合并策略导致权限或地址布局不符合系统约束。
8. 符号版本命令
在 ELF 共享库中,符号不仅具有可见性属性(STV_DEFAULT、STV_HIDDEN),还可以绑定到特定版本节点。ld 通过 –version-script 读取版本脚本,在链接阶段生成 .gnu.version* 相关段,从而构建符号版本树。该机制既能控制导出集合,又能维持 ABI 向后兼容,是大型基础库演进的核心手段。
版本脚本以“版本节点”为基本单元,节点之间形成有向继承关系。示例中 VERS_1.2 继承 VERS_1.1,VERS_2.0 继承 VERS_1.2,形成链式演进结构。每个节点可定义 global 与 local 符号集合;未显式声明的符号默认继承上级规则。local: 块会将匹配符号降级为局部,从动态符号表中移除,从而不对外导出。
例如:
VERS_1.1 {
global: foo1;
local: old*; original*; new*;
};
表示仅 foo1 以 VERS_1.1 版本对外可见,其余匹配前缀的符号在共享库外不可解析。后续节点若写成:
VERS_1.2 { foo2; } VERS_1.1;
则 foo2 绑定到 VERS_1.2,同时继承 VERS_1.1 的导出符号。动态链接器在解析时会根据可执行文件中记录的版本需求,选择匹配节点,从而保证旧程序仍可绑定旧实现。
对 C++ 符号,版本脚本支持 extern "C++" 块与签名字符串匹配,例如:
extern "C++" {
ns::*;
"f(int, double)";
};
其匹配对象是经过 demangle 处理后的符号名,而非原始 mangled name。这对于模板与命名空间控制尤为重要。
构建流程可概括为:
#mermaid-svg-YkECZx20lMadCOKk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YkECZx20lMadCOKk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YkECZx20lMadCOKk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YkECZx20lMadCOKk .error-icon{fill:#552222;}#mermaid-svg-YkECZx20lMadCOKk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YkECZx20lMadCOKk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YkECZx20lMadCOKk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YkECZx20lMadCOKk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YkECZx20lMadCOKk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YkECZx20lMadCOKk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YkECZx20lMadCOKk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YkECZx20lMadCOKk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YkECZx20lMadCOKk .marker.cross{stroke:#333333;}#mermaid-svg-YkECZx20lMadCOKk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YkECZx20lMadCOKk p{margin:0;}#mermaid-svg-YkECZx20lMadCOKk .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-YkECZx20lMadCOKk .cluster-label text{fill:#333;}#mermaid-svg-YkECZx20lMadCOKk .cluster-label span{color:#333;}#mermaid-svg-YkECZx20lMadCOKk .cluster-label span p{background-color:transparent;}#mermaid-svg-YkECZx20lMadCOKk .label text,#mermaid-svg-YkECZx20lMadCOKk span{fill:#333;color:#333;}#mermaid-svg-YkECZx20lMadCOKk .node rect,#mermaid-svg-YkECZx20lMadCOKk .node circle,#mermaid-svg-YkECZx20lMadCOKk .node ellipse,#mermaid-svg-YkECZx20lMadCOKk .node polygon,#mermaid-svg-YkECZx20lMadCOKk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YkECZx20lMadCOKk .rough-node .label text,#mermaid-svg-YkECZx20lMadCOKk .node .label text,#mermaid-svg-YkECZx20lMadCOKk .image-shape .label,#mermaid-svg-YkECZx20lMadCOKk .icon-shape .label{text-anchor:middle;}#mermaid-svg-YkECZx20lMadCOKk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YkECZx20lMadCOKk .rough-node .label,#mermaid-svg-YkECZx20lMadCOKk .node .label,#mermaid-svg-YkECZx20lMadCOKk .image-shape .label,#mermaid-svg-YkECZx20lMadCOKk .icon-shape .label{text-align:center;}#mermaid-svg-YkECZx20lMadCOKk .node.clickable{cursor:pointer;}#mermaid-svg-YkECZx20lMadCOKk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YkECZx20lMadCOKk .arrowheadPath{fill:#333333;}#mermaid-svg-YkECZx20lMadCOKk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YkECZx20lMadCOKk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YkECZx20lMadCOKk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YkECZx20lMadCOKk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YkECZx20lMadCOKk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YkECZx20lMadCOKk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YkECZx20lMadCOKk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YkECZx20lMadCOKk .cluster text{fill:#333;}#mermaid-svg-YkECZx20lMadCOKk .cluster span{color:#333;}#mermaid-svg-YkECZx20lMadCOKk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YkECZx20lMadCOKk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YkECZx20lMadCOKk rect.text{fill:none;stroke-width:0;}#mermaid-svg-YkECZx20lMadCOKk .icon-shape,#mermaid-svg-YkECZx20lMadCOKk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YkECZx20lMadCOKk .icon-shape p,#mermaid-svg-YkECZx20lMadCOKk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YkECZx20lMadCOKk .icon-shape rect,#mermaid-svg-YkECZx20lMadCOKk .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YkECZx20lMadCOKk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YkECZx20lMadCOKk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YkECZx20lMadCOKk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Compile Objects
ld –version-script
Generate .dynsym & .gnu.version
Runtime Version Check by ld.so
通过版本脚本集中管理符号导出,比在源码中分散使用 __attribute__((visibility)) 更具全局一致性,同时提供 ABI 演进所需的多版本共存能力。
9. 链接器脚本中的表达式
ld 的链接器脚本采用与 C 类似的表达式语法,但语义更贴近地址计算与段布局控制。所有表达式按整数运算,位宽取决于目标架构,常见为 32 或 64 位。表达式在脚本解析阶段或分配阶段求值,其核心作用是决定输出段的地址、大小与符号值,而非生成运行时代码。
常量与符号是表达式的基础单元。可以直接使用十进制、十六进制常量,也可以引用已定义符号或内建符号。例如:
_start = 0x80000000;
_stack_top = ORIGIN(RAM) + LENGTH(RAM);
符号在脚本中本质是可重新赋值的地址标签,赋值语句会影响后续地址计算。未定义符号若未被解析,将在链接阶段报错。
位置计数器 . 是脚本中最关键的特殊符号,它表示当前输出段的地址偏移。对 . 的赋值会移动段布局位置,例如:
.text : {
*(.text)
. = ALIGN(16);
*(.rodata)
}
这里 ALIGN(16) 是表达式函数,返回对齐后的值,. 被更新,从而控制后续 section 的对齐方式。对 . 的修改直接影响最终 ELF 的段布局。
表达式还支持算术、位运算与条件表达式,例如:
_etext = .;
_data_size = _edata – _sdata;
这些表达式在链接阶段求值,用于生成边界符号,供启动代码或运行时使用。表达式所在的 section 决定其语义位置,例如在 SECTIONS 块外赋值与在段内部赋值,其地址含义不同。
孤儿 section 指未在脚本中显式指定的输入 section,ld 会根据规则自动放入合适位置。若表达式引用了孤儿 section 相关符号,其最终值依赖链接器放置顺序,因此在复杂脚本中建议显式列出关键 section,以避免地址漂移造成的不确定性。
10. 内置函数
链接器脚本中的内置函数本质上是为地址计算与段控制服务的“语义工具”,它们在链接阶段由 ld 解释执行,而非生成目标代码。这些函数通常围绕内存区域、对齐控制、段边界与符号存在性判断展开,是编写复杂 SECTIONS 布局时的核心能力。
对齐与地址计算函数最为常用。ALIGN(n) 将当前位置向上对齐到 n 的整数倍,常用于段内结构对齐;BLOCK(n) 与其语义类似但作用于段起始位置。例如:
.text : {
*(.text)
. = ALIGN(16);
}
这里通过修改 . 实现指令对齐。NEXT(n) 则返回下一个满足对齐要求的地址值,常用于分配特定边界。
内存区域相关函数常与 MEMORY 配合使用。ORIGIN(name) 返回区域起始地址,LENGTH(name) 返回区域长度,使脚本具备平台无关性:
MEMORY {
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128K
}
_stack_top = ORIGIN(RAM) + LENGTH(RAM);
这种写法避免硬编码地址,提高可移植性。
段边界与加载地址控制依赖 ADDR(section) 与 LOADADDR(section)。前者返回运行地址(VMA),后者返回加载地址(LMA),在区分 AT> 布局时尤为重要。例如:
.data : AT(ADDR(.text) + SIZEOF(.text)) {
*(.data)
}
此外,SIZEOF(section) 与 SIZEOF_HEADERS 用于获取段大小与 ELF 头部大小,常用于生成启动代码所需的边界符号。
符号判定类函数如 DEFINED(symbol) 与 PROVIDE(symbol = expr) 提供弱定义机制。当用户代码已定义符号时不覆盖,否则提供默认值:
PROVIDE(_heap_start = .);
这在构建通用运行时框架时非常关键,可避免重复定义错误,同时允许上层覆盖默认布局。

Once Day
也信美人终作土,不堪幽梦太匆匆……
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注!
(。◕‿◕。)感谢您的阅读与支持~~~
网硕互联帮助中心





评论前必须登录!
注册