链接程序和库指南

符号查找

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

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

目标文件有两个属性会影响符号查找。第一个属性是请求目标文件的符号搜索范围,第二个属性是进程中每个目标文件提供的符号可见性。目标文件的搜索范围可以是:

world

目标文件可以查找进程中的任何其他全局目标文件。

group

目标文件只能查找同一中的目标文件。通过使用 dlopen(3C) 获取的目标文件或使用链接编辑器的 -B group 选项生成的目标文件创建的依赖项树构成一个唯一的组。

目标文件中符号的可见性可以是:

global

可在具有 world 搜索范围的任何目标文件中引用该目标文件的符号。

local

只能在构成同一组的其他目标文件中引用该目标文件的符号。

缺省情况下,使用 dlopen(3C) 获取的目标文件将被指定 world 符号搜索范围和 local 符号可见性。缺省符号查找模型一节将使用此缺省模型说明典型的目标文件组交互。定义全局目标文件隔离组目标文件分层结构等节将介绍使用 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() 请求

单个 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() 请求

多个 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() 请求

具有公共依赖项的多个 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) 调用的顺序的影响。如果符号 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() 打开第二个目标文件,则这两个目标文件都会被指定给一个唯一的组。这种情况可以防止一个目标文件在另一个目标文件中查找符号。

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

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

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

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

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