根据其他共享目标文件生成动态可执行文件或共享目标文件时,会在生成的目标文件中记录这些依赖项。有关更多详细信息,请参见共享目标文件处理和记录共享目标文件名称。如果依赖项还包含版本定义,则会在所生成的目标文件中记录关联的版本依赖项。
以下示例使用上一节中的数据文件生成一个适用于编译时环境的共享目标文件 libfoo.so.1。
$ cc -o libfoo.so.1 -h libfoo.so.1 -M mapfile -G foo.o bar.o data.o $ ln -s libfoo.so.1 libfoo.so $ 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.1 [WEAK]: {SUNW_1.2}: SUNW_1.2.1; SUNW_1.3a: {SUNW_1.2}: bar1; SUNW_1.3a; SUNW_1.3b: {SUNW_1.2}: bar2; SUNW_1.3b
共享目标文件 libfoo.so.1 提供六个公共接口。其中 SUNW_1.1、SUNW_1.2、SUNW_1.3a 以及 SUNW_1.3b 这四个接口定义了导出的符号名称。接口 SUNW_1.2.1 描述了目标文件的一个内部实现更改。接口 libfoo.so.1 定义了若干保留标签。使用 libfoo.so.1 作为依赖项创建的动态目标文件记录了动态目标文件绑定到的接口的版本名称。
以下示例创建了一个引用 foo1 和 foo2 符号的应用程序。使用 pvs(1) 及 –r 选项可以检查该应用程序中记录的版本控制依赖项信息。
$ cat prog.c extern void foo1(); extern void foo2(); main() { foo1(); foo2(); } $ cc -o prog prog.c -L. -R. -lfoo $ pvs -r prog libfoo.so.1 (SUNW_1.2, SUNW_1.2.1);
在本示例中,应用程序 prog 绑定到两个接口:SUNW_1.1 和 SUNW_1.2。这两个接口分别提供全局符号 foo1 和 foo2。
因为版本定义 SUNW_1.1 在 libfoo.so.1 中定义为由版本定义 SUNW_1.2 继承,所以您只需记录一个依赖项。这种继承是为了实现版本定义依赖项的标准化。这种标准化可以减少目标文件中保留的版本信息量,并减少运行时需要的版本验证处理。
因为应用程序 prog 是根据包含弱版本定义 SUNW_1.2.1 的共享目标文件实现而生成的,所以也记录了该依赖项。虽然此版本定义被定义为继承版本定义 SUNW_1.2,但是其弱版本的性质阻止了其使用 SUNW_1.1 进行标准化。弱版本定义会导致单独记录依赖项的情况。
如果有多个弱版本定义彼此继承,则这些定义将以与非弱版本定义一样的方式进行标准化。
在执行应用程序时,运行时链接程序会验证是否存在任何来自绑定到的目标文件的已记录版本定义。使用 ldd(1) 及 –v 选项可以显示此验证。例如,通过对应用程序 prog 运行 ldd(1),显示出在依赖项 libfoo.so.1 中正确发现版本定义依赖项。
$ ldd -v prog find object=libfoo.so.1; required by prog libfoo.so.1 => ./libfoo.so.1 find version=libfoo.so.1; libfoo.so.1 (SUNW_1.2) => ./libfoo.so.1 libfoo.so.1 (SUNW_1.2.1) => ./libfoo.so.1 ....
如果找不到非弱版本定义依赖项,应用程序初始化期间会发生致命错误。将在不给出任何提示的情况下忽略任何无法找到的弱版本定义依赖项。例如,如果在应用程序 prog 运行的环境中,libfoo.so.1 仅包含版本定义 SUNW_1.1,则会发生以下致命错误。
$ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; $ prog ld.so.1: prog: fatal: libfoo.so.1: version 'SUNW_1.2' not \ found (required by file prog)
如果 prog 未记录任何版本定义依赖项,则符号 foo2 的缺失会导致运行时的致命重定位错误。此重定位错误可能在进程初始化或进程执行期间发生。如果应用程序的执行路径不调用函数 foo2,则错误情况可能根本不会发生。请参见重定位错误。
版本定义依赖项也可以即时指出应用程序所需接口的可用性。
例如,prog 可能运行在这样一个环境中,在该环境中,libfoo.so.1 仅包含版本定义 SUNW_1.1 和 SUNW_1.2 。在这种情况下,所有非弱版本定义的要求均得到满足。缺少弱版本定义 SUNW_1.2.1 被认为是非致命的。在这种情况下,不会出现运行时错误。
$ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}; $ prog string used by foo1() string used by foo2()
使用 ldd(1) 可以显示所有无法找到的版本定义。
$ ldd prog libfoo.so.1 => ./libfoo.so.1 libfoo.so.1 (SUNW_1.2.1) => (version not found) ....
在运行时,如果依赖项的实现不包含版本定义信息,则会在不给出任何提示的情况下忽略该依赖项的任何版本验证。在从非版本化的共享目标文件过渡到版本化的共享目标文件时,此策略可以提供一定程度的向下兼容性。ldd(1) 总是可以用于显示任何版本要求差异。
版本定义符号还提供一种机制,用于验证 dlopen(3C) 所获取的目标文件的版本要求。使用 dlopen(3C) 添加到进程地址空间的目标文件不会自动进行版本依赖项验证。因此,dlopen(3C) 的调用者负责验证是否符合任何版本控制要求。
通过使用 dlsym(3C) 查找关联的版本定义符号,可以验证某个需要的版本定义是否存在。以下示例使用 dlopen(3C) 将共享目标文件 libfoo.so.1 添加到一个进程。然后验证接口 SUNW_1.2 的可用性。
#include <stdio.h> #include <dlfcn.h> main() { void *handle; const char *file = "libfoo.so.1"; const char *vers = "SUNW_1.2"; .... if ((handle = dlopen(file, (RTLD_LAZY | RTLD_FIRST))) == NULL) { (void) printf("dlopen: %s\n", dlerror()); return (1); } if (dlsym(handle, vers) == NULL) { (void) printf("fatal: %s: version '%s' not found\n", file, vers); return (1); } ....