如何读取动态链接库中的符号表,并调用

我们知道动态加载的函数库是一类函数库,他可以在程序运行过程中的任何时间加载。它们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个plugin模块时才动态的加载。例如,Pluggable AuthenticationModules(PAM)系统就是用动态加载函数库来使得管理员可以配置和重新配置身份验证信息。
Linux系统下,DL函数库与其他函数库在格式上没有特殊的区别,我们前面提到过,它们创建的时候是标准的object格式。主要的区别就是这些函数库不是在程序链接的时候或者启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。
通常C语言环境下,需要包含这个头文件dlfcn.h
Linux中使用的函数和Solaris中一样,都是dlpoen() API。当时不是所有的平台都使用同样的接口,例如HP-UX使用hl_load()机制,而Windows平台用另外的其他的调用接口。如果你的目的是使得你的代码有很强的移植性,你应该使用一些wrapping函数库,这样的wrapping函数库隐藏不同的平台的接口区别。一种方法是使用glibc函数库中的对动态加载模块的支持,它使用一些潜在的动态加载函数库界面使得它们可以夸平台使用。具体可以参http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. 另外一个方法是使用libltdl,是GNUlibtool的一部分,可以进一步参考CORBA相关资料。

1) dlopen()

dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:
void * dlopen(const char *filename, int flag);

如果文件名filename是以“/”开头,也就是使用绝对路径,那么dlopne就直接使用它,而不去查找某些环境变量或者系统设置的函数库所在的目录了。否则dlopen()就会按照下面的次序查找函数库文件:
1. 环境变量LD_LIBRARY指明的路径。

2. /etc/ld.so.cache中的函数库列表。

3. /lib目录,然后/usr/lib。不过一些很老的a.out的loader则是采用相反的次序,也就是先查/usr/lib,然后是/lib。

Dlopen()函数中,参数flag的值必须是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含义是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'。

如果有好几个函数库,它们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。

dlopen()函数的返回值是一个句柄,然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败dlopen()就返回一个NULL。如果一个函数库被多次打开,它会返回同样的句柄。



如果一个函数库里面有一个输出的函数名字为_init,那么_init就会在dlopen()这个函数返回前被执行。我们可以利用这个函数在我的函数库里面做一些初始化的工作。我们后面会继续讨论这个问题的。

2) dlerror()
通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。
3) dlsym()

如果你加载了一个DL函数库而不去使用当然是不可能的了,使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已经打开的函数库里面查找给定的符号。这个函数如下定义:

void * dlsym(void *handle, char *symbol);

函数中的参数handle就是由dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。
如果dlsym()函数没有找到需要查找的symbol,则返回NULL。如果你知道某个symbol的值不可能是NULL或者0,那么就很好,你就可以根据这个返回结果判断查找的symbol是否存在了;不过,如果某个symbol的值就是NULL,那么这个判断就有问题了。标准的判断方法是先调用dlerror(),清除以前可能存在的错误,然后调用dlsym()来访问一个symbol,然后再调用dlerror()来判断是否出现了错误。一个典型的过程如下:

dlerror(); /* clear error code */

s = (actual_type) dlsym(handle, symbol_being_searched_for);

if ((err = dlerror()) != NULL)

{

/* handle error, the symbol wasn't found */

}

else

{

/* symbol found, its value is in s */

}



4) dlclose()

dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。真正释放的时候,如果函数库里面有_fini()这个函数,
则自动调用_fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。

5) DL Library Example

下面是一个例子。例子中调入math函数库,然后打印2.0的余弦函数值。例子中每次都检查是否出错。是个不错的范例:
  1. #include<stdio.h>
    #include <dlfcn.h>
    #include <stdlib.h>
    intmain(intargc,char*argv){
  2. void*handle;
  3. char*error;
  4. double(*cosine)(double);
  5. handle=dlopen("/lib/libm.so.6",RTLD_LAZY);
  6. if(!handle){
  7. fputs(dlerror(),stderr);
  8. exit(1);
  9. }
  10. cosine=dlsym(handle,"cos");
  11. if((error=dlerror())!=NULL){
  12. fputs(error,stderr);
  13. exit(1);
  14. }
  15. printf("%f",(*cosine)(2,0));
  16. dlclose(handle);
  17. return0;
  18. }
  1. 如果这个程序名字叫cosine.c,那么用下面的命令来编译:
    gcc -o cosine cosine.c -ldl
  2. 使用-ldl选项指明生成的对象模块需要使用共享库
  3. 注意事项:

    1.dlsym返回的指针是无类型的,要转换的指定的函数的类型。
    2.使用函数指针时的写法:(*pfunc)(word); 不能直接写成 pfunc(word);会段错误的
    3.C还不支持默认参数,写show_help时不能给msg以NULL的默认值
    3.编译时要使用共享库dl 其中有dlopen dlsynm dlerrordlclose 函数

    使用命令:

    ./mydlopen ./dlopen.so say aaaabbbdddd

    ------------------------------------分割------------------------------------------

    说明:pfunc = (int (*)(char *))dlsym(handle,func);

    其中char *是将参数强转,因为int (*pfunc)(char *str);是这样定义的。

    如果int (*pfunc)(char *str,int number);这样定义,那么强转的时候应该这样写:

    pfunc = (int (*)(char *,int))dlsym(handle,func);

    --------------------------------------分割-----------------------------------------

    编译的时候的说明,比如现在的文件目录树如下:

    1.work/

    2.|--head.h

    3.|--main.c

    4.|--func.c

    5.|--func2.c

    这个时候欲将func2.c编译成动态链接库dlfunc.so,而func2.c中的函数用到了head.h和func.c里面的变量和函数,这个时候应该使用命令:

    gcc -o dlfunc.so -shared func2.c func.c -I./

    这样就生成了dlfunc.so。

    然后编译连接主程序。

    gcc -o test main.c -ldl -I./

    就行了。


在Linux动态链接库的初始化通常是通过动态链接器(ld.so)来完成的。 当一个程序使用到了某个动态链接库时,操作系统会使用动态链接器来加载和初始化该库。动态链接器会检查程序对该库的引用,并根据指定的搜索路径来查找库文件。一旦找到库文件,动态链接器会将该库加载到进程的虚拟地址空间。 动态链接器在加载和初始化动态链接库时,会执行以下几个步骤: 1. 打开库文件:动态链接器会使用系统调用(如open)打开库文件,以便读取和加载库的代码和数据。 2. 解析符号:动态链接器会解析库的符号。这包括解析库所有的函数和全局变量的地址,并将其与程序对应的符号进行匹配。 3. 分配空间:动态链接器会为库代码和全局变量分配内存空间,并将其加载到进程的虚拟地址空间。 4. 修复引用:动态链接器会修改程序对库的符号的引用,使其指向在内存的正确地址。 5. 执行初始化:动态链接器会执行库的初始化函数,进行一些初始化操作,例如初始化全局变量、注册回调等。 6. 设置共享:动态链接器会将库设置为共享状态,这样其他进程也可以使用该库。 通过以上步骤,动态链接器能够成功地加载和初始化动态链接库,使得程序能够正确地使用库提供的功能和资源。动态链接库的使用不仅可以减小程序的体积,还可以提供代码的复用和维护的便利性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值