版本定义通常由符号名称与唯一版本名称关联组成。这些关联在 mapfile 内建立,并且在使用链接编辑器的 -M 选项最终链接编辑目标文件时应用它们。缩减符号范围一节中介绍了此技术。
版本定义是在将版本名称指定为 mapfile 指令的一部分时定义的。在以下示例中,将两个源文件与 mapfile 指令组合在一起,以生成具有已定义公共接口的目标文件:
$ cat foo.c extern const char * _foo1; void foo1() { (void) printf(_foo1); } $ cat data.c const char * _foo1 = "string used by foo1()\n"; $ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ nm -x libfoo.so.1 | grep "foo.$" [33] |0x0001058c|0x00000004|OBJT |LOCL |0x0 |17 |_foo1 [35] |0x00000454|0x00000034|FUNC |GLOB |0x0 |9 |foo1 |
符号 foo1 是定义用来提供共享库公共接口的唯一全局符号。特殊的自动缩减指令 "*" 可使所有其他全局符号缩减,以在要生成的目标文件中具有本地绑定。定义其他符号中介绍了此指令。关联的版本名称 SUNW_1.1 将生成版本定义。因此,共享库的公共接口包含与全局符号 foo1 关联的内部版本定义 SUNW_1.1。
每次使用版本定义或自动缩减指令生成目标文件时,还会创建基本版本定义。此基本版本是使用文件本身的名称定义的,用于将链接编辑器生成的所有保留符号关联在一起。有关这些保留符号的列表,请参见生成输出文件。
可以使用带有 -d 选项的 pvs(1) 来显示目标文件内包含的版本定义:
$ pvs -d libfoo.so.1 libfoo.so.1; SUNW_1.1; |
目标文件 libfoo.so.1 具有名为 SUNW_1.1 的内部版本定义以及基本版本定义 libfoo.so.1。
使用链接编辑器的 -z noversion 选项,可以由 mapfile 来定向符号缩减,但是会抑制创建版本定义。
从此初始版本定义开始,可以通过添加新接口和已更新功能来演变目标文件。例如,可以通过更新源文件 foo.c 和 data.c,将新函数 foo2 及其支持数据结构添加到目标文件中:
$ cat foo.c extern const char * _foo1; extern const char * _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 SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ nm -x libfoo.so.1 | grep "foo.$" [33] |0x00010644|0x00000004|OBJT |LOCL |0x0 |17 |_foo1 [34] |0x00010648|0x00000004|OBJT |LOCL |0x0 |17 |_foo2 [36] |0x000004bc|0x00000034|FUNC |GLOB |0x0 |9 |foo1 [37] |0x000004f0|0x00000034|FUNC |GLOB |0x0 |9 |foo2 |
符号 foo1 和 foo2 都定义为共享库公共接口的一部分。但是,其中每个符号都指定给不同的版本定义;foo1 指定给 SUNW_1.1,foo2 指定给 SUNW_1.2。
可以使用同时带有 -d、-v 和 -s 选项的 pvs(1) 来显示这些版本定义、版本继承和符号关联。
$ 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"; |
可以引入弱版本定义 (weak version definition) 来标识此更改:
$ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; 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}; |
空版本定义通过弱标志指示出来。通过这些弱版本定义 (weak version definition),应用程序可以通过绑定到与此功能关联的版本定义来检验是否存在特定的实现。绑定到版本定义一节更详细地说明了如何使用这些定义。
上述示例说明了添加到目标文件中的新版本定义如何继承任何现有的版本定义。还可以创建唯一且独立的版本定义。以下示例将两个新文件 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 SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; SUNW_1.2.1 { } SUNW_1.2; # Release X+2 SUNW_1.3a { # Release X+3 global: bar1; } SUNW_1.2; SUNW_1.3b { # Release X+3 global: bar2; } SUNW_1.2; |
同样,可以使用 pvs(1) 检查使用此 mapfile 时在 libfoo.so.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}; |
以下各节介绍如何使用这些版本定义记录来检验运行时绑定要求以及在创建目标文件过程中控制其绑定。