GNU linker ld —— VERSION Command

GNU linker ld (GNU Binutils) version 2.42 —— Linker Scripts:VERSION Command

个人笔记,仅供参考!!
当使用 ELF 时,链接器支持符号版本,符号版本仅在使用共享库时有用。动态链接器可以使用符号版本来选择在运行可能已链接到早期共享库版本的程序时,特定函数的版本。当在运行可能已链接到共享库的早期版本的程序时,动态链接器可以使用符号版本来选择函数的特定版本。

可以直接在主链接器脚本中包含版本脚本,或者将版本脚本作为隐式链接器脚本提供。还可以使用 --version-script 链接器选项。

VERSION 命令的语法如下:

1
VERSION { version-script-commands }

版本脚本命令的格式与 Solaris 2.5 链接器使用的格式相同。版本脚本定义了一个版本节点树。在版本脚本中指定节点名称和相互依赖关系。可以指定哪些符号绑定到哪个版本节点,并且可以将一组指定的符号缩小到局部范围,以便它们不会在共享库之外全局可见。

版本脚本实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
VERS_1.1 {
global:
foo1;
local:
old*;
original*;
new*;
};

VERS_1.2 {
foo2;
} VERS_1.1;

VERS_2.0 {
bar1; bar2;
extern "C++" {
ns::*;
"f(int, double)";
};
} VERS_1.2;

这个示例版本脚本定义了三个版本节点。首先定义的版本节点是VERS_1.1,它没有其他依赖项,其中符号foo1绑定到了VERS_1.1。脚本使用通配符模式将多个符号缩小到局部范围,以便它们不会在共享库之外可见;匹配以 oldoriginalnew开头的任何符号。通配符模式与在匹配文件名时使用的 shell 中的通配符模式相同。但是,如果在双引号内指定符号名称,则该名称将被视为文字,而不是通配符模式。

接下来,版本脚本定义了节点 VERS_1.2。此节点依赖于 VERS_1.1。脚本将符号 foo2 绑定到版本节点 VERS_1.2

最后,版本脚本定义了节点 VERS_2.0。此节点依赖于 VERS_1.2。并将符号 bar1bar2 绑定到版本节点 VERS_2.0

当链接器在库中找到一个未明确绑定到版本节点的符号时,它会将其绑定到库的未指定基本版本。可以使用 global: *; 在版本脚本的某个位置将所有其他未指定的符号绑定到给定的版本节点。注意,在全局规范中使用通配符有风险,最后一个版本节点除外。其他地方的全局通配符会导致意外地将符号添加到旧版本的导出集中。这是错误的,因为较旧的版本应该具有一组固定的符号。

版本节点的名称除了可能对阅读它们的人有所暗示外,没有特定的含义。例如,“2.0” 版本完全可以出现在 “1.1” 和 “1.2” 之间。但是,这样写版本脚本会令人感到困惑。

如果版本脚本中只有一个版本节点,可以省略节点名称。这样的版本脚本不为符号分配任何版本,只选择哪些符号将全局可见,哪些符号不可见。例如:{ global: foo; bar; local: *; };

当你将一个应用程序与具有版本化符号的共享库链接时,应用程序本身知道它所需的每个符号的版本,它还知道它需要从每个链接的共享库中获取哪些版本节点。因此,在运行时,动态加载器可以快速检查以确保您链接的库实际上提供了应用程序需要解析所有动态符号所需的所有版本节点。通过这种方式,动态链接器可以确定它所需的所有外部符号都可以解析,而无需搜索每个符号引用。

符号版本控制实际上是一种比SunOS更复杂的进行次要版本检查的方法。这里解决的根本问题是,对外部函数的引用通常是按需绑定的,并且在应用程序启动时并不都绑定。如果共享库过时,可能会缺少所需的接口;当应用程序尝试使用该接口时,它可能会突然意外地失败。使用符号版本控制,如果与应用程序一起使用的库太旧,用户在启动程序时会收到警告。

对于Sun的版本控制方法,GNU有几个扩展。其中之一是源文件中定义符号时实现将符号绑定到版本节点,而不是在版本脚本中定义。这主要是为了减轻库维护者的负担。可以通过在C源文件中添加以下内容来实现:

1
__asm__(".symver original_foo,foo@VERS_1.1");

这将将函数“original_foo”重命名为绑定到版本节点“VERS_1.1”的“foo”的别名。可以使用“local:”指令防止导出符号“original_foo”。“.symver”指令优先于版本脚本。

第二个GNU扩展允许同一函数的多个版本出现在给定的共享库中。通过这种方式,可以对接口进行不兼容的更改,而不会增加共享库的主要版本号,同时仍允许链接到旧接口的应用程序继续运行。

要实现这一点,必须在源文件中使用多个“.symver”指令。以下是一个示例:

1
2
3
4
__asm__(".symver original_foo,foo@");
__asm__(".symver old_foo,foo@VERS_1.1");
__asm__(".symver old_foo1,foo@VERS_1.2");
__asm__(".symver new_foo,foo@@VERS_2.0");

在此示例中,“foo@”表示绑定到未指定基本版本的符号“foo”。包含此示例的源文件定义了4个C函数:“original_foo”、“old_foo”、“old_foo1”和“new_foo”。

当有多个给定符号的定义时,需要一种方法来指定外部引用该符号的默认版本。可以使用“.symver”指令的“foo@@VERS_2.0”方式来实现这一点。在此方式中,只能声明一个符号的一个版本作为默认版本。

如果您希望将引用绑定到共享库中的符号的特定版本,可以使用方便的别名(例如“old_foo”),或者可以使用“.symver”指令来明确绑定到所讨论的函数的外部版本。

还可以在版本脚本中指定语言:

1
VERSION extern "lang" { version-script-commands }

支持的“lang”包括“C”、“C++”和“Java”。链接器将在链接时遍历符号列表,并根据“lang”对其进行解析,然后将其与“version-script-commands”中指定的模式进行匹配。默认的“lang”是“C”。

参考连接:

【1】https://sourceware.org/binutils/docs/ld/SECTIONS.html