链接脚本解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链接脚本解析相关的知识,希望对你有一定的参考价值。

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


本文出自 “11451177” 博客,请务必保留此出处http://11461177.blog.51cto.com/11451177/1983911

以上是关于链接脚本解析的主要内容,如果未能解决你的问题,请参考以下文章

初入AngularJS基础门

链接器链接过程及相关概念解析

分享前端开发常用代码片段

收藏|分享前端开发常用代码片段

高效Web开发的10个jQuery代码片段

微信小程序代码片段