链接脚本基础语法

目录

前言

ELF文件布局

链接脚本语法

段定义标准格式

[地址计数器 .](#地址计数器 .)

地址计数器的动态特性

[赋值 vs 引用](#赋值 vs 引用)

符号定义

通配符规则

COMMON块

[COMMON 块的产生与处理](#COMMON 块的产生与处理)

示例脚本


前言

由于嵌入式系统内存资源珍贵,链接脚本可指定代码段(.text )、只读数据段(.rodata) 、数据段(.data )、未初始化数据段(.bss )等在内存中的存储位置以实现内存布局管控;

链接脚本将多个目标文件(.o )中相同类型的段(如 .text 代码段、.data 已初始化数据段、.bss 未初始化数据段 )合并,构建统一可执行文件;

链接脚本可以控制程序入口地址,定义初始化数据在启动时从 Flash 到 RAM 的搬移规则

ELF文件布局

复制代码
代码段(.text): 存储可执行的机器指令

数据段(.data): 存储已初始化的全局变量和静态变量

只读数据段(.rodata): 存储不可修改的数据,比如字符串常量 const修饰的全局变量 常量

BSS 段(.bss): 记录未初始化的全局变量和静态变量,不占用文件空间

链接脚本语法

bash 复制代码
SECTIONS {
    // 定义各个输出段及其内存布局
}
  • **SECTIONS**是链接脚本的关键字,表示开始定义各个段的内存布局;
  • 大括号 {} 内包含所有段的配置信息;

段定义标准格式

bash 复制代码
段名 [地址] : { 段内容 }
  • 段名 :比如 .text.data.rodata.bss等,标识段的类型;
  • 地址 :可选参数,指定具体段的起始地址(如 0x80000000);
  • 冒号(::必须存在,用于分隔 "段名 + 地址" 和 "段内容",是语法结构的核心标识;
  • 段内容 :用大括号**{}** 包裹,定义哪些输入段需要合并到当前段中(如 *(.text*));
cpp 复制代码
/* 示例脚本 */
SECTIONS {
    .text 0x08000000 : { 
        *(.text*)
    }
}

以点开头的段名被定义为系统保留段,以点开头的段名由编译器和链接器 "预留",链接器会默认将以点开头的段名视为标准段,并应用特定的处理逻辑,如果省略点,可能导致链接器无法正确识别段的类型,用户自定义的段通常不以点开头 (如my_section),从而避免与系统段名冲突;

bash 复制代码
my_section : { *(.my_section*) }   // 用户自定义段名

地址计数器 .

bash 复制代码
. = 0x08000000; /* 设置当前地址计数器为 0x08000000 */
.text : {
  *(.text*) /* 所有 .text 输入段从这里开始放置 */
}
/* 链接器自动增加 . 的值到 .text 段末尾 */

地址计数器 . 的本质为变量,其值表示下一个将要被分配的地址 (即下一个空闲地址),而非当前已使用的地址,地址计数器**.始终指向尚未分配** 的内存位置;地址计数器决定当前正在描述的输出段会被放置到哪个具体地址;

地址计数器的动态特性

地址计数器 **.**的值在链接脚本执行过程中会自动变化:

  • 隐式增长: 当你将一个输入段(比如 .text, .data)放入输出段时,链接器会自动将 . 的值增加该输入段的大小,指向该段之后的下一个可用地址;

  • 显式设置: 可以通过赋值语句 直接设置 . 的值(例如 . = 0x08000000;),强制将后续内容定位到特定地址;

赋值 vs 引用

cpp 复制代码
. = expression; /* 赋值操作,直接设置 . 的值 */

symbol = .; /* 定义符号 symbol 并将其值设置为当前 . 的值, 引用不会改变 . 本身 */

符号定义

复制代码
symbol = expression; 
  • symbol 自定义的全局符号名称(通常以___开头避免冲突);

  • expression 基于地址计数器.、内存区域或其它符号的表达式;

cpp 复制代码
@ 链接脚本文件
.data 0x80004000 : {
    __data_start = .;  @ 记录数据段起始地址
    *(.data*)          @ 合并所有.data*段( 非语句,无分号)
    __data_end = .;    @ 记录数据段结束地址
}

链接脚本中定义的符号为强符号(Strong Symbol),不会被其他目标文件中的同名弱符号(Weak Symbol)覆盖;

链接脚本中定义的符号(__data_start 、__data_end)具有全局属性,可在C/C++代码中用extern声明或在汇编文件中直接使用;

bash 复制代码
@ ARM汇编文件
ldr r0, =__data_start  @ 将链接脚本符号作为立即数加载
bash 复制代码
汇编器处理机制

1. 符号引用: 汇编器遇到 __data_start 时,会将其视为未解析的符号;

2. 生成重定位信息: 在目标文件中记录该符号需要链接器解析;

3. 链接器解析: 链接阶段,链接器将 __start_start 替换为实际地址值;

链接脚本中定义的符号本身不携带类型信息,在 C/C++ 和汇编文件中,开发者可以通过选择适当的类型声明来安全地使用这些符号;

cpp 复制代码
extern uint8_t __data_start; 
extern void*   __data_start;     

通配符规则

星号 *:匹配任意字符序列

  • 作用:代替 "任意长度的任意字符",用于匹配不确定的段名前缀、后缀或中间部分。

  • 示例

    • *(.text):匹配所有输入文件 中名称以 .text 开头的段(如 .text.text.init.text.fini 等子段 ),常用于合并所有代码段。
    • *.o(.data):匹配所有文件名以 .o 结尾的目标文件 中的 .data 段,精准筛选特定文件的段
      注意:
  • "动" 则加分号 :凡是改变地址(. = ...)或定义符号(__symbol = ...)的 "动作",末尾必须加分号;

  • "静" 则无分号 :单纯 "选择段"(*(.text*))或 "引用内容" 的表达式,无需分号;

COMMON块

bash 复制代码
.bss : {
    __bss_start = .;
    *(.bss*)     @ 合并目标文件中的 .bss 段(已分配空间的未初始化变量)
    *(COMMON)    @ 合并目标文件中的 COMMON 块(未分配空间的未初始化变量)
    __bss_end = .;
}

早期 C 语言编译器中,对于 未初始化的全局变量 ,编译器不会为其分配固定大小的内存空间,而是将其标记为一个 "未定义的符号" ,并记录其类型和期望的大小,此类符号在目标文件(.o文件)中会被组织成 COMMON 块

COMMON 块的产生与处理

  1. 示例程序
cpp 复制代码
// file1.c
int global_var;  // 未初始化全局变量,生成 COMMON 块
cpp 复制代码
// file2.c
int global_var;  // 同名未初始化全局变量,另一个 COMMON 块
  1. 编译后的目标文件
  • file1.ofile2.o 中各有一个 COMMON 块,只记录 global_varint 类型(4 字节);
  • 每个目标文件中的 COMMON块不占用实际空间,仅记录符号信息
  1. 链接时的处理
  • 链接器遇到 *(COMMON) 时,将两个目标文件的 COMMON 块合并,为 global_var 分配 4 字节空间,放入 .bss 段;

  • 最终 .bss 段中只有一个 global_var 变量,避免重复定义错误;
    注意:

  • 如果省略 *(COMMON),可能导致未初始化变量无法被正确处理,所以链接脚本中必须写 *(COMMON);

  • 为确保完整收集不同编译器生成的未初始化变量,避免因遗漏导致的链接错误或运行时内存错误,因此在链接脚本的 .bss 段中必须同时合并 .bss*COMMON

示例脚本

bash 复制代码
SECTIONS {
    .text 0x80000000 : {
        *(.text*)
    }

    .rodata : {
        *(.rodata*)
    }

    .data 0x80004000 : {
        __data_start = .;
        *(.data*)
        __data_end = .;
    }

    .bss : {
        __bss_start = .;
        *(.bss*)
        *(COMMON)
        __bss_end = .;
    }

    .stack 0x80200000 : {
        . = . + 0x40000;  @ 分配256KB栈空间
        __stack_top = .;  @ 栈顶地址
    }
}
相关推荐
大树883 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz3 小时前
Maven依赖冲突
java·服务器·maven
不会C语言的男孩5 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈5 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix
程序猿阿伟6 小时前
《Chrome离线扩展安装的底层逻辑与场景落地指南》
服务器·网络·chrome
凡人叶枫6 小时前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
AC赳赳老秦6 小时前
用 OpenClaw 搭建服务器故障应急响应系统,自动处理 80% 常见运维故障
android·运维·服务器·python·rxjava·deepseek·openclaw