链接程序和库指南

获取新符号

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

在以下可能很常见的示例中,应用程序首先会将其他目标文件添加到其地址空间。然后,应用程序会使用 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 中搜索符号 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,应用程序可以测试是否存在其他符号。符号搜索采用重定位调用目标文件时所用的同一模型。 请参见缺省符号查找模型。例如,如果应用程序 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,可提供可预测的插入方法。在一般目标文件依赖项中使用此方法时应该十分小心,因为目标文件的实际装入顺序有时无法预测。