目录


链接脚本原理

每一个链接过程都由链接脚本 (linker script,一般以 lds 作为文件的后缀名) 控制。 链接脚本主要用于规定如何把输入文件内的 section 放入输出文件内,并控制输出文件 内容各部分在程序地址空间内布局。链接器有个默认的内置链接脚本,可以使用 ld -verbose 查看。ld 链接选项 -r 和 -N 可以影响默认的链接脚本,-T 选项用以指定 特定的链接脚本,它将代替默认的链接脚本。也可以使用暗含的链接脚本以增加自定义的 链接命令。

基本概念

链接器把一个或多个输入文件合成一个输出文件。

1. 输入文件指的是目标文件或链接脚本文件
2. 输出文件指的是目标文件或可执行文件。

目标文件 (包括可执行文件) 具有固定的格式,在 UNIX 或者 GNU/Linux 平台下,一般 为 ELF 格式。把输入文件的 section 称为输入 section (input section),把输出文件 内的 section 称为输出 section (output section)。

目标文件的每个 section 至少包含两个信息:名字和大小。大部分 section 还包含于它 相关联的一块数据,称为 section contents (section 内容)。 一个 section可被标记 为 loadable (可加载的) 或 allocatable (可分配的)。

loadable section:
     在输出文件运行时,相应的 section 内容将被载入进程的虚拟地址空间

allocatable section:
     内容为空的 section 可标记为 "可分配的"。在输出文件运行时,在进程
     虚拟地址空间中腾出大小同 section 指定大小的部分。某些情况下,这块
     内存必须清零。

如果一个 section 不是 “可加载的”或 “可分配的”,那么该 section 通常包含调试信 息,可用 objdump -h 命令查看相关信息。每个“可加载的” 或 “可分配的” 输出 section 通常包含两个地址:

VMA
     Virtual memory address 虚拟内存地址或程序地址空间地址
LMA
     Load memory address 加载内存地址或进程地址空间地址

通常 VMA 和 LMA 是相同的。在目标文件中, loadablle 或 allocatable 的输出 section 有两种地址: VMA 和 LMA。VMA 是执行输出文件时 section 所在的地址,而 LMA 是加载输出文件时 section 所在的地址。一般而言,某 section 的 VMA == LMA。 但在嵌入式系统中,经常存在加载地址和执行地址不同的情况:

1. 比如将输出文件加载到开发板的 flash 中 (由 LMA 指定), 而运行时将位于 flash 
2. 中的输出文件复制到 SDRAM 中 (由 VMA 指定)

符号 (Symobl): 每个目标文件都有符号表 (SYMBOL TABLE),包含已定义的符号 (对 应全局变量和 static 变量和定义的函数名字) 和未定义符号 (未定义的函数的名字和引 用但没定义的符号) 信息。

符号值: 每个符号对应一个地址。即符号指 (这与 C 程序内变量的值不一样,某种 情况下可以堪称变量地址) 可以使用 nm 命令查看


链接脚本最小实践


链接脚本赋值语句

表达式的语法和 C 语言的表达式语法一样,表达式的值都是整形,如果 ld 的运行主机 和生成文件的目标机都是 32 位,则表达式是 32 位数据,否则是 64 位数据。能够在表 达式内使用的值,设置符号的值。表达式格式如下:

SYMBOL = EXPRESSION
SYMOBL += EXPRESSION
SYMBOL -= EXPRESSION
SYMBOL *= EXPRESSION
SYMBOL /= EXPRESSION
SYMBOL <<= EXPRESSION
SYMBOL >>= EXPRESSION
SYMBOL &= EXPRESSION
SYMBOL |= EXPRESSION

除第一类表达式外,使用其他表达式需要 SYMBOL 被定义与目标文件。

. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间某位置 (或 某 section 内的偏移,如果它在 SECTIONS 命令内的某 section 描述内),该符号只能 在 SECTIONS 命令内使用。

注意:赋值语句包含 4 个语法元素:符号名操作符表达式分号, 一个也不能少。被赋值后,符号所属的 section 被设置为表达式 EXPRESSION 所属的 SECTION。赋值语句可以出现在链接脚本的三个地方:

1. SECTIONS 命令内
2. SECTIONS 命令内的 section 描述内
3. 全局位置

一个简单的例子

floating_point = 0;   /* 全局位置 */
SECTIONS
{
    DemoText : 
    {
        *(.text)
        _etext = .; /* section 描述内 */
    }

    _bdata = (. + 3) & ~ 4; /* SECTIONS 命令内 */

    .data : { *(.data) }

}

操作符优先级
1. left ! - ~ (1)
2. left * / %
3. left + -
4. left >> <<
5. left == != > < <= >=
6. left &
7. left |
8. left &&
9. left ||
10. right ?:
11. right &= += -= *= /= (2)

(1) 表示前缀符 (2) 表示后缀符

表达式计算

链接器延迟计算大部分表示计算,但是对待与连接过程紧密相关的表达式,链接器会立即 计算表达式。如果不能计算就报错。比如,对于 section 的 VMA 地址,内核区域块的开 始地址和大小,与其相关的表达式应该立即被计算。


一个简单的例子
SECTIONS
{
    DemoText 9+DEMO_ADDR : { *(.text) }
}

这个例子中,9+DEMO_ADDR 表达式的值用于设置 DemoText section 的 VMA 地址,因此 需要立即运算,但是由于 DEMO_ADDR 变量的值不确定,所以此时链接器无法确立表达式 的值,因此链接器会报错。


相对值和绝对值

在输出 section 描述符内的表达式,链接器取其相对值,相对于该 section 的开始位置 的偏移。在 SECTIONS 命令内且非输入 section 描述内的表达式,链接器去其绝对值。 通过 ABSOLUTE 关键字转换成绝对值,即在原来值的基础上加上表达式所的 section 的 VMA 值。

一个简单例子

SECTIONS
{
    .data : { *(.data) _edata = ABSOLUTE(.); }
}

这个例子中, _edata 符号的值是 .data section 的末尾值(绝对值,在程序地址空间 内)

链接脚本相关的内建函数:

1. ABSOLUTE(EXP): 转换成绝对值
2. ADDR(SECTION): 返回某 section 的 VMA 值
3. ALIGN(EXP): 返回定位符 . 的修调值,对齐后的值,(. + EXP - 1) & ~(EXP - 1)
4. BLOCK(EXP): 如同 ALIGN(EXP),为了向前兼容
5. DEFINED(SYMBOL): 如果符号 SYMBOL 在全局符号表内,且被定义了,那么返回 1,
   否则返回 0
6. LOADADDR(SECTION): 返回某 SECTION 的 LMA
7. MAX(EXP1, EXP2): 返回大者
8. MIN(EXP1,EXP2): 返回小者
9. NEXT(EXP): 返回下一个能被使用的地址,该地址是 EXP 的倍数,类似于
   ALIGN(EXP)。除非使用 MEMORY 命令定义了一些非连续的内存块,否则 NEXT(EXP)
   与 ALIGN(一定相同)
10. SIZEOF(SECTION): 返回 SECTION 的大小。当 SECTION 没有被分配时,即此时
    SECTION 大小不能确定,链接器会报错,
11. SIZEOF_HEADERS: 返回输出文件的文件头大小,用以确定第一个 section 的开始
    地址

具体实践请看:


链接脚本语法

ABSOLUTE

ADDR

ALIGN

ASSERT

AT

BLOCK

BYTE

COMMONPAGESIZE

CONSTANT

CREATE_OBJECT_SYMBOLS

DATA_SEGMENT_ALIGN

DATA_SEGMENT_END

DATA_SEGMENT_RELRO_END

DEFINED

DISCARD

ENTRY

EXCLUDE_FILE

EXTERN

FILL

GROUP

HIDDEN

INCLUDE

INPUT

KEEP

LOADADDR

LONG

MAX

MAXPAGESIZE

MEMORY

MIN

NEXT

NOCROSSREFS

ONLY_IF_RO

ONLY_IF_RW

OUTPUT

OUTPUT_ARCH

OUTPUT_FORMAT

OUTPUT_FORMAT(3)

OVERLAY

PHDRS

PROVIDE

PROVIDE_HIDDEN

QUAD

SEARCH_DIR

SECTIONS

SENGMENT_START

SHORT

SIZEOF(SECTION)

SIZEOF_HEADERS

SORT

SORT_NONE

SQUAD

STARTUP

TARGET


附录

linux 中链接脚本 ld 文件详解

LD scripts

LD scripts China

LD Usermanual

BiscuitOS Home

BiscuitOS Blog

Linux Kernel

Bootlin: Elixir Cross Referencer

赞赏一下吧 🙂

MMU