链接脚本

概述

链接脚本(Linker Script)是用于指导链接器(Linker)如何将程序的不同部分(如代码、数据、库等)组织到最终可执行文件或库文件中的文件。链接器在编译过程的最后阶段运行,负责将各个目标文件(.o 文件)和库文件链接在一起,生成最终的可执行文件。链接脚本提供了对最终生成文件布局的精细控制。

基本结构

链接脚本通常包含以下几个部分:

OUTPUT_FORMAT

指定输出文件的格式,如 ELF、COFF 等。

ENTRY

指定程序的入口点,通常是程序的起始函数,如 main 或 _start。

SECTIONS

定义输出文件的内存布局,包括各个段的名称、属性和内容。
SECTIONS 部分是链接脚本的核心,它定义了程序的内存布局。以下是一些基本的语法和要素:

段名

在链接脚本(Linker Script)的 SECTIONS 部分中,段名(section name)用于标识和定义程序的不同内存区域。这些段名通常遵循一定的命名约定,并且与程序的内存布局和访问要求密切相关。以下是一些常见的段名及其用途:

  1. .text

    • 用途:包含程序的执行代码,即机器指令。
    • 特点:通常具有只读属性,并且需要按照特定的对齐要求(如4字节或8字节对齐)来放置。
  2. .data

    • 用途:包含已初始化的全局和静态变量。
    • 特点:在程序启动时,这些变量会被赋予在编译时确定的值,并且需要存储在可读写的内存区域中。
  3. .bss

    • 用途:包含未初始化的全局和静态变量。
    • 特点:这些变量在程序加载时会被自动清零(或保持未初始化状态,具体取决于实现),并且通常也需要存储在可读写的内存区域中。.bss 段不占用磁盘空间,因为它只包含变量的长度信息,而不包含实际的数据内容。
  4. .rodata

    • 用途:包含只读数据,如常量字符串、常量数值等。
    • 特点:这些数据在程序运行期间不会被修改,因此可以存储在只读内存区域中以节省空间。
  5. .idata(或类似名称,如 .idata$.idata$2 等,具体取决于编译器和链接器):

    • 用途:保存导入函数和库的相关信息,用于在程序运行时链接外部模块。
    • 特点:这个段在动态链接的上下文中尤为重要,因为它包含了动态链接所需的所有符号和重定位信息。
  6. .edata(或类似名称):

    • 用途:包含导出函数和变量的地址信息,以便其他程序可以调用。
    • 特点:这个段通常与 .idata 段一起使用,在动态链接的上下文中提供导出符号的信息。
  7. .rsrc(或类似名称,如 .rsrc$):

    • 用途:存储程序所使用的各种资源,如图标、位图、菜单、对话框等。
    • 特点:这些资源通常是嵌入在可执行文件中的,并且在程序运行时被加载和显示。
  8. .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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
OUTPUT_FORMAT("elf64-x86-64")
ENTRY(main)

SECTIONS
{
. = 0x10000; /* 设置当前位置(即段的起始地址)为 0x10000 */

.text : { /* 代码段 */
*(.text) /* 包含所有输入文件的 .text 段 */
} >FLASH /* 指定段应该放置在 FLASH 内存中 */

.data : { /* 数据段 */
*(.data) /* 包含所有输入文件的 .data 段 */
} >SRAM AT >FLASH /* 指定段应该放置在 SRAM 中,但加载到 FLASH 中 */

_edata = .; /* 数据段结束地址 */

.bss : { /* 未初始化数据段 */
*(.bss) /* 包含所有输入文件的 .bss 段 */
} >SRAM /* 指定段应该放置在 SRAM 中 */

_end = .; /* 最终结束地址 */

/DISCARD/ : { /* 丢弃的段 */
*(.note.GNU-stack) /* 丢弃 GNU 栈注记段 */
*(.comment) /* 丢弃注释段 */
}

FLASH : ORIGIN = 0x08000000, LENGTH = 256K /* FLASH 内存区域 */
SRAM : ORIGIN = 0x20000000, LENGTH = 64K /* SRAM 内存区域 */
}

链接脚本的使用

在编译和链接程序时,可以通过 -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

链接脚本为开发者提供了对程序内存布局的完全控制,这在嵌入式系统开发中尤其重要,因为嵌入式系统的资源通常有限,并且需要精确控制程序的内存布局。