链接程序和库指南

延迟装入动态依赖项

在内存中装入动态库时,将会检查该目标文件的任何其他依赖项。缺省情况下,将立即装入存在的所有依赖项。此循环会一直继续,直到找遍整个依赖项树为止。最后,解析由重定位指定的所有目标文件间数据引用。无论应用程序在执行期间是否引用了这些依赖项中的代码,都会执行这些操作。

在延迟装入模型中,标记为延迟装入的所有依赖项仅在显式引用时装入。通过利用函数调用的延迟绑定,依赖项装入将会一直延迟,直到第一次引用该函数。因此,绝不会装入从不引用的目标文件。

重定位引用可以是即时的,也可以是延迟的。因为初始化目标文件时必须解析即时引用,所以必须即时装入满足此引用要求的任何依赖项。因此,将这类依赖项标识为延迟可装入几乎没有效果。 请参见执行重定位的时间。通常,建议不要在动态库之间进行即时引用。

链接编辑器对调试库 liblddbg 的引用将使用延迟装入。由于不会经常进行调试,因此每次调用链接编辑器时装入此库既无必要又需要很大开销。通过指示可以延迟装入此库,处理库的开销将转至要求调试输出的那些调用。

实现延迟装入模型的替代方法是在需要时使用 dlopen()dlsym() 来装入并绑定到依赖项。如果 dlsym() 引用数很少,则此模型非常理想。如果在链接编辑时不知道依赖项名称或位置,则此模型也很适用。对于更复杂的与已知依赖项的交互,对正常符号引用进行编码并指定要延迟装入的依赖项更为简单。

通过链接编辑器选项 -z lazyload-z nolazyload,可将目标文件分别指定为延迟装入或正常装入。这些选项在链接编辑命令行上与位置相关。该选项之后的任何依赖项都将采用其指定的装入属性。缺省情况下,-z nolazyload 选项有效。

以下简单程序具有依赖项 libdebug.so.1。动态节 (.dynamic) 显示 libdebug.so.1 被标记为延迟装入。符号信息节 (.SUNW_syminfo) 显示触发 libdebug.so.1 装入的符号引用。


$ cc -o prog prog.c -L. -zlazyload -ldebug -znolazyload -lelf -R'$ORIGIN'

$ elfdump -d prog

 

Dynamic Section:  .dynamic

     index  tag           value

       [0]  POSFLAG_1     0x1           [ LAZY ]

       [1]  NEEDED        0x123         libdebug.so.1

       [2]  NEEDED        0x131         libelf.so.1

       [3]  NEEDED        0x13d         libc.so.1

       [4]  RUNPATH       0x147         $ORIGIN

       ...

$ elfdump -y prog

 

Syminfo section: .SUNW_syminfo

     index  flgs        bound to        symbol

      ....

      [52]  DL      [1] libdebug.so.1   debug

值为 LAZYPOSFLAG_1 指示应该延迟装入下面的 NEEDEDlibdebug.so.1。由于 libelf.so.1 没有前述的 LAZY 标志,因此该库将在程序初始启动时装入。


注 –

libc.so.1 具有特殊的系统要求,该要求指出不应延迟装入该文件。 如果在处理 libc.so.1-z lazyload 有效,则实际上会忽略该标志。


使用延迟装入时,可能需要准确声明在应用程序使用的目标文件中的依赖项和运行路径。例如,假定有两个目标文件 libA.solibB.so,它们同时引用了 libX.so 中的符号。libA.solibX.so 声明为依赖项,但 libB.so 没有这样操作。通常,将 libA.solibB.so 一起使用时,libB.so 可以引用 libX.so,因为 libA.so 使此依赖项可用。但是,如果 libA.solibX.so 声明为延迟装入,则在 libB.so 引用此依赖项时可能不会装入 libX.so。如果 libB.solibX.so 声明为依赖项,但未能提供查找该依赖项所需的运行路径,则可能会出现类似错误。

无论是否延迟装入,动态库都应声明其所有依赖项以及如何查找这些依赖项。对于延迟装入,此依赖项信息更为重要。


注 –

通过将环境变量 LD_NOLAZYLOAD 设置为非空值,可在运行时禁用延迟装入。


提供 dlopen() 的替代项

延迟装入可以提供替代 dlopen(3C)dlsym(3C) 的方法。 请参见运行时链接编程接口。例如,libfoo.so.1 中的以下代码将验证是否装入了目标文件,然后调用该目标文件提供的接口。


void foo()

{

    void * handle;



    if ((handle = dlopen("libbar.so.1", RTLD_LAZY)) != NULL) {

        int (* fptr)();



        if ((fptr = (int (*)())dlsym(handle, "bar1")) != NULL)

            (*fptr)(arg1);

        if ((fptr = (int (*)())dlsym(handle, "bar2")) != NULL)

            (*fptr)(arg2);

        ....

}

如果提供所需接口的目标文件满足下列条件,则可以简化此代码。

使用延迟装入,可以实现同样的 libbar.so.1 推迟装入。在此情况下,对函数 bar1() 的引用将导致延迟装入关联的依赖项。此外,标准函数调用可用于编译器或 lint(1) 验证。


void foo()

{

    bar1(arg1);

    bar2(arg2);

    ....

}

$ cc -G -o libfoo.so.1 foo.c -L. -zlazyload -zdefs -lbar -R'$ORIGIN'

但是,如果提供所需接口的目标文件并非始终可用,则此模型会失败。在此情况下,最好能够在不必知道依赖项名称的情况下测试依赖项是否存在。需要一种测试满足函数引用要求的依赖项可用性的方法。

带有 RTLD_PROBE 句柄的 dlsym(3C) 可用于验证依赖项是否存在以及是否将其装入。例如,对 bar1() 的引用可以验证在链接编辑时建立的延迟依赖项是否可用。此测试可用于控制对依赖项以使用 dlopen(3C) 的方式提供的函数的引用。


void foo()

{

    if (dlsym(RTLD_PROBE, "bar1")) {

        bar1(arg1);

        bar2(arg2);

        ....

}

此方法允许安全推迟装入已记录的依赖项以及标准函数调用。


注 –

特殊句柄 RTLD_DEFAULT 提供的机制与使用 RTLD_PROBE 类似。但是,使用 RTLD_DEFAULT 可能会导致在尝试查找的符号不存在时处理所有暂挂延迟装入目标文件。此装入是对尚未完整定义其依赖项的目标文件的补偿。但是,该补偿可能会破坏延迟装入的优点。

建议使用 -z defs 选项来生成利用延迟装入的所有目标文件。