Oracle® Solaris 11.2 链接程序和库指南

退出打印视图

更新时间: 2014 年 7 月
 
 

绑定到版本定义

根据其他共享目标文件生成动态可执行文件或共享目标文件时,会在生成的目标文件中记录这些依赖项。有关更多详细信息,请参见共享目标文件处理记录共享目标文件名称。如果依赖项还包含版本定义,则会在所生成的目标文件中记录关联的版本依赖项。

以下示例使用上一节中的数据文件生成一个适用于编译时环境的共享目标文件 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.1SUNW_1.2SUNW_1.3a 以及 SUNW_1.3b 这四个接口定义了导出的符号名称。接口 SUNW_1.2.1 描述了目标文件的一个内部实现更改。接口 libfoo.so.1 定义了若干保留标签。使用 libfoo.so.1 作为依赖项创建的动态目标文件记录了动态目标文件绑定到的接口的版本名称。

以下示例创建了一个引用 foo1foo2 符号的应用程序。使用 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.1SUNW_1.2。这两个接口分别提供全局符号 foo1foo2

因为版本定义 SUNW_1.1libfoo.so.1 中定义为由版本定义 SUNW_1.2 继承,所以您只需记录一个依赖项。这种继承是为了实现版本定义依赖项的标准化。这种标准化可以减少目标文件中保留的版本信息量,并减少运行时需要的版本验证处理。

因为应用程序 prog 是根据包含弱版本定义 SUNW_1.2.1 的共享目标文件实现而生成的,所以也记录了该依赖项。虽然此版本定义被定义为继承版本定义 SUNW_1.2,但是其弱版本的性质阻止了其使用 SUNW_1.1 进行标准化。弱版本定义会导致单独记录依赖项的情况。

如果有多个弱版本定义彼此继承,则这些定义将以与非弱版本定义一样的方式进行标准化。


注 - 可以使用链接编辑器的 –z noversion 选项来抑制记录版本依赖项。

在执行应用程序时,运行时链接程序会验证是否存在任何来自绑定到的目标文件的已记录版本定义。使用 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
    ....

注 - ldd(1) 中使用 –v 选项可以输出详细结果。将生成一份包含所有依赖项和所有版本控制要求的递归列表。

如果找不到非弱版本定义依赖项,应用程序初始化期间会发生致命错误。将在不给出任何提示的情况下忽略任何无法找到的弱版本定义依赖项。例如,如果在应用程序 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.1SUNW_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) 总是可以用于显示任何版本要求差异。


注 - 环境变量 LD_NOVERSION 可以用于抑制所有运行时版本控制验证。

验证新增目标文件中的版本

版本定义符号还提供一种机制,用于验证 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);
        }
        ....

注 - 使用 dlopen(3C) 的标志 RTLD_FIRST 可以确保将 dlsym(3C) 搜索限制在 libfoo.so.1