如果在版本脚本中,这是一个唯一的版本节点,节点名可以被省略。这样的版本脚本不给符号赋任何版本,只是选择哪些符号会被全局可见而哪些不会。 { global: foo; bar; local: *; };
当你把一个程序跟一个带有版本符号的共享库连接时,程序自身知道每个符号的哪个版本是它需要的,而且它还知道它连接的每一个节享库中哪些版本的节点是它需要的。这样,在运行时,动态载入程序可以做一个快速的确认,以保证你连接的库确实提供了所有的程序需要用来解析所有动态符号的版本节点。用这种方法,就有可能让每一个动态连接器知道所有的外部符号不需要通过搜索每一个符号引用就能解析。
符号版本在SunOS上做次版本确认是一种很成熟的方法。一个被提出来的基本的问题是对于外部函数的标准引用会在需要时被绑定到正确的版本,但不是在程序启动的时候全部被绑定。如果一个共享库过期了,一个需要的界面可能就不存在了;当程序需要使用这个界面的时候,它可能会突然地意外失败。有了符号版本后,当用户启动他们的程序时,如果要使用的共享库太老了的话,用户会得到一条警告信息。
GNU对Sun的版本确认办法有一些扩展。首先就是能在符号定义的源文件中把一个符号绑定到一个版本节点而不是在一个版本脚本中。这主要是为了减轻库维护的工作量。你可以通过类似下面的代码实现这一点:
__asm__(\
在C源文件中。这句会给函数'original_foo'取一个别名'foo',并绑定到版本节点`VERS_1.1'。操作符'local:'可以被用来阻止符号'original_foo'被导出。操作符'.symver'使这句优先于版本脚本。
第二个GNU的扩展是在一个给定的共享库中允许同一个函数的多个版本。通过这种办法,你可以不增加共享库的主版本号而对界面做完全不相容的修改。 要实现这个,你必须在一个源文件中多次使用'.symver'操作符。这里是一个例子:
__asm__(\ __asm__(\ __asm__(\ __asm__(\
在这个例子中,'foo@'表示把符号'foo'绑定到一个没有指基版本的符号上。含有这个例子的源文件必须定义4个C函数:`original_foo', `old_foo', `old_foo1', 和`new_foo'.
当你有一个给定符号的多个定义后,有必要有一个方法可以指定一个缺省的版本,对于这个符号的外部引用就可以找到这个版本。用这种方法,你可以只声明一个符号的一个版本作为缺省版本,否则,你会拥有同一个符号的多个定义。 如果你想要绑定一个引用到共享库中的符号的一个指定的版本,你可以很方便地使用别名(比如,old_foo),或者你可以使用'.symver'操作符来指定绑定到一个外部函数的特定版本。
你也可以在版本脚本中指定语言。
VERSION extern \被支持的'lang'有‘C’,‘C++’和‘Java’。 --
标 题: GLD中文手册--(九) 连接脚本中的表达式
连接脚本语言中的表达式的语法跟C的表达式是完全是致的。所有的表达式都以整型值被求值。所有的表达式也被以相同的宽度求值。在32位系统是它是32位,否则是64位。
你可以在表达式中使用和设置符号值。
连接器为了使用表达式,定义了几个具有特殊途的内建函数。 常数
---------
所有的常数都是整型值。
就像在C中,连接器把以'0'开头的整型数视为八进制数,把以'0x'或'0X'开头的视为十六进制。连接器把其它的整型数视为十进制。
另外,你可以使用'K'和'M'后缀作为常数的度量单位,分别为'1024'和'1024*1024'。
比如,下面的三个常数表示同一个值。 _fourk_1 = 4K; _fourk_2 = 4096; _fourk_3 = 0x1000;
符号名
------------ 除了引用,符号名都是以一个字母,下划线或者句号开始,可以包含字母,数字,下划线,句点和连接号。不是被引用的符号名必须不和任何关键字冲突。你可以指定一个含有不固定它符数或具有跟关键字相同名字但符号名必须在双引号内: \
\
因为符号可以含有很多非文字字符,所以以空格分隔符号是很安全的。比如,'A-B'是一个符号,而'A - B'是一个执行减法运算的表达式。 定位计数器
--------------------
一个特殊的连接器变量\总是含有当前的输出定位计数器。因为'.'总引用输出段中的一个位置,它只可以出现在'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'的同义词,是为了与其它的连接器保持兼容。这在设置输出节的地址时非常有用。