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平台,若发现相关错误或者建议,请及时联系文档创建者进行修订和更新。