Linker Scripts

Linker Scripts

GNU ld 支持通过使用链接器脚本来配置链接过程。链接器脚本使用特定于 GNU ld 应用程序的专用脚本语言编写。虽然 ld 并不严格要求使用链接器脚本文件,但它提供了一种更易于维护的替代方法,可以替代在命令行上将复杂的配置选项指定为参数。

链接器脚本的主要用途是指定最终可执行二进制文件的格式和布局。这与操作系统开发特别相关,因为可执行二进制文件通常需要特定的文件布局才能被某些引导加载程序识别。GNU GRUB 就是这样一种引导加载程序。

调用 ld 应用程序时,通常通过使用 -T script.ld 命令行参数来调用链接器脚本。

Keywords

下面列出了链接器脚本中使用的一些重要关键字。

ENTRY

ENTRY(main)
ENTRY(MultibootEntry)

ENTRY 关键字用于定义应用程序的入口点,即输出文件中的第一个可执行指令。
  • 此关键字接受链接程序/内核入口点的符号名称作为单个参数。
  • 提供的符号名称指向的代码将是 ELF 和 PE 二进制文件中 .text 部分的第一个字节。

OUTPUT_FORMAT

OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_FORMAT("pe-i386")

OUTPUT_FORMAT 指令采用单个参数。

  • 它指定可执行文件的输出格式。
  • 要找出系统的 binutils 和 GCC 支持哪些输出格式,可以使用 <code="bash">objdump -i 命令。


下面详细介绍了一些常用的格式:



STARTUP

STARTUP(Boot.o)
STARTUP(crt0.o)

STARTUP 接受一个参数。它是您想要链接到可执行文件最开始位置的文件。
  • 对于用户空间程序,这通常是 crt0.o 或 crtbegin.o。
  • 对于内核,它通常是包含您的汇编样板的文件,用于启动堆栈,在某些情况下启动 GDT 等,然后调用您的 kmain()。

SEARCH_DIR

SEARCH_DIR(Directory)

这将向您的库搜索目录添加一个路径。
  • -nostdlib 标志将导致在此路径中找到的任何库都被忽略。我不确定为什么,这似乎是 ld 的工作方式。
  • 它将链接器脚本指定的搜索目录视为标准目录,因此使用 -no-default-libs 和此类标志忽略它们

INPUT

INPUT(File1.o File2.o File3.o ...)
INPUT
(
	File1.o
	File2.o
	File3.o
	...
)

INPUT 是“链接器(中)脚本”的替代品,用于将目标文件添加到命令行。您通常会指定类似“ld File1.o File2.o”的内容,但 INPUT 部分可用于在链接器脚本中执行此操作。

OUTPUT

OUTPUT(Kernel.bin)

OUTPUT 命令指定将作为链接过程的输出结果而被生成的文件。这是最终创建的二进制文件的名称。此命令的效果与 -o filename 命令行标志的效果相同,后者会覆盖它。

MEMORY

MEMORY
{
    ROM (rx) : ORIGIN = 0, LENGTH = 256k
    RAM (wx) : org = 0x00100000, len = 1M
}

MEMORY 声明一个或多个内存区域,并带有指定该区域是否可以写入、读取或执行的属性。这主要用于嵌入式系统,因为其中地址空间的不同区域可能包含不同的访问权限。

上面的示例脚本告诉链接器有两个内存区域:
  • “ROM”从地址 0x00000000 开始,长度为 256kB,可以读取和执行。
  • “RAM”从地址 0x00100000 开始,长度为 1MB,可以写入、读取和执行。

SECTIONS

SECTIONS
{
  .text.start (_KERNEL_BASE_) : {
    startup.o( .text )
  }

  .text : ALIGN(CONSTANT(MAXPAGESIZE)) {
_TEXT_START_ = .;
    *(.text)
_TEXT_END_ = .;
  }

  .data : ALIGN(CONSTANT(MAXPAGESIZE)) {
_DATA_START_ = .;
    *(.data)
_DATA_END_ = .;
  }

  .bss : ALIGN(CONSTANT(MAXPAGESIZE)) {
_BSS_START_ = .;
    *(.bss)
_BSS_END_ = .;
  }
}

此脚本告诉链接器将 startup.o 中“.text”部分的代码放在开头,从逻辑地址 _KERNEL_BASE_ 开始。然后是所有页大小对齐的其他输入文件的所有“.text”、“.data”和“.bss”部分。如果您的部分未正确进行页面对齐和分隔,GNU ld 将生成一个“具有 RWX 权限的 LOAD 段”警告。


注意,不同的架构有不同的页面大小。您可以通过向链接器添加“-z max-page-size=0x1000”开关来修改“MAXPAGESIZE”的大小。此行为仅在 GNU ld 上存在。


链接器符号被定义为保存每个部分的起始和结束地址。这些符号在应用程序中具有外部链接,并可作为指针在代码中被访问。有关链接器文件符号的更多信息,请参见下文。


KEEP

链接器脚本中的 KEEP 语句将指示链接器保留指定的部分,即使其中没​​有包含任何被引用的符号。此语句在链接器脚本的 SECTIONS 部分中使用。当在链接时执行垃圾收集时,这将变得相关,通过将 --gc-sections 开关传递给链接器来启用垃圾收集。KEEP 语句指示链接器在创建依赖关系图时使用指定的部分作为根节点,查找未使用的节。本质上是强制将该部分被标记为已使用。

此语句通常出现在针对 ARM 架构的链接器脚本中,用于将中断向量表放置在偏移量 0x00000000 处。如果没有此指令,则代码中可能未明确引用该表,这将导致该表被删除。

SECTIONS
{
	.text :
	{
		KEEP(*(.text.ivt))
		*(.text.boot)
		*(.text*)
	} > ROM

	/** ... **/
}

Symbols

可以在链接器脚本中定义任意符号。这些符号将被添加到程序的符号表中。表中的每个符号都有一个名称和一个关联的地址。链接器脚本中已赋值的符号将被赋予外部链接,并可在程序代码中作为指针被访问。

下面的示例脚本展示了可以使用符号赋值的三个不同的位置:


floating_point = 0;
SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
    }
  _bdata = (. + 3) & ~ 3;
  .data : { *(.data) }
}

在上面的例子中:

  • 符号 floating_point 被定义为零。
  • 符号 _etext 被定义为最后一个 .text 输入节后面的地址。
  • 符号 _bdata 被定义为,向上对齐到 4 字节边界的, .text 输出节后面的,地址。


下面是 C 代码中这些符号的使用示例:

/** Externally linked symbol */
extern uintptr_t _etext;
// ...
/** Pointer to the binary data at the address stored in the symbol expression. */
uint32_t* item = &_etext;


评论

此博客中的热门博文

ISO 14229-1-2020

AUTOSAR_SWS_CANDriver

Linux Driver Char Device 笔记

AUTOSAR_SWS_PWMDriver

AUTOSAR_SWS_PortDriver

AUTOSAR_SWS_ECUStateManager

EB - MCAL - MCU

AUTOSAR_SWS_ICUDriver

EB - MCAL - PWM