像window调用库文件一样,在linux下,也有相应的API因为加载库文件而存在。它们主要是以下几个函数:
函数名 | 功能描述 |
---|---|
dlopen | 打开对象文件,使其可被程序访问 |
dlsym | 获取执行了 dlopen 函数的对象文件中的函数的地址 |
dlerror | 该函数没有参数,它会在发生前面的错误时返回一个字符串,同时将其从内存 中清空; 在没有错误发生时返回 NULL, |
dlclose | 关闭目标文件。如果无需再调用共享对象的话,应用程序可以调用该方法来通知 操作系统不再需要句柄和对象引用了。它完全是按引用来计数的,所以同一个 共享对象的多个用户相互间不会发生冲突(只要还有一个用户在使用它, 它就会在内存中)。任何通过已关闭的对象的 |
实例代码(soTest.c):
1 #include <stdio.h> 2 #include <dlfcn.h> 3 4 int main(int argc, char *argv[]){ 5 void * libm_handle = NULL; 6 float (*cosf_method)(float); 7 char *errorInfo; 8 float result; 9 10 // dlopen 函数还会自动解析共享库中的依赖项。这样,如果您打开了一个依赖于其他共享库的对象,它就会自动加载它们。 11 // 函数返回一个句柄,该句柄用于后续的 API 调用 12 libm_handle = dlopen("libm.so", RTLD_LAZY ); 13 // 如果返回 NULL 句柄,表示无法找到对象文件,过程结束。否则的话,将会得到对象的一个句柄,可以进一步询问对象 14 if (!libm_handle){ 15 // 如果返回 NULL 句柄,通过dlerror方法可以取得无法访问对象的原因 16 printf("Open Error:%s.\n",dlerror()); 17 return 0; 18 } 19 20 // 使用 dlsym 函数,尝试解析新打开的对象文件中的符号。您将会得到一个有效的指向该符号的指针,或者是得到一个 NULL 并返回一个错误 21 cosf_method = dlsym(libm_handle,"cosf"); 22 errorInfo = dlerror();// 调用dlerror方法,返回错误信息的同时,内存中的错误信息被清空 23 if (errorInfo != NULL){ 24 printf("Dlsym Error:%s.\n",errorInfo); 25 return 0; 26 } 27 28 // 执行“cosf”方法 29 result = (*cosf_method)(0.0); 30 printf("result = %f.\n",result); 31 32 // 调用 ELF 对象中的目标函数后,通过调用 dlclose 来关闭对它的访问 33 dlclose(libm_handle); 34 35 return 0; 36 }
在这个例子中主要是调用了 math 库(libm.so)中的“cosf”函数,dlopen函数的第二个参数表示加载库文件的模式,主要有两种:RTLD_LAZY 暂缓决定,等有需要时再解出符号;RTLD_NOW 立即决定,返回前解除所有未决定的符号。另外记得引用包含API的头文件“#include <dlfcn.h>”(^_^)。
编译执行结果如下:
如果将代码12行中的库文件名改为一个不存在的库文件,运行后错误结果如下:
如果将代码21行中的函数名改为一个不存在的函数名,运行后错误结果如下:
本文主要简单讲述在linux下调用SO库文件的一些基本知识和注意点。
本实例在redhat 5.2的64系统下测试通过。
个人创作,欢迎指错。
牵扯到ELF格式,gcc编译选项待补,简单实用的说明一下,对Linux下的so文件有个实际性的认识。
1.so文件是什么?
2.怎么生成以及使用一个so动态库文件?
3.地址空间,以及线程安全.
4.库的初始化,解析:
5.使用我们自己库里的函数替换系统函数:
//-------------------------------------------------------------------------------
1.so文件是什么?
也是ELF格式文件,共享库(动态库),类似于DLL。节约资源,加快速度,代码升级简化。
知道这么多就够了,实用主义。等有了印象再研究原理。
2.怎么生成以及使用一个so动态库文件?
先写一个C文件:s.c
C代码
- #include <stdio.h>
- int count;
- void out_msg(const char *m)
- {//2秒钟输出1次信息,并计数
- for(;;) {printf("%s %d\n", m, ++count); sleep(2);}
- }
编译:得到输出文件libs.o
gcc -fPIC -g -c s.c -o libs.o
链接:得到输出文件libs.so
gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc
一个头文件:s.h
C代码
- #ifndef _MY_SO_HEADER_
- #define _MY_SO_HEADER_
- void out_msg(const char *m);
- #endif
再来一个C文件来引用这个库中的函数:ts.c
C代码
- #include <stdio.h>
- #include "s.h"
- int main(int argc, char** argv)
- {
- printf("TS Main\n");
- out_msg("TS ");
- sleep(5); //这句话可以注释掉,在第4节的时候打开就可以。
- printf("TS Quit\n");
- }
编译链接这个文件:得到输出文件ts
gcc -g ts.c -o ts -L. -ls
执行./ts,嗯:成功了。。。还差点
得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory
系统不能找到我们自己定义的libs.so,那么告诉他,修改变量LD_LIBRARY_PATH,为了方便,写个脚本:e(文件名就叫e,懒得弄长了)
#!/bin/sh
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
执行:./e &
屏幕上就开始不停有信息输出了,当然TS Quit你是看不到的,前面是个死循环,后面会用到这句
3.地址空间,以及线程安全:
如果这样:
./e &开始执行后,稍微等待一下然后再 ./e&,
这个时候屏幕信息会怎么样呢?全局变量count会怎么变化?
会是两个进程交叉输出信息,并且各自的count互不干扰,虽然他们引用了同一个so文件。
也就是说只有代码是否线程安全一说,没有代码是否是进程安全这一说法。
4.库的初始化,解析:
windows下的动态库加载,卸载都会有初始化函数以及卸载函数来完成库的初始化以及资源回收,linux当然也可以实现。
ELF文件本身执行时就会执行一个_init()函数以及_fini()函数来完成这个,我们只要把自己的函数能让系统在这个时候执行
就可以了。
修改我们前面的s.c文件:
C代码
- #include <stdio.h>
- void my_init(void) __attribute__((constructor)); //告诉gcc把这个函数扔到init section
- void my_fini(void) __attribute__((destructor)); //告诉gcc把这个函数扔到fini section
- void out_msg(const char *m)
- {
- printf(" Ok!\n");
- }
- int i; //仍然是个计数器
- void my_init(void)
- {
- printf("Init ... ... %d\n", ++i);
- }
- void my_fini(void)
- {
- printf("Fini ... ... %d\n", ++i);
- }
重新制作 libs.so,ts本是不用重新编译了,代码维护升级方便很多。
然后执行: ./e &
可以看到屏幕输出:(不完整信息,只是顺序一样)
Init
Main
OK
Quit
Fini
可以看到我们自己定义的初始化函数以及解析函数都被执行了,而且是在最前面以及最后面。
如果s.c中的sleep(5)没有注释掉,那么有机会:
./e&
./e&连续执行两次,那么初始化函数和解析函数也会执行两次,虽然系统只加载了一次libs.so。
如果sleep时候kill 掉后台进程,那么解析函数不会被执行。
5.使用我们自己库里的函数替换系统函数:
创建一个新的文件b.c:我们要替换系统函数malloc以及free(可以自己写个内存泄露检测工具了)
C代码
- #include <stdio.h>
- void* malloc(int size)
- {
- printf("My malloc\n");
- return NULL;
- }
- void free(void* ad)
- {
- printf("My free\n");
- }
老规矩,编译链接成一个so文件:得到libb.so
gcc -fPIC -g -c b.c -o libb.o
gcc -g -shared -Wl,-soname,libb.so -o libb.so -lc
修改s.c:重新生成libs.so
C代码
- void out_msg()
- {
- int *p;
- p = (int*)malloc(100);
- free(p);
- printf("Stop Ok!\n");
- }
修改脚本文件e:
#!/bin/sh
export LD_PRELOAD=${pwd}libb.so:${LD_PRELOAD}
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
关键就在LD_PRELOAD上了,这个路径指定的so将在所有的so之前加载,并且符号会覆盖后面加载的so文件中的符号。如果可执行文件的权限不合适(SID),这个变量会被忽略。
执行:./e &
嗯,可以看到我们的malloc,free工作了。
暂时就想到这么多了。