1. 概述

链接器的作用主要是对符号的解析以及将符号与地址进行绑定。要实现这个功能需要依赖链接脚本,链接脚本大多数情况下用来链接输入文件,并生成目标文件。编译器的“-T”参数就是用来指定链接脚本的。

2. 链接脚本

需要解析的链接脚本代码如程序清单 2.1所示。 

程序清单 2.1 链接脚本源码

OUTPUT_FORMAT("elf32-tradlittlemips")OUTPUT_ARCH(mips)ENTRY(_start)SECTIONS{  /* Read-onlysections, merged into text segment: */  . = 0x80100000;  .text      :   {    _ftext = . ;    *(.text)    *(.rodata)    *(.rodata1)    *(.reginfo)    *(.init)    *(.stub)    /* .gnu.warningsections are handled specially by elf32.em. */    *(.gnu.warning)  } =0  _etext = .;  PROVIDE (etext =.);  .fini      : { *(.fini)    } =0  .data    :  {    _fdata = . ;    *(.data)    CONSTRUCTORS  }  .data1   : { *(.data1) }  .ctors         :  {               __CTOR_LIST__ = .;               LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)              *(.ctors)               LONG(0)               __CTOR_END__ = .;  }  .dtors         :  {               __DTOR_LIST__ = .;               LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)              *(.dtors)               LONG(0)               __DTOR_END__ = .;  }  _gp = ALIGN(16) +0x7ff0;  .got           :  {    *(.got.plt)*(.got)   }  /* We want thesmall data sections together, so single-instruction offsets     can access them all, and initialized dataall before uninitialized, so     we can shortenthe on-disk segment size.  */  .sdata     : { *(.sdata) }  .lit8 : {*(.lit8) }  .lit4 : {*(.lit4) }  _edata  =  .;  PROVIDE (edata =.);  __bss_start = .;  _fbss = .;  .sbss      : { *(.sbss) *(.scommon) }  .bss       :  {   *(.dynbss)   *(.bss)   *(COMMON)  }  . = ALIGN(16);  __bss_end = .;  _end = .;__end =.; end = .;   PROVIDE (end =.);  /* These areneeded for ELF backends which have not yet been     converted tothe new style linker.  */  .stab 0 : {*(.stab) }  .stabstr 0 : {*(.stabstr) }  /* DWARF debugsections.     Symbols in the.debug DWARF section are relative to the beginning of the     section so webegin .debug at 0.  It's not clear yetwhat needs to happen     for theothers.   */  .debug          0 : { *(.debug) } .debug_srcinfo  0 : {*(.debug_srcinfo) } .debug_aranges  0 : {*(.debug_aranges) }  .debug_pubnames 0: { *(.debug_pubnames) } .debug_sfnames  0 : {*(.debug_sfnames) }.line           0 :{ *(.line) }  /* These mustappear regardless of  .  */  .gptab.sdata : {*(.gptab.data) *(.gptab.sdata) }  .gptab.sbss : {*(.gptab.bss) *(.gptab.sbss) }}

3. 链接脚本逐句解析

OUTPUT_FORMAT("elf32-tradlittlemips")OUTPUT_ARCH(mips)

OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 脚本的保留字命令。OUTPUT_FORMAT 说明输出二进制文件的格式,OUTPUT_ARCH 说明输出文件所在平台。

ENTRY(_start)1

ENTRY 命令的作用是,将后面括号中的符号值设置成入口地址。入口地址(entry point)的定义:进程执行的第一条用户空间的指令在进程地址空间中的地址。 

ld 有多种方法设置进程入口地址,通常它按以下顺序设置:(编号越前, 优先级越高) 
1. ld 命令行的“-e”选项; 
2. 链接脚本的 ENTRY(SYMBOL) 命令; 
3. 如果定义了 start 符号, 使用 start 符号值; 
4. 如果存在 .text section, 使用 .text section 的第一字节的位置值; 
5. 使用值 0。

SECTIONS{

SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section:即如何将输入 section 合为输出 section;如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA) 。其格式如下:

SECTIONS{….}/* Read-only sections, merged into text segment: */  . = 0x80100000;

这句把定位器符号置为 0x80100000 (若不指定,则该符号的初始值为 0)。 

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

.text      :   {    _ftext = . ;    *(.text)    *(.rodata)    *(.rodata1)    *(.reginfo)    *(.init)    *(.stub)    /* .gnu.warningsections are handled specially by elf32.em. */    *(.gnu.warning)  } =0

.text : 表示text段开始。 

(.text) 将所有(符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定。 
} =0 表示合并时留下的空隙用 0 填充。

_etext = .;  PROVIDE (etext = .);

_etext = .;很多变量都定义成等于这个 . 符,实际上这个符号所代表的值是在变化的,随着脚本越往后走,值越增加,根据前面填充的多少自动往后加。 

PROVIDE关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号。 
此时定义了一个 etext 符号,当目标文件内引用了 etext 符号,却没有定义它时,etext 符号对应的地址被定义为 .text section 之后的第一个字节的地址。

.fini     : { *(.fini)    } =0

含义同前文。

.data    :  {    _fdata = . ;    *(.data)    CONSTRUCTORS  }  .data1  : { *(.data1) }

此处代码就是用于描述数据段了。 

CONSTRUCTORS 是一个保留字命令。与 c++ 内的(全局对象的)构造函数和(全局对像的)析构函数相关。

.ctors         :  {               __CTOR_LIST__ = .;               LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)              *(.ctors)               LONG(0)               __CTOR_END__ = .;  }  .dtors         :  {               __DTOR_LIST__ = .;               LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)              *(.dtors)               LONG(0)               __DTOR_END__ = .;  }

对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入 .ctors section 和 .dtors section 内。 

当链接器生成的目标文件格式不支持任意section名字时,比如ECOFF、XCOFF格式,链接器将通过名字来识别全局构造和全局析构,对于这些文件格式,链接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内。

符号CTORS_LIST表示全局构造信息的的开始处,CTORS_END表示全局构造信息的结束处。 

这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束。 
一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用。

_gp = ALIGN(16) + 0x7ff0;1

_gp是一个重要的全局变量,用作全局引用的一个指针。

.got           :  {    *(.got.plt)*(.got)   }  /* We want thesmall data sections together, so single-instruction offsets     can accessthem all, and initialized data all before uninitialized, so     we can shortenthe on-disk segment size.  */  .sdata     : { *(.sdata) }  .lit8 : {*(.lit8) }  .lit4 :{ *(.lit4) }

含义同前文。

_edata  =  .;  PROVIDE (edata = .);

意义与前面的 etext 类似。edata 符号也较为重要。

__bss_start = .;  _fbss = .;  .sbss      : { *(.sbss) *(.scommon) }  .bss       :  {   *(.dynbss)   *(.bss)   *(COMMON)  }  . = ALIGN(16);  __bss_end = .;  _end = .;__end =.; end = .;   PROVIDE (end = .);

此处是描述BSS段。COMMON 这个保留字的意义: 

通用符号(common symbol)的输入section:在许多目标文件格式中,通用符号并没有占用一个section。链接器认为,输入文件的所有通用符号在名为COMMON的section内。上例中将所有输入文件的所有通用符号放入输出.bss section内。 
上述脚本,定义了几个重要的符号:

__bss_start = .;__bss_end = .;_end = .;__end = .;end = .;

这些内容在代码中可能会用到的。

/* These are needed for ELF backends which have not yetbeen     converted tothe new style linker.  */  .stab 0 : {*(.stab) }  .stabstr 0 : {*(.stabstr) }  /* DWARF debugsections.     Symbols in the.debug DWARF section are relative to the beginning of the     section so webegin .debug at 0.  It's not clear yetwhat needs to happen     for theothers.   */  .debug          0 : { *(.debug) } .debug_srcinfo  0 : {*(.debug_srcinfo) } .debug_aranges  0 : {*(.debug_aranges) }  .debug_pubnames 0: { *(.debug_pubnames) } .debug_sfnames  0 : {*(.debug_sfnames) }  .line           0 : { *(.line) }  /* These mustappear regardless of  .  */  .gptab.sdata : {*(.gptab.data) *(.gptab.sdata) }  .gptab.sbss : {*(.gptab.bss) *(.gptab.sbss) }}

最后这部分内容意义与上述类似,看英文注释可以明白,是新版本链接器所需要的一些内容。

4. 免责声明

内部交流文档,仅针对SylixOS平台,若发现相关错误或者建议,请及时联系文档创建者进行修订和更新。