进程可以使用 dlsym(3C) 获取特定符号的地址。此函数采用句柄和符号名称,并将符号地址返回给调用方。该句柄通过以下方式指示符号搜索:
可通过指定目标文件的 dlopen(3C) 返回句柄。该句柄允许从指定目标文件及定义其依赖项树的目标文件获取符号。使用模式 RTLD_FIRST 返回的句柄仅允许从指定目标文件获取符号。
可通过其值为 0 的路径名的 dlopen(3C) 返回句柄。该句柄允许从关联链接映射的启动目标文件及定义其依赖项树的目标文件获取符号。通常,启动目标文件为动态可执行文件。对于关联链接映射,该句柄还允许从通过 dlopen(3C) 获取且模式为 RTLD_GLOBAL 的任何目标文件获取符号。使用模式 RTLD_FIRST 返回的句柄仅允许从关联链接映射的启动目标文件获取符号。
特殊句柄 RTLD_DEFAULT 和 RTLD_PROBE 允许从关联链接映射的启动目标文件及定义其依赖项树的目标文件获取符号。此句柄还允许从通过 dlopen(3C) 获取且与调用方同属一组的任何目标文件获取符号。 使用 RTLD_DEFAULT 或 RTLD_PROBE 时采用在解析调用目标文件中的符号重定位时所用的同一模型。
在以下可能很常见的示例中,应用程序首先会将其他目标文件添加到其地址空间。然后,应用程序会使用 dlsym(3C) 来查找函数或数据符号。接下来,应用程序将使用这些符号来调用这些新目标文件中提供的服务。文件 main.c 包含以下代码:
#include <stdio.h> #include <dlfcn.h> main() { void * handle; int * dptr, (* fptr)(); if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); exit (1); } if (((fptr = (int (*)())dlsym(handle, "foo")) == NULL) || ((dptr = (int *)dlsym(handle, "bar")) == NULL)) { (void) printf("dlsym: %s\n", dlerror()); exit (1); } return ((*fptr)(*dptr)); } |
首先会在文件 foo.so.1 中搜索符号 foo 和 bar,然后在与此文件关联的所有依赖项中搜索。接下来,在 return() 语句中使用单个参数 bar 调用函数 foo。
使用前面的文件 main.c 生成的应用程序 prog 包含下列依赖项。
$ ldd prog libc.so.1 => /lib/libc.so.1 |
如果在 dlopen(3C) 中指定的文件名的值为 0,则会首先在 prog 中搜索符号 foo 和 bar,然后在 /lib/libc.so.1 中搜索。
该句柄指示启动符号搜索所在根。搜索机制将从此根开始采用重定位符号查找中所述的模型。
如果找不到所需符号,则 dlsym(3C) 会返回 NULL 值。在此情况下,可使用 dlerror(3C) 来指示失败的真正原因。在以下示例中,应用程序 prog 找不到符号 bar。
$ prog dlsym: ld.so.1: main: fatal: bar: can't find symbol |
使用特殊句柄 RTLD_DEFAULT 和 RTLD_PROBE,应用程序可以测试是否存在其他符号。符号搜索采用重定位调用目标文件时所用的同一模型。 请参见缺省符号查找模型。例如,如果应用程序 prog 包含以下代码片段:
if ((fptr = (int (*)())dlsym(RTLD_DEFAULT, "foo")) != NULL) (*fptr)(); |
则会首先在 prog 中搜索 foo,然后在 /lib/libc.so.1 中搜索。如果此代码片段包含在图 3–1 中显示的示例的文件 B.so.1 中,则会继续在 B.so.1 中搜索 foo,然后在 C.so.1 中搜索。
此机制为未定义的弱引用的使用提供了强大而灵活的替代方案,如弱符号中所述。
使用特殊句柄 RTLD_NEXT,应用程序可在符号范围内查找下一个符号。例如,如果应用程序 prog 包含以下代码片段:
if ((fptr = (int (*)())dlsym(RTLD_NEXT, "foo")) == NULL) { (void) printf("dlsym: %s\n", dlerror()); exit (1); } return ((*fptr)()); |
则会在与 prog 关联的共享库(在此情况下为 /lib/libc.so.1)中搜索 foo。如果此代码片段包含在图 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 (0); } } (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,可提供可预测的插入方法。在一般目标文件依赖项中使用此方法时应该十分小心,因为目标文件的实际装入顺序有时无法预测。