链接时定位库
无论在什么时候,只要链接程序就需要查找库,编译器会查找指定的目录列表。这些目录是否被包含进查询路径,依赖于使用哪种竞争模式 ld(ld是链接器),编译的时候如何配置 ld,以及命令行指定的目录。大多数的系统库放置的位置是目录/lib
和/usr/lib
中,这是系统配置好的,因此会自动查找这两个目录。通过使用一个或者多个-L选项,指定其他的查询目录。命令在执行的时候就会直接到我们指定的目录下寻找链接文件。
实例:/home/lib目录下存放着我们的库文件libtest.a(静态库),编译的时候命令如下:
gcc -L/home/lib mian.o -o main -ltest
执行命令结束,我们就可以得到最终的目标文件。
运行时载入库
只要程序被链接想要使用共享库,就必须在运行的时候能够找到共享库的位置。因为是通过名字确定库,而不是通过目录定位库,因此可以在链接程序的时候使用一个库,而在运行的时候使用另一个库。如过我们改变库文件的版本号,而没有改变程序的版本,在运行的时候会出现问题,所以很多库会把版本号作为名字的一部分,例如:libm.so或libutil-2.2.4.so。
无论何时载入程序并打算运行的时候,共享库都应该位于以下的位置:
- 环境变量LD_LIBRARY_PATH列出的所有用分号分割的目录;
- 文件/etc/ld.so.cache中找到的库的列表,由工具ldconfig维护
- 目录/usr
- 目录/usr/lib
如果想要知道载入了那个库,以及确切的了解应用程序使用的那个库,可以使用ldd。命令格式如下:
ldd option filename
option可以表示的选项如下:
-d:进程数据重寻址
-r:进程数据和函数重寻址
-u:打印出未使用的直接依赖关系
-v:打印出所有的信息
实例:我们当前目录下存在一个可执行文件main。在命令行输入命令:
ldd -v main
显示的信息如下:
linux-vdso.so.1 (0x00007ffe6d730000)
libadd.so => /usr/lib/libadd.so (0x00007fca6e1c5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fca6ddd4000)
/lib64/ld-linux-x86-64.so.2 (0x00007fca6e5c9000)
Version information:
./main:
libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/libc.so.6:
ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2
上面显示的信息,文件链接的库文件,以及库文件的版本号和库的位置。
动态加载动态库函数
共享库中的函数可被加载并执行而不需要链接到程序里。加载动态链接库,首先要为共享库分配物理内存,然后在进程对应的页表项中建立虚拟页和物理页面之间的映射。动态加载函数需要使用四个基本函数分别是:dlopen()、dlsym()、dlerror()、dlclose(),使用这些函数需要包含头文件dlfcn.h。
分别介绍一下各个函数的作用和使用方式(我们也可以通过man手册查看)。
1. 打开动态链接库文件函数为 dlopen,函数原型如下:
void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库(最好文件绝对路径),并返回操作句柄。
flag的选项可以是以下两种:
RTLD_NOW:立即决定,返回前解除所有未决定的符号。
RTLD_LAZY:暂缓决定,等有需要时再解出符号
返回值:打开错误返回NULL,成功返回库引用。编译时要加入“-ldl”选项(指定dl库),实例:
gcc test.c -o test -ldl
2. 取函数执行地址的的函数为 dlsym,函数原型如下:
void *dlsym(void *handle, char *symbol);
dlsym 根据动态链接库操作句柄 (handle) 与符号 (symbol) ,返回符号对应的函数的执行代码地址。
3. 关闭动态链接库函数为dlclose,函数原型如下:
int dlclose (void *handle);
dlclose 用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为 0 时,才会真正被系统卸载。返回值为 0 表示成功,非零表示失败。
4. 动态库错误函数为 dlerror,函数原型如下:
const char *dlerror(void);
当动态链接库操作函数执行失败时,dlerror 可以返回出错信息,返回值为 NULL 时表示操作函数执行成功。
实例:只要两个函数向标准输出中显示字符,说明他们被调用了。
/*sayhello.c*/ #include <stdio.h> void sayhello() { printf("hello/n"); } /*saysomething.c*/ #include <stdio.h> void saysomething(char *str) { printf("%s/n",str); }
把上面这两个函数制作成静态库文件。使用命令:
gcc -shared -fpic sayhello.c saysomrthing.c -o libsayfn.so
调用函数如下:
/*main.c*/ #include <stdio.h> #include <dlfcn.h> #include <stdlib.h> int main(int argc,char *argv[]) { void *handler; char *error; void (*sayhello)(void); void (*saysomething)(char *); handler = dlopen("libsayfn.so",RTLD_LAZY); if(error = dlerror()) { printf("%s/n",error); exit(1); } sayhello = dlsym(handler,"sayhello"); if(error = dlerror()) { printf("%s/n",error); exit(1); } saysomething = dlsym(handler,"saysomething"); if(error = dlerror()) { printf("%s/n",error); exit(1); } sayhello(); saysomething("this is somethng"); dlclose(handler); return 0; }
最后编译main.c文件,使用下面的命令:
gcc main.c -ldl -o main
编译的时候需要注意,需要添加链接选项-ldl,还有就是在执行程序的时候需要把动态库文件放到指定的位置,否则就会出现动态库找不到的错误信息。
运行程序就可以看到如下的信息:
hello
This is something
这就说明我们这里例子没有问题。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/23661.html