0%

Linker 及 ld 脚本分析

链接器工作过程及链接脚本文件 .lds 分析

Linker

链接器将多目标文件生成最终可执行文件,示意图如下

Linker Role

在链接过程中执行如下操作

  1. Symbol Resolution - 符号解析
    • 多文件声明与调用
    • 调用替换为具体函数入口
  2. Relocation - 重定位
    • 相同段合并,例如 .text
    • Section Placement, 各段在内存中的位置

详细解释见 Linker

Linker Script File

控制链接器行为的脚本文件,一般后缀为 .lds。如果不指定,存在默认脚本,使用 ld --verbose 查看

Linker Script File

SECTIONS { ❶
        . = 0x00000000; ❷
        .text : { ❸
                abc.o (.text);
                def.o (.text);
        }
}
  1. SECTIONS 脚本各段组成及位置
  2. . 定位器符号
  3. .textabc.odef.o 中的 .text 组成

LMA vs VMA

  • LMA (load memory address 加载内存地址或进程地址空间地址)
  • VMA (virtual memory address 虚拟内存地址或程序地址空间地址)

VMA 是执行输出文件时 section 所在的地址,而 LMA 是加载输出文件时 section 所在的地址。一般而言,某 section 的 VMA == LMA. 但在嵌入式系统中,经常存在加载地址和执行地址不同的情况:比如将输出文件加载到开发板的 flash 中(由 LMA 指定), 而在运行时将位于 flash 中的输出文件复制到 SDRAM 中(由 VMA 指定)。

SECTIONS 命令

SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section: 如何将输入 section 合为输出 section; 如何把输出 section 放入 VMA 和 LMA

SECTIONS
{
    SECTIONS-COMMAND
    SECTIONS-COMMAND
    ...
}

SECTION-COMMAND 有四种:

  1. ENTRY 命令
  2. 符号赋值语句
  3. 一个输出 section 的描述 (output section description)
  4. 一个 section 叠加描述 (overlay description)

ENTRY 命令

将符号 SYMBOL 的值设置成入口地址

ENTRY(SYMBOL)

符号赋值

  • 对符号的赋值只对全局变量起作用
  • 赋值语句包含 4 个语法元素:符号名、操作符、表达式、分号;一个也不能少
  • . 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置。该符号只能在 SECTIONS 命令内使用

叠加描述

覆盖图描述使两个或多个不同的 section 占用同一块程序地址空间。覆盖图管理代码负责将 section 的拷入和拷出。

输出描述

格式如下

SECTION-NAME [ADDRESS] [(TYPE)] : [AT(LMA)]
{
    OUTPUT-SECTION-COMMAND
    OUTPUT-SECTION-COMMAND
    …
} [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
  • ALIGN 地址对齐
  • 文件通配符
  • . 定位符
  • KEEP
  • AT 修改 LMA
  • >REGION 修改 VMA

MEMORY 命令

在默认情形下,连接器可以为 section 在程序地址空间内分配任意位置的存储区域。并通过输出 section 描述的 >REGION 属性显示地将该输出 section 限定于在程序地址空间内的某块存储区域,当存储区域大小不能满足要求时,连接器会报告该错误。

MEMORY
{
    NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
    NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
    ...
}

Example

MEMORY
{
    sram  :   ORIGIN = 0x00100000,    LENGTH = 0x8000
    ddram :   ORIGIN = 0x80000000,    LENGTH = 0x100000
}

ENTRY(ResetEntry)
SECTIONS {
    . = ALIGN(4);
    .text :
    {
        sram/*(.text*)
        sram/*(.data*)
        sram/*(.rodata*)
        *(.reset_patch)
        *(.sram_text)
        *(.sram)
    } > sram

    . = ALIGN(0x8000);
    .data :
    {
        *(.text*)
        . = ALIGN(4);
        *(.data*)
        *(.rodata*)
        . = ALIGN(16);
    } > ddram

    . = ALIGN(4);
    .bss :
    {
        . = ALIGN(4);
        __bss_start__ = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        __bss_end__ = .;
    } > ddram
}

Ref

  1. Linux 下的 lds 链接脚本详解