链接脚本
链接脚本
概述
链接脚本(Linker Script)是用于指导链接器(Linker)如何将程序的不同部分(如代码、数据、库等)组织到最终可执行文件或库文件中的文件。链接器在编译过程的最后阶段运行,负责将各个目标文件(.o 文件)和库文件链接在一起,生成最终的可执行文件。链接脚本提供了对最终生成文件布局的精细控制。
基本结构
链接脚本通常包含以下几个部分:
OUTPUT_FORMAT
指定输出文件的格式,如 ELF、COFF 等。
ENTRY
指定程序的入口点,通常是程序的起始函数,如 main 或 _start。
SECTIONS
定义输出文件的内存布局,包括各个段的名称、属性和内容。
SECTIONS 部分是链接脚本的核心,它定义了程序的内存布局。以下是一些基本的语法和要素:
段名
在链接脚本(Linker Script)的 SECTIONS
部分中,段名(section name)用于标识和定义程序的不同内存区域。这些段名通常遵循一定的命名约定,并且与程序的内存布局和访问要求密切相关。以下是一些常见的段名及其用途:
.text:
- 用途:包含程序的执行代码,即机器指令。
- 特点:通常具有只读属性,并且需要按照特定的对齐要求(如4字节或8字节对齐)来放置。
.data:
- 用途:包含已初始化的全局和静态变量。
- 特点:在程序启动时,这些变量会被赋予在编译时确定的值,并且需要存储在可读写的内存区域中。
.bss:
- 用途:包含未初始化的全局和静态变量。
- 特点:这些变量在程序加载时会被自动清零(或保持未初始化状态,具体取决于实现),并且通常也需要存储在可读写的内存区域中。
.bss
段不占用磁盘空间,因为它只包含变量的长度信息,而不包含实际的数据内容。
.rodata:
- 用途:包含只读数据,如常量字符串、常量数值等。
- 特点:这些数据在程序运行期间不会被修改,因此可以存储在只读内存区域中以节省空间。
.idata(或类似名称,如
.idata$
、.idata$2
等,具体取决于编译器和链接器):- 用途:保存导入函数和库的相关信息,用于在程序运行时链接外部模块。
- 特点:这个段在动态链接的上下文中尤为重要,因为它包含了动态链接所需的所有符号和重定位信息。
.edata(或类似名称):
- 用途:包含导出函数和变量的地址信息,以便其他程序可以调用。
- 特点:这个段通常与
.idata
段一起使用,在动态链接的上下文中提供导出符号的信息。
.rsrc(或类似名称,如
.rsrc$
):- 用途:存储程序所使用的各种资源,如图标、位图、菜单、对话框等。
- 特点:这些资源通常是嵌入在可执行文件中的,并且在程序运行时被加载和显示。
.debug:
- 用途:包含调试相关的信息,如符号表、源代码行号等。
- 特点:这些信息对于调试程序非常有用,但在发布版本中通常会被去除以减少文件大小和提高安全性。
地址
可以使用 . = 地址 来设置当前段的起始地址。地址可以是绝对地址,也可以是相对于某个基地址的偏移量。
内容
使用花括号 {} 来包含段的内容。内容可以是具体的目标文件(如 start.o),也可以是通配符表达式(如 *(.text)),表示包含所有输入文件的指定段。
属性
可以为段指定一些属性,如对齐要求(ALIGN(n))、加载地址和运行地址(>region AT >load_region)等。
符号
可以在段中定义符号,用于在程序中引用段的起始地址或结束地址。
__bss_start
和__bss_end
可以用于定位.bss
段的边界。
==注意:对于32位的SOC来说,CPU都是以4字节对齐来进行访问的。所以__bss_start和__bss_end的地址一定要是4字节对齐的。==
基本语法
1 | OUTPUT_FORMAT("elf64-x86-64") |
链接脚本的使用
在编译和链接程序时,可以通过 -T 选项指定链接脚本。例如:
1 | ld -T my_linker_script.ld -o my_program my_program.o |
或者在使用 GCC 时:
1 | gcc -o my_program my_program.c -Wl,-T,my_linker_script.ld |
链接脚本为开发者提供了对程序内存布局的完全控制,这在嵌入式系统开发中尤其重要,因为嵌入式系统的资源通常有限,并且需要精确控制程序的内存布局。