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

退出打印视图

更新时间: 2014 年 7 月
 
 

重定位处理

在找到并装入所有目标文件后,运行时链接程序必须处理每个目标文件并执行所有必需的重定位。另外,还必须以同一方式重定位使用 dlopen(3C) 引入进程地址空间中的所有目标文件。

对于简单应用程序,此过程很简单。但是,对于使用较复杂的应用程序(包含涉及多个目标文件的 dlopen(3C) 调用,且可能包含公共依赖项)的用户,则此过程可能相当重要。

可以根据重定位发生的时间对其进行分类。运行时链接程序的缺省行为是在初始化时处理所有即时引用重定位,以及在进程执行期间处理所有延迟引用(此机制通常称为延迟绑定)。

当模式定义为 RTLD_LAZY 时,此相同的机制可应用于任何使用 dlopen(3C) 添加的目标文件。替代方法是要求在添加目标文件时立即执行目标文件的所有重定位。您可以使用 RTLD_NOW 模式,也可以使用链接编辑器的 –z now 选项在生成目标文件时将此要求记录在目标文件中。此重定位要求将传播至要打开的目标文件的所有依赖项。

重定位还可分为非符号重定位和符号重定位。本节的余下部分将讨论有关符号重定位的问题(无论这些重定位何时进行),并重点介绍符号查找的某些细节信息。

符号查找

如果 dlopen(3C) 获取的目标文件引用全局符号,则运行时链接程序必须从构成进程的目标文件池中查找此符号。如果缺少直接绑定,则会将缺省符号搜索模型应用于通过 dlopen() 获取的目标文件。但是,dlopen() 模式以及构成进程的目标文件的属性允许使用替代的符号搜索模型。

需要直接绑定的目标文件将直接在关联的依赖项中搜索符号(尽管要维护后面介绍的所有属性)。请参见Chapter 6, 直接绑定


注 - 指定了 STV_SINGLETON 可见性的符号使用缺省符号搜索绑定,无论 dlopen(3C) 属性如何都是如此。请参见Table 12–23

缺省情况下,使用 dlopen(3C) 获取的目标文件将被指定全局符号搜索作用域和局部符号可见性。缺省符号查找模型一节将使用此缺省模型说明典型的目标文件组交互。定义全局目标文件隔离组目标文件分层结构部分介绍使用 dlopen(3C) 模式和文件属性扩展缺省符号查找模型的示例。

缺省符号查找模型

对于通过基本 dlopen(3C) 添加的每个目标文件,运行时链接程序将首先在动态可执行文件中查找符号。之后,运行时链接程序在进程初始化期间提供的每个目标文件中查找。如果找不到该符号,运行时链接程序将继续搜索。接下来,运行时链接程序会在通过 dlopen(3C) 获取的目标文件及其依赖项中查找。

使用缺省符号查找模型时,可以转换到延迟装入环境。如果在当前装入的目标文件中找不到某符号,则会处理所有暂挂的延迟装入目标文件,以尝试查找该符号。此装入是对尚未完整定义其依赖项的目标文件的补偿。但是,该补偿可能会破坏延迟装入的优点。

在以下示例中,动态可执行文件 prog 和共享目标文件 B.so.1 具有下列依赖项。

$ ldd prog
        A.so.1 =>        ./A.so.1
$ ldd B.so.1
        C.so.1 =>        ./C.so.1

如果 prog 通过 dlopen(3C) 获取共享目标文件 B.so.1,则会首先在 prog 中查找重定位共享目标文件 B.so.1C.so.1 所需的任何符号,然后依次在 A.so.1B.so.1C.so.1 中进行查找。在此简单示例中,将通过 dlopen(3C) 获取的共享目标文件视作已在对应用程序的原始链接编辑结束时将它们添加进来。例如,可以采用图解方式表示前面列表中引用的目标文件,如下图所示。

图 3-1  单个 dlopen() 请求

image:单个 dlopen() 请求。

通过 dlopen(3C) 获取的目标文件(显示为阴影块)所需的任何符号查找,将从动态可执行文件 prog 继续执行一直到最后一个共享目标文件 C.so.1

此符号查找是按装入目标文件时为目标文件指定的属性建立的。请记住,已为动态可执行文件及随其装入的所有依赖项指定了全局符号可见性,并且为新目标文件指定了全局符号搜索作用域。因此,新目标文件能够在原始目标文件中查找符号。新目标文件也会构成一个唯一的组,其中每个目标文件都具有局部符号可见性。因此,该组中的每个目标文件都可在其他组成员中查找符号。

这些新目标文件不会影响应用程序或其初始依赖项所需的正常符号查找。例如,如果 A.so.1 要求在执行了前面的 dlopen(3C) 后进行函数重定位,则运行时链接程序对重定位符号的正常搜索是首先在 prog 中查找,然后在 A.so.1 中查找。运行时链接程序不会继续在 B.so.1C.so.1 中查找。

此符号查找同样是按装入目标文件时为目标文件指定的属性建立的。对于动态可执行文件及随其装入的所有依赖项,都指定全局符号搜索作用域。此作用域不允许它们在仅提供局部符号可见性的新目标文件中查找符号。

这些符号搜索和符号可见性属性用于维护目标文件之间的关联。这些关联基于它们引入进程地址空间的情况以及目标文件之间的依赖项关系。将与给定 dlopen(3C) 关联的目标文件指定给唯一的组,可以确保仅允许与同一 dlopen(3C) 关联的目标文件在目标文件本身及其关联的依赖项中查找符号。

定义目标文件之间的关联的概念在多次执行 dlopen(3C) 的应用程序中更为清晰。例如,假定共享目标文件 D.so.1 具有以下依赖项:

$ ldd D.so.1
        E.so.1 =>         ./E.so.1

并且 prog 应用程序使用 dlopen(3C) 装入此共享目标文件及共享目标文件 B.so.1。下图说明了目标文件之间的符号查找关系。

图 3-2  多个 dlopen() 请求

image:多个 dlopen() 请求。

假定 B.so.1D.so.1 都包含符号 foo 的定义,C.so.1E.so.1 都包含需要此符号的重定位。由于目标文件与唯一的组关联,因此 C.so.1 绑定到 B.so.1 中的定义,而 E.so.1 绑定到 D.so.1 中的定义。此机制用于为通过多次调用 dlopen(3C) 获取的目标文件提供最直观的绑定。

在到现在为止已介绍的情况中使用目标文件时,执行每个 dlopen(3C) 的顺序对生成的符号绑定没有影响。但是,如果目标文件具有公共依赖项,则生成的绑定可能会受到进行 dlopen(3C) 调用的顺序的影响。

在以下示例中,共享目标文件 O.so.1P.so.1 具有相同的公共依赖项。

$ ldd O.so.1
        Z.so.1 =>        ./Z.so.1
$ ldd P.so.1
        Z.so.1 =>        ./Z.so.1

在此示例中,prog 应用程序将对其中每个共享目标文件执行 dlopen(3C)。由于共享目标文件 Z.so.1O.so.1P.so.1 的公共依赖项,因此将为与两次 dlopen(3C) 调用关联的两个组指定 Z.so.1。此关系如下图所示。

图 3-3  具有公共依赖项的多个 dlopen() 请求

image:具有公共依赖项的多个 dlopen() 请求。

Z.so.1 可同时供 O.so.1P.so.1 用于查找符号。更重要的是,就 dlopen(3C) 排序而言,Z.so.1 还可用于同时在 O.so.1P.so.1 中查找符号。

因此,如果 O.so.1P.so.1 同时包含符号 foo 的定义(这是 Z.so.1 重定位所需的),则进行的实际绑定不可预测,因为它会受到 dlopen(3C) 调用的顺序的影响。如果符号l foo 的功能在定义它的两个共享目标文件间不同,则在 Z.so.1 中执行代码的整体结果可能会因应用程序的 dlopen(3C) 排序而异。

定义全局目标文件

通过使用 RTLD_GLOBAL 标志扩充模式参数,可将为通过 dlopen(3C) 获取的目标文件缺省指定的局部符号可见性提升为全局可见性。在此模式下,具有全局符号搜索作用域的任何其他目标文件可使用通过 dlopen(3C) 获取的所有目标文件来查找符号。

此外,通过 dlopen(3C) 获取的、并且带有 RTLD_GLOBAL 标志的任何目标文件都可供使用 dlopen() 及值为 0 的路径名的符号查找使用。


注 - 如果某个组成员定义了局部符号可见性,并且被另一个定义了全局符号可见性的组引用,则该目标文件的可见性将变为局部和全局可见性的串联。即使以后删除该全局组引用,也会保留此属性提升。
隔离组

通过使用 RTLD_GROUP 标志扩充模式参数,可将为通过 dlopen(3C) 获取的目标文件缺省指定的全局符号搜索作用域缩小为组。在此模式下,仅允许通过 dlopen(3C) 获取的所有目标文件在其各自的组中查找符号。

使用链接编辑器的 –B group 选项,可在生成目标文件时为其指定组符号搜索作用域。


注 - 如果某个组成员定义了组搜索要求,并且被另一个定义了全局搜索要求的组引用,则该目标文件的搜索要求将变为组和全局搜索的串联。即使以后删除该全局组引用,也会保留此属性提升。
目标文件分层结构

如果初始目标文件是从 dlopen(3C) 获取的,并且使用 dlopen() 打开第二个目标文件,则这两个目标文件都会被指定给一个唯一的组。这种情况可以防止一个目标文件在另一个目标文件中查找符号。

在某些实现中,初始目标文件必须导出符号以便重定位第二个目标文件。通过以下两种机制之一,可以满足此要求:

  • 使初始目标文件成为第二个目标文件的显式依赖项。

  • 使用 RTLD_PARENT 模式标志对第二个目标文件执行 dlopen(3C)

如果初始目标文件是第二个目标文件的显式依赖项,则会将该初始目标文件指定给第二个目标文件所在的组。因此,初始目标文件可以为第二个目标文件的重定位提供符号。

如果许多目标文件可使用 dlopen(3C) 打开第二个目标文件,并且每个初始目标文件必须导出相同符号以满足第二个目标文件重定位的需要,则不能对第二个目标文件指定显式依赖项。在此情况下,可使用 RTLD_PARENT 标志扩充第二个目标文件的 dlopen(3C) 模式。此标志将导致第二个目标文件所在的组以显式依赖项的方式传播至初始目标文件。

这两种方法之间有一点区别。如果指定显式依赖项,则依赖项本身将成为第二个目标文件的 dlopen(3C) 依赖关系树的一部分,从而可用于使用 dlsym(3C) 的符号查找。如果使用 RTLD_PARENT 获取第二个目标文件,则使用 dlsym(3C) 的符号查找不能使用初始目标文件。

如果第二个目标文件是通过 dlopen(3C) 从具有全局符号可见性的初始目标文件获取的,则 RTLD_PARENT 模式既是冗余的,也是无害的。从应用程序或应用程序的依赖项之一调用 dlopen(3C) 时,通常会发生这种情况。