ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种。完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起来,并重定向它们的数据,完成符号解析.Linking其实主要就是完成四个方面的工作:

  • storage allocation: 存储分配
  • symbol management: 符号管理
  • libraries: 库
  • relocation: 重定位

ld可以识别一种Linker command Language表示的linker scriopt文件来显式的控制链接的过程。通过BFD(Binary Format Description)库,ld可以读取和操作COFF(common object file format)、ELF(executable and linking format)、a.out等各种格式的目标文件。

ld 脚本的理解

下面为mit jos的内核ld脚本.

/* Simple linker script for the JOS kernel.
   See the GNU ld 'info' manual ("info ld") to learn the syntax. */

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)

SECTIONS
{
	/* Link the kernel at this address: "." means the current address */
	. = 0xF0100000;

	/* AT(...) gives the load address of this section, which tells
	   the boot loader where to load the kernel in physical memory */
	.text : AT(0x100000) {
		*(.text .stub .text.* .gnu.linkonce.t.*)
	}

	PROVIDE(etext = .);	/* Define the 'etext' symbol to this value */

	.rodata : {
		*(.rodata .rodata.* .gnu.linkonce.r.*)
	}

	/* Include debugging information in kernel memory */
	.stab : {
		PROVIDE(__STAB_BEGIN__ = .);
		*(.stab);
		PROVIDE(__STAB_END__ = .);
		BYTE(0)		/* Force the linker to allocate space
				   for this section */
	}

	.stabstr : {
		PROVIDE(__STABSTR_BEGIN__ = .);
		*(.stabstr);
		PROVIDE(__STABSTR_END__ = .);
		BYTE(0)		/* Force the linker to allocate space
				   for this section */
	}

	/* Adjust the address for the data segment to the next page */
	. = ALIGN(0x1000);

	/* The data segment */
	.data : {
		*(.data)
	}

	PROVIDE(edata = .);

	.bss : {
		*(.bss)
	}

	PROVIDE(end = .);

	/DISCARD/ : {
		*(.eh_frame .note.GNU-stack)
	}
}

接下来进行一一解释.

该文件涉及如下几个命令:

  • OUTPUT_FORMAT:指定输出文件的格式
  • OUTPUT_ARCH:指定输出的架构
  • ENTRY:指定入口地址,注意这里使用的是代码中定义的_start符号,也就是说脚本中可以直接访问符号表中的符号
  • SECTIONS:用来指定如何将输入文件映射到输出文件等等

SECTION是脚本中最重要的命令了,所有的LD脚本都会有这个命令,要看懂SECTIONS的内容需要许多概念,下面来一一说明

SECTIONS命令中,".“是一个特殊的符号,表示当前VMA,”.“的初始值是0.通过给”.“赋值可以增加它的值,每创建一个新的输出section时,”.“也会根据其大小相应地增加。通过直接赋值给”.“可能会产生空洞,这些空洞会被填充上(可以指定填充值)。需要注意的是,通过赋值不可以使”."回退,如果ld检测到这种情况,就会报错。

例如,对于下面这个例子中,file1位于输出节的开头,然后有一个1000字节的间隙。然后出现file2,在加载file3之前也有1000字节的间隙。符号“ = 0x1234”指定要在间隙中写入哪些数据.

SECTIONS
{
  output :
  {
  file1(.text)
  . = . + 1000;
  file2(.text)
  . += 1000;
  file3(.text)
  } = 0x1234;
}

表达式的类型由其在脚本文件中的位置控制。在节定义中分配的符号是相对于节的基础创建的。在任何其他位置分配的符号将被创建为绝对符号。由于在节定义中创建的符号是相对于节的基础的,因此如果请求可重定位的输出,该符号将保持可重定位。即使使用绝对分配函数ABSOLUTE在节定义中分配了符号,也可以使用绝对值创建符号。例如,要创建一个绝对符号,其地址是名为.data的输出节的最后一个字节:

TODO to be continued


我很好奇