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

退出打印视图

更新时间: 2014 年 7 月
 
 

获取新符号

进程可以使用 dlsym(3C) 获取特定符号的地址。此函数采用句柄符号名称,并将符号地址返回给调用者。该句柄通过以下方式指示符号搜索:

  • 可通过指定目标文件的 dlopen(3C) 返回句柄。该句柄允许从指定目标文件及定义其依赖项树的目标文件获取符号。使用模式 RTLD_FIRST 返回的句柄仅允许从指定目标文件获取符号。

  • 可通过其值为 0 的路径名的 dlopen(3C) 返回句柄。该句柄允许从关联链接映射的启动目标文件及定义其依赖项树的目标文件获取符号。通常,启动目标文件为动态可执行文件。对于关联链接映射,该句柄还允许从通过 dlopen(3C) 获取且模式为 RTLD_GLOBAL 的任何目标文件获取符号。使用模式 RTLD_FIRST 返回的句柄仅允许从关联链接映射的启动目标文件获取符号。

  • 特殊句柄 RTLD_DEFAULTRTLD_PROBE 允许从关联链接映射的启动目标文件及定义其依赖项树的目标文件获取符号。此句柄还允许从通过 dlopen(3C) 获取且与调用者同属一组的任何目标文件获取符号。使用 RTLD_DEFAULTRTLD_PROBE 时采用在解析调用目标文件中的符号重定位时所用的同一模型。

  • 特殊句柄 RTLD_NEXT 允许从调用者链接映射列表中的下一个关联目标文件获取符号。

在以下可能很常见的示例中,应用程序首先会将其他目标文件添加到其地址空间。然后,应用程序会使用 dlsym(3C) 来查找函数或数据符号。接下来,应用程序将使用这些符号来调用这些新目标文件中提供的服务。文件 main.c 包含以下代码:

#include    <stdio.h>
#include    <dlfcn.h>

int main()
{
        void *handle;
        int  *dptr, (*fptr)();

        if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) {
                (void) printf("dlopen: %s\n", dlerror());
                return (1);
        }

        if (((fptr = (int (*)())dlsym(handle, "foo")) == NULL) ||
            ((dptr = (int *)dlsym(handle, "bar")) == NULL)) {
                (void) printf("dlsym: %s\n", dlerror());
                return (1);
        }

        return ((*fptr)(*dptr));
}

首先会在文件 foo.so.1 中搜索符号 foobar,然后在与此文件关联的所有依赖项中搜索。然后,使用 return() 语句中的单个参数 bar 来调用函数 foo

使用前面的文件 main.c 生成的应用程序 prog 包含下列依赖项。

$ ldd prog
        libc.so.1 =>     /lib/libc.so.1

如果在 dlopen(3C) 中指定的文件名的值为 0,则会首先在 prog 中搜索符号 foobar,然后在 /lib/libc.so.1 中搜索。

该句柄指示启动符号搜索所在根。搜索机制将从此根开始采用重定位符号查找中所述的模型。

如果无法找到需要的符号,dlsym(3C) 将返回 NULL 值。在此情况下,可使用 dlerror(3C) 来指示失败的真正原因。在以下示例中,应用程序 prog 无法找到符号 bar

$ prog
dlsym: ld.so.1: main: fatal: bar: can't find symbol

测试功能

使用特殊句柄 RTLD_DEFAULTRTLD_PROBE,应用程序可以测试是否存在其他符号。

RTLD_DEFAULT 句柄采用运行时链接程序所用的同一规则来解析调用目标文件中的任何符号引用。请参见缺省符号查找模型。应注意该模型的两个方面。

  • 与动态可执行文件中的同一符号引用匹配的符号引用将绑定到与可执行文件中的引用关联的过程链接表项。请参见过程链接表(特定于处理器)。动态链接的这种人为因素确保进程中的所有组件看到的是函数的单一地址。

  • 如果在进程中目前装入的目标文件中无法找到可以满足非弱符号引用的符号定义,将启动延迟装入回退。将对装入的所有动态目标文件重复执行此回退,从而装入所有暂挂的延迟可装入目标文件,以尝试解析符号。该模型对尚未完整定义其依赖项的目标文件进行补偿。但是,该补偿可能会破坏延迟装入的优点。如果找不到重定位符号,可能会装入不需要的目标文件,或是全面装入所有的延迟可装入目标文件。

RTLD_PROBE 采用与 RTLD_DEFAULT 类似的模型,但是与标有 RTLD_DEFAULT 的模型在两个方面有所不同。RTLD_PROBE 只能绑定到显式符号定义,而不能绑定到可执行文件中的任何过程链接表项。此外,RTLD_PROBE 不会启动一个全面的延迟装入回退。RTLD_PROBE 是用来检测现有进程中符号是否存在的最合适的标志。

RTLD_DEFAULTRTLD_PROBE 都可以启动显式延迟装入。目标文件可以引用函数,且该引用可以通过一个延迟可装入依赖项建立。调用该函数之前,可以使用 RTLD_DEFAULTRTLD_PROBE 测试函数是否存在。由于目标文件会引用函数,因此首先要尝试装入关联的延迟依赖项。随后遵循 RTLD_DEFAULTRTLD_PROBE 的规则以绑定到函数。在下面的示例中,RTLD_PROBE 调用用于触发延迟装入,以及在存在依赖项时绑定到装入的依赖项。

void foo()
{
        if (dlsym(RTLD_PROBE, "foo1")) {
                foo1(arg1);
                foo2(arg2);
                ....
}

要为功能性测试提供一个强大而灵活的模型,关联的延迟依赖项应显式标记为延迟。请参见提供 dlopen() 的替代项。该标记还在运行时提供更改延迟依赖项的方式。

RTLD_DEFAULTRTLD_PROBE 的使用为未定义的弱引用的使用提供了一个更强大的替代方案,如弱符号中所述。

使用插入

使用特殊句柄 RTLD_NEXT,应用程序可在符号作用域内查找下一个符号。例如,如果应用程序 prog 包含以下代码片段:

        if ((fptr = (int (*)())dlsym(RTLD_NEXT, "foo")) == NULL) {
                 (void) printf("dlsym: %s\n", dlerror());
                 return (1);
        }

        return ((*fptr)());

则会在与 prog 关联的共享目标文件(在此情况下为 /lib/libc.so.1)中搜索 foo。如果此代码片段包含在 Figure 3–1 中显示的示例的文件 B.so.1 中,则仅会在 C.so.1 中搜索 foo

使用 RTLD_NEXT 提供了使用符号插入的方法。例如,可通过前面的目标文件插入目标文件中的函数,然后扩充原始函数的处理。例如,在共享目标文件 malloc.so.1 中放置以下代码片段。

#include    <sys/types.h>
#include    <dlfcn.h>
#include    <stdio.h>

void *
malloc(size_t size)
{
        static void *(*fptr)() = 0;
        char        buffer[50];

        if (fptr == 0) {
                fptr = (void *(*)())dlsym(RTLD_NEXT, "malloc");
                if (fptr == NULL) {
                        (void) printf("dlopen: %s\n", dlerror());
                        return (NULL);
                }
        }

        (void) sprintf(buffer, "malloc: %#x bytes\n", size);
        (void) write(1, buffer, strlen(buffer));
        return ((*fptr)(size));
}

malloc.so.1 可插入到 malloc(3C) 通常所在的系统库 /lib/libc.so.1 之前。现在,在调用原始函数以完成分配之前,插入对 malloc() 的调用:

$ cc -o malloc.so.1 -G -K pic malloc.c
$ cc -o prog file1.o file2.o .... -R. malloc.so.1
$ prog
malloc: 0x32 bytes
malloc: 0x14 bytes
....

或者,可使用以下命令实现相同插入:

$ cc -o malloc.so.1 -G -K pic malloc.c
$ cc -o prog main.c
$ LD_PRELOAD=./malloc.so.1  prog
malloc: 0x32 bytes
malloc: 0x14 bytes
....

注 - 使用任何插入方法的用户在处理任何可能的递归时都必须小心。前面的示例使用 sprintf(3C),而不是直接使用 printf(3C) 来格式化诊断消息,以避免由于 printf(3C) 可能使用 malloc(3C) 而导致产生递归。

在动态可执行文件或预装入的目标文件中使用 RTLD_NEXT,可提供可预测的插入方法。在一般目标文件依赖项中使用此方法时应该十分小心,因为目标文件的实际装入顺序有时无法预测。