版本定义通常包括符号名称与唯一版本名称之间的关联。这些关联建立在 mapfile 中,并使用链接编辑器的 –M 选项提供给目标文件的最终链接编辑。缩减符号作用域一节中介绍了此技术。
任何时候作为 mapfile 指令的一部分指定一个版本名称时,都会创建一个版本定义。在以下示例中,两个源文件进行合并,并配合 mapfile 指令,生成一个定义了公共接口的目标文件。
$ cat foo.c #include <stdio.h> extern const char *_foo1; void foo1() { (void) printf(_foo1); } $ cat data.c const char *_foo1 = "string used by foo1()\n"; $ cat mapfile $mapfile_version 2 SYMBOL_VERSION SUNW_1.1 { # Release X global: foo1; local: *; }; $ cc -c -Kpic foo.c data.c $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ elfdump -sN.symtab libfoo.so.1 | grep 'foo.$' [32] 0x1074c 0x4 OBJT LOCL H 0 .data _foo1 [53] 0x560 0x38 FUNC GLOB D 0 .text foo1
符号 foo1 是为提供共享目标文件的公共接口而定义的唯一全局符号。特殊的自动缩减指令 "*" 缩减了其他所有全局符号,从而在所生成的目标文件中实现局部绑定。SYMBOL_SCOPE / SYMBOL_VERSION 指令部分对该自动缩减指令进行了介绍。关联的版本名称 SUNW_1.1 导致生成版本定义。因此,共享目标文件的公共接口包括与内部版本定义 SUNW_1.1 关联的全局符号 foo1。
任何时候使用版本定义或自动缩减指令生成目标文件时,都会同时创建一个基版本定义。此基版本使用所生成的目标文件的名称来定义。此基版本用于关联链接编辑器生成的任何保留符号。有关保留符号的列表,请参见生成输出文件。
使用 pvs(1) 和 –d 选项可以显示目标文件中包含的版本定义。
$ pvs -d libfoo.so.1 libfoo.so.1; SUNW_1.1;
目标文件 libfoo.so.1 包含一个名为 SUNW_1.1 的内部版本定义和一个名为 libfoo.so.1 的基版本定义。
目标文件可以从该初始版本定义开始,通过添加新的接口和更新功能不断演变。例如,通过更新源文件 foo.c 和 data.c,可以将新的函数 foo2 及其支持的数据结构添加到目标文件中。
$ cat foo.c #include <stdio.h> extern const char *_foo1, *_foo2; void foo1() { (void) printf(_foo1); } void foo2() { (void) printf(_foo2); } $ cat data.c const char *_foo1 = "string used by foo1()\n"; const char *_foo2 = "string used by foo2()\n";
可以创建一个新的版本定义 SUNW_1.2 来定义一个新接口,代表符号 foo2。此外,还可以将此新接口定义为继承原始版本定义 SUNW_1.1。
创建此新接口很重要,因为该接口描述了目标文件的演变。这些接口使用户可以验证和选择要绑定的接口。绑定到版本定义和指定版本绑定部分更详细地介绍了这些概念。
以下示例介绍了创建上述两个接口的 mapfile 指令。
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION SUNW_1.1 { # Release X global: foo1; local: *; }; SYMBOL_VERSION SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ elfdump -sN.symtab libfoo.so.1 | grep 'foo.$' [28] 0x107a4 0x4 OBJT LOCL H 0 .data _foo1 [29] 0x107a8 0x4 OBJT LOCL H 0 .data _foo2 [48] 0x5e8 0x20 FUNC GLOB D 0 .text foo1 [51] 0x618 0x20 FUNC GLOB D 0 .text foo2
符号 foo1 和 foo2 均定义为共享目标文件公共接口的一部分。但是,每个符号指定给了一个不同的版本定义。foo1 指定给了版本 SUNW_1.1。foo2 指定给了版本 SUNW_1.2。
使用 pvs(1) 以及 –d、–v 和 –s 选项可以显示这些版本定义及其继承性和符号关联。
$ pvs -dsv libfoo.so.1 libfoo.so.1: _end; _GLOBAL_OFFSET_TABLE_; _DYNAMIC; _edata; _PROCEDURE_LINKAGE_TABLE_; _etext; SUNW_1.1: foo1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}: foo2; SUNW_1.2
版本定义 SUNW_1.2 具有一个对版本定义 SUNW_1.1 的依赖项。
不同版本定义之间的继承是一项很有用的技术。这种继承可以减少任何绑定到版本依赖项的目标文件最终记录的版本信息。绑定到版本定义一节对版本继承进行了更详细的介绍。
版本定义符号创建后会与版本定义相关联。在上面的 pvs(1) 示例中,使用 –v 选项时,将显示这些符号。
对于不需要引入新接口定义的目标文件内部更改,可以通过创建弱版本定义来定义。例如,这类更改可以是错误修复或性能改进。这类版本定义是空的,版本定义没有任何全局接口符号与之关联。
例如,假定之前示例中使用的数据文件 data.c 更新后提供更为详细的字符串定义。
$ cat data.c const char *_foo1 = "string used by function foo1()\n"; const char *_foo2 = "string used by function foo2()\n";
这时便可以引入一个弱版本定义来标识此项更改。
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION SUNW_1.1 { # Release X global: foo1; local: *; }; SYMBOL_VERSION SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; SYMBOL_VERSION SUNW_1.2.1 { } SUNW_1.2; # Release X+2 $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}; SUNW_1.2.1 [WEAK]: {SUNW_1.2};
空版本定义由弱标签表示。利用这些弱版本定义,应用程序可以验证某个特定的实现详细信息是否存在。应用程序可以绑定到与其要求的实现详细信息相关联的版本定义。绑定到版本定义一节更详细地说明了如何使用这些定义。
上面的示例说明了添加到目标文件的新版本定义如何继承任何现有的版本定义。您还可以创建具有唯一性的、独立的版本定义。在以下示例中,两个新文件 bar1.c 和 bar2.c 添加到目标文件 libfoo.so.1。这两个文件分别提供了两个新符号:bar1 和 bar2。
$ cat bar1.c extern void foo1(); void bar1() { foo1(); } $ cat bar2.c extern void foo2(); void bar2() { foo2(); }
这两个新符号旨在定义两个新的公共接口。这些新接口彼此并不相关。但是,每个接口均表现出对原始 SUNW_1.2 接口的依赖性。
以下 mapfile 定义创建了必要的关联。
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION SUNW_1.1 { # Release X global: foo1; local: *; }; SYMBOL_VERSION SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; SYMBOL_VERSION SUNW_1.2.1 { } SUNW_1.2; # Release X+2 SYMBOL_VERSION SUNW_1.3a { # Release X+3 global: bar1; } SUNW_1.2; SYMBOL_VERSION SUNW_1.3b { # Release X+3 global: bar2; } SUNW_1.2;
使用此 mapfile 时在 libfoo.so.1 中创建的版本定义及其相关的依赖项可以使用 pvs(1) 进行检查。
$ cc -o libfoo.so.1 -M mapfile -G foo.o bar1.o bar2.o data.o $ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}; SUNW_1.2.1 [WEAK]: {SUNW_1.2}; SUNW_1.3a: {SUNW_1.2}; SUNW_1.3b: {SUNW_1.2};
版本定义可用于验证运行时绑定要求,还可以用于控制目标文件创建期间的目标文件绑定。以下各节将更详细地探讨这些版本定义的用法。