摘自:http://bbs.kaspersky.com.cn/thread-281159-1-1.html

ld.info链接脚本解读

 

[Unix/Linux学堂]


时间搬移覆盖代码。

在覆盖区域的最后,定位计数器的值被设为覆盖区域的开始地址加上最大的节的长度。

这里是一个例子。记住这只会出现在‘SECTIONS’结构的内部。

      OVERLAY 0x1000 : AT (0x4000)
      {
        .text0 { o1/*.o(.text) }
        .text1 { o2/*.o(.text) }
      }

这段代码会定义'.text0'和'.text1',它们都从地址0x1000开始。‘.text0'会被载入到地址0x4000处,而
'.text1'会被载入到紧随'.text0'后的位置。下面的几个符号会被定义:`__load_start_text0',
`__load_stop_text0', `__load_start_text1', `__load_stop_text1'.

拷贝'.text1'到覆盖区域的C代码看上去可能会像下面这样:

      extern char __load_start_text1, __load_stop_text1;
      memcpy ((char *) 0x1000, &__load_start_text1,
              &__load_stop_text1 - &__load_start_text1);

注意'OVERLAY'命令只是为了语法上的便利,因为它所做的所有事情都可以用更加基本的命令加以代替。上面
的例子可以用下面的完全特效的写法:

      .text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
      __load_start_text0 = LOADADDR (.text0);
      __load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0);
      .text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
      __load_start_text1 = LOADADDR (.text1);
      __load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1);
      . = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));





 

只可以出现在'SECTIONS'命令中的表达式中。'.'符号可以出现在表达式中一个普能符号允许出现的任何位
置。

把一个值赋给'.'会让定位计数器产生移动。这会在输出段中产生空洞。定位计数器从不向前移动。

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

在前面的例子中,来自'file1'的'.text'节被定位在输出节'output'的起始位置。它后面跟有1000byte的
空隙。然后是来自'file2'的'.text'节,同样是后面跟有1000byte的空隙,最后是来自'file3'的'.text'
节。符号'=0x12345678'指定在空隙中填入什么样的数据。

注意:'.'实际上引用的是当前包含目标的从开始处的字节偏移。通常,它就是'SECTIONS'语句,其起始地
址是0,因为'.'可以被用作绝对地址。但是如果'.'被用在一个节描述中,它引用的是从这个节起始处开始
的偏移,而不是一个绝对地址。这样,在下面这样一个脚本中:

    SECTIONS
    {
        . = 0x100
        .text: {
          *(.text)
          . = 0x200
        }
        . = 0x500
        .data: {
          *(.data)
          . += 0x600
        }
    }

'.text'节被赋于起始地址0x100,尽管在'.text'输入节中没有足够的数据来填充这个区域,但其长
度还是0x200bytes。(如果数据太多,那会产生一条错误信息,因为这会试图把'.'向前移)。'.data'
节会从0x500处开始,并且它在结尾处还会有0x600的额外空间。

运算符
---------

连接器可以识别标准的C的算术运算符集, 以及它们的优先集.

    优先集        结合性          运算符                  备注
    (highest)
    1              left            !  -  ~                  (1)
    2              left            *  /  %
    3              left            +  -
    4              left            >>  <<
    5              left            ==  !=  >  <  <=  >=
    6              left            &
    7              left            |
    8              left            &&
    9              left            ||
    10              right          ? :
    11              right          &=  +=  -=  *=  /=      (2)
    (lowest)
  注: (1) 前缀运算符 (2) *Note Assignments::.

求值
----------

连接器是懒惰求表达式的值。它只在确实需要的时候去求一个表达式的值。

连接器需要一些信息,比如第一个节的起始地址的值,还有内存区域的起点与长度,在做任何连接的
时候这都需要。在连接器读取连接脚本的时候,这些值在可能的时候被计算出来。

但是,其它的值(比如符号的值)直到内存被分配之后才会知道或需要。这样的值直到其它信息(比
如输出节的长度)可以被用来进行符号赋值的时候才被计算出来。

直到内存分配之后,节的长度才会被知道,所以依赖于节长度的赋值只能到内存分配之后才会被执行。

有些表达式,比如那些依赖于定位计数器'.'的表达式,必须在节分配的过程中被计算出来。

如果一个表达式的结果现在被需要,但是目前得不到这个值,这样会导致一个错误。比如,象下面这
样一个脚本:

    SECTIONS
      {
        .text 9+this_isnt_constant :
          { *(.text) }
      }

会产生一个错误信息'non constant expression for initial address'.

表达式的节
----------------------------

当一个连接器计算一个表达式时,得到的结果可能是一个绝对值,也可能跟某个节相关。一个节相关的
表达式是从一个节的基地址开始的固定的偏称值。

表达式在连接脚本中的位置决定了它是绝对的或节相关的。一个出现在输出节定义中的表达式是跟输出
节的基地址相关的。一个出现在其它地方的表达式则是绝对的。

如果你通过'-r'选项指定需要可重位输出,那一个被赋为节相关的表达式的符号就会是可重定位的。意
思是下一步的连接操作会改变这个符号的值。符号的节就是节相关的表达式所在的节。

一个被赋为绝对表达式的符号在后面进一步的连接操作中会始终保持它的值不变。符号会是绝对的,并
不会有任何的特定的相关节。

如果一个表达式有可能会是节相关的,你可以使用内建函数'ABSOLUTE'强制一个表达式为绝对的。比如,
要创建一个被赋为输出节'.data'的末尾地址的绝对符号:

    SECTIONS
      {
        .data : { *(.data) _edata = ABSOLUTE(.); }
      }

如果没有使用'ABSOLUTE','_edata'会跟节'.data'相关。

内建函数
-----------------

为了使用连接脚本表达式,连接脚本语言含有一些内建函数。

`ABSOLUTE(EXP)'
返回表达式EXP的绝对值(不可重定位,而不是非负)。主要在把一个绝对值赋给一个节定义内的
符号时有用。

`ADDR(SECTION)'
返回节SECTION的绝对地址(VMA)。你的脚本之前必须已经定义了这个节的地址。在接下来的例子
中,'symbol_1'和'symbol_2'被赋以相同的值。

        SECTIONS { ...
          .output1 :
            {
            start_of_output_1 = ABSOLUTE(.);
            ...
            }
          .output :
            {
            symbol_1 = ADDR(.output1);
            symbol_2 = start_of_output_1;
            }
        ... }

`ALIGN(EXP)'
返回定位计数器'.'对齐到下一个EXP指定的边界后的值。‘ALIGN’不改变定位计数器的值,它只是
在定位计数器上面作了一个算术运算。这里有一个例子,它在前面的节之后,把输出节'.data'对齐
到下一个'0x2000'字节的边界,并在输入节之后把节内的一个变量对齐到下一个'0x8000'字节的边界。

        SECTIONS { ...
          .data ALIGN(0x2000): {
            *(.data)
            variable = ALIGN(0x8000);
          }
        ... }

这个例子中前一个'ALIGN'指定一个节的位置,因为它是作为节定义的可选项ADDRESS属性出现的。第
二个‘ALIGN’被用来定义一个符号的值。

内建函数'NEXT'跟‘ALIGN’非常相似。

`BLOCK(EXP)'
这是'ALIGN'的同义词,是为了与其它的连接器保持兼容。这在设置输出节的地址时非常有用。

`DATA_SEGMENT_ALIGN(MAXPAGESIZE, COMMONPAGESIZE)'
    这跟下面的两个表达同义:
        (ALIGN(MAXPAGESIZE) + (. & (MAXPAGESIZE - 1)))
    或者:
        (ALIGN(MAXPAGESIZE) + (. & (MAXPAGESIZE - COMMONPAGESIZE)))

隐式连接脚本
=======================

如果你指定了一个连接器输出文件,而连接器不能识别它是一个目标文件还是档案文件,它会试图把它读作
一个连接脚本。如果这个文件不能作为一个连接脚本被分析,连接器就会报告一个错误。

一个隐式的连接器脚本不会替代缺省的连接器脚本。

一般,一个隐式的连接器脚本只包含符号赋值,或者'INPUT','GROUP'或'VERSION'命令。

BFD
***

连接器通过BFD库来对目标文件和档案文件进行操作。这些库允许连接器忽略目标文件的格式而使用相关的
例程来操作目标文件。只要简单地创建一个新的BFD后台并把它加到库中,一个不同的目标文件格式就会被
支持。但是为了节约运行时内存,连接器和相关的工具一般被配置为只支持可用的目标文件格式的一个子集,
你可以使用'objdump -i'来列出你配置的所有支持的格式。

就像大多数的案例,BFD是一个在多种相互有冲突的需求之间的一个折中,影响BFD设计的一个最主要的因
素是效率。因为BFD简化了程序和后台,更多的时间和精力被放在了优化算法以追求更快的速度。

BFD解决方案的一个副产品是你必须记住有信息丢失的潜在可能。在使用BFD机制时,有两处地方有用信息可
能丢失:在转化时和在输出时。

它如何工作: BFD概要。
===============================

当一个目标文件被打开时,BFD子程序自动确定输入目标文件的格式。然后它们在内存中用指向子程序的指针
构建一个描述符,这个描述符被用作存取目标文件的数据结构元素。

因为需要来自目标文件的不同信息,BFD从文件的不同节中读取它们,并处理。比如,连接器的一个非常普遍
的操作是处理符号表。每一个BFD后台提供一个在目标文件的符号表达形式跟内部规范格式之间的转化的函数,
当一个连接器需要一个目标文件的符号表时,它通过一个内存指针调用一个来自相应的BFD后台的子程序,这
个子程序读取表并把它转化为规范表。然后,连接器写输出文件的符号表,另一个BFD后台子程序被调用,以
创建新的符号表并把它转化为选定的输出格式。

信息丢失。
----------------

在输出的过程中,信息可能会被丢失。BFD支持的输出格式并不提供一致的特性,并且在某一种格式中可以被
描述的信息可能在另一种格式中没有地方可放。一个例子是在'b.out'中的对齐信息,在一个'a.out'格式的
文件中,没有地方可以存储对齐信息,所以当一个文件是从'b.out'连接而成的,并产生的是一个'a.out'的
文件,对齐信息就不会被传入到输出文件中(连接器还是在内部使用对齐信息,所以连接器的执行还是正确的)

另一个例子是COFF节名字。COFF文件中可以含有不限数量的节,每一个都有一个文字的节名。如果连接的目标是
一种不支持过多节的格式(比如,'a.out')或者是一种不含有节名的格式(比如,Oasys格式),连接器不
能像通常那样简单地处理它。你可以通过把所需的输入输出节通过连接脚本语言进行详细映射来解决这下问题。

在规范化的过程中信息也会丢失。BFD内部的对应于外部格式的规范形式并不是完全详尽的;有些在输入格式
中的结构在内部并没有对应的表示方法。这意味着BFD后台在从外部到内部或从内部到外部的转化过程中不能
维护所有可能的数据。

这个限制只在一个程序读取一种格式并写成另一种格式的时候会是一个问题。每一个BFD后台有责任维护尽可能
多的数据,内部的BFD规范格式具有对BFD内核不透明的结构体,只导出给后台。当一个文件以一种格式读取后,
规范格式就会为之产生。同时,后台把所有可能丢失的信息进行存储。如果这些数据随后会写以相同的格式写
回,后台程序就可以使用BFD内核提供的跟选前准备的相同的规范格式。因为在后台之间有大量相同的东西,在
把big endianCOFF拷贝成littile endian COFF时,或者'a.out'到'b.out'时,不会有信息丢失。当一些混合格
式被连接到一起时,只有那些格式跟目标格式不同的文件会丢失信息。

相关文章: