当前位置: 首页 > 科技观察

程序运行时如何找到动态库?

时间:2023-03-19 11:47:22 科技观察

我们在随便开发一个C/C++程序的时候,难免会大量使用动态库://来源:公众号【编程珠玑】#includeintmain(){printf("你好,编程珠\n");return0;}编译查看使用的动态库:$gcc-omainmain.c$lddmainlinux-vdso.so.1(0x00007ffdf8fdf000)libc.so.6=>/lib/x86_64-linux-gnu/libc.so.6(0x00007f1f8535e000)/lib64/ld-linux-x86-64.so.2(0x00007f1f85951000)从ldd命令的结果可以看出主程序依赖了哪些动态库哪条路。那么大家有没有想过如何找到动态库的路径,查找顺序是怎样的呢?在准备动态库之前,如果你对动态库没有基本的了解,建议你阅读《浅谈静态库和动态库》或其他相关资料。为了说明下面的问题,这里我们先创建一个简单的动态库,大家也可以参考?://test.c//来源:公众号【编程珠玑】#include#include"test.h"#include"test1.h"voidtest(){printf("Iamtest;你好,编程珠\n");test1();}//test.hvoidtest();//test1.c#include#include"test1.h"voidtest1(){printf("test1,neededbytet\n");}//test1.hvoidtest1();分别制作动态库libtest.so和libtest1.so,在后面的例子中会用到:$gcctest1.c-fPIC-shared-olibtest1.so$gcctest.c-fPIC-shared-olibtest.so-L.-ltest1这样你就会在当前目录下看到一个libtest。生成so和libtest1.so文件,其中litest.so依赖libtest.so注意由于libtest.so依赖libtest1.so,这里我们使用-L指定要链接的test1的路径,所以看到:$lddlibtest.solinux-vdso.so.1(0x00007ffd1bbca000)libtest1.so=>未找到libc.so.6=>/lib/x86_64-linux-gnu/libc.so.6(0x00007f9f1d0ae000)/lib64/ld-linux-x86-64.so.2(0x00007f9f1d6a1000)从这里可以看出libtest依赖于libtest1库,但是特别注意libtest1.so指向notfound。这会有什么影响吗?我们稍后会看到。我们都知道链接时找路径,在编译成可执行文件之前,链接器在链接动态库的时候也需要找到动态库的路径。不然怎么链接指定的动态库呢?那么顺序是什么?首先,查找将是编译时链接的路径。修改之前的main.c,使其调用libtest.so中的test函数:;//调用libtest.so中的测试函数return0;}编译链接:$gcc-omainmain.c-I./-L./-ltest-ltest1已完美编译。另外,如果我们把libtest.so和libtest1.so都移到/usr/lib下,我们发现即使没有-L也可以编译:$gcc-omainmain.c-I./-ltest-ltest1这里需要注意的是我们使用-L./指定搜索库的路径。由于libtest.so依赖于libtest1.so,所以在编译链接的时候也需要链接test1。总结从上面的内容我们可以看出,在链接的时候,我们通过-L参数搜索要链接的库路径,但是这个路径信息不会写入ELF文件,所以通过ldd会看到notfound命令,通过-rpath可以指定链接时的搜索路径。这些信息将写入ELF文件。最后的结果是,因为libtest.so依赖libtest1.so,当其他程序依赖libtest.so时,会自动从ELF的rpath中搜索自己依赖的其他库,所以只需要链接libtest即可,例如:在制作库libtest1.so时,添加-rpath-link选项:$gcctest.c-fPIC-shared-olibtest.so-L.-ltest1-Wl,-rpath-link$(pwd)编译main时,没有需要指定链接libtest1.so$gcc-omainmain.c-L./-ltest只需要链接libtest.so,依赖libtest1.so也链接进去了。当然,如果-L指定的路径不存在,就会去别处找,不然怎么找依赖的系统库呢?总结的大致顺序如下:-L通过-rpath-link或-rpath选项搜索指定链接路径在依赖库中的搜索顺序(后面会提到)gcc默认链接路径(gcc--print-search-dir|greplibrariesview)linkerconfigurationsearchpath(ld-verbose|grepSEARCH_DIRview)forspecificsystems或者链接器,可能有些不同,但大致相同。虽然运行时搜索路径编译成功,但是我们跑起来发现运行失败。$./main./main:errorwhileloadingsharedlibraries:libtest.so:cannotopensharedobjectfile:Nosuchfileordirectory其实我们用ldd命令也可以看到:linux-vdso.so.1(0x00007ffcd566e000)libtest.so=>notfoundlibc.so.6=>/lib/x86_64-linux-gnu/libc.so.6(0x00007f356d1f6000)/lib64/ld-linux-x86-64.so.2(0x00007f356d7e9000)LD_PRELOAD环境变量《性能优化-使用高效内存分配器》中介绍了这个环境变量,是还提到用它做测试很方便。同样,这种方法最好只用于测试,因为它的优先级非常高,并且会影响到整个世界。使用起来也很简单:$exportLD_PRELOAD=./libtest.so$./main为了避免影响后面的验证,这里取消环境变量的设置:unsetLD_PREALOD找到上面的rpath路径就是那个动态库找不到,那么会先去rpath指定的路径搜索,编译时需要指定:$gcctest.c-fPIC-shared-olibtest.so-L.-ltest1-Wl,-rpath$(pwd)$gcc-omainmain.c-L.-ltest-Wl,-rpath$(pwd)$./mainIamtest;你好,编程珠test1,neededbytest就是说如果我们在编译的时候指定了路径,是可以找到的,但是这个信息是写到ELF文件里面的。LD_LIBRARY_PATH环境变量也可以用来设置通过这个环境变量搜索库的路径。像这样运行$gcc-omainmain.c-L.-ltest$exportLD_LIBRARY_PATH=./$./main是没有问题的。同样,为了避免影响后续测试,取消设置环境变量:unsetLD_LIBRARY_PATH/etc/ld.so.conf这个文件在我的机器上的内容如下:$cat/etc/ld.so.confinclude/etc/ld.so.conf.d/*.conf$ls/etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conflibc.confx86_64-linux-gnu.conf所以其实是指/etcAllconf/ld.so.conf.d/目录下的路径包含路径,打开其中一个libc.conf,里面包含的路径为:$/usr/local/lib,此时我们将之前的libtest.so复制到中此目录(可能需要sudo权限):$sudocplibtest.so/usr/local/lib$sudoldconfig$./mainIamtest;你好,编程珠test1,needbytest注意,复制到这里后,需要执行ldconfig,它会更新对应的Share库,以便可执行文件找到。其实执行完成后,确实可以在/etc/ld.so.cache文件中找到:$grep-alibtest.so/etc/ld.so.cache同样,为了影响后面的测试,切记删除:rm/usr/local/lib/libtest.so其实是先从/etc/ld.so.cache中的路径查找,再从ld.so.conf中的路径查找。后面我们可以通过命令看到。/usr/lib,/lib/当然,如果以上路径都不存在,最终还是会在lib或者/usr/lib下找到。为了验证,我们将库复制到/lib目录下$cplibtest.so/lib$。/主要测试;你好,编程珠test1,neededbytet也可以正常运行。总结总结一下,动态库的搜索顺序如下:LD_PRELOAD环境变量指定库路径-rpath指定链接时的路径LD_LIBRARY_PATH环境变量设置路径/etc/ld.so.conf配置文件指定路径defaultshared库路径,/usr/lib,你可以很容易地验证这些搜索路径在lib之上的优先级。简单的方法就是在这些位置放置同名不同功能的库,看它先使用哪个路径。你可以自己试试。LD_DEBUG该环境通常用于调试。例如查看整个加载过程:$LD_DEBUG=files./main或者查看依赖库的查找过程:$LD_DEBUG=libs./main3557:findlibrary=libtest.so[0];searching3557:searchcache=/etc/ld.so.cache3557:tryingfile=/usr/local/lib/libtest.so另外还可以展示符号的查找过程:$LD_DEBUG=symbols./main总结理解动态库的查找路径,可以帮助你在开发的时候定位发现库没有问题,LD_DEBUG环境变量也可以帮你调试好,比如查看库搜索路径,显示符号搜索过程等等。虽然程序运行时获取动态库路径的方法有很多种,但并不是每一种方法都适用,有些方法根本不应该使用,但这不在本文讨论范围之内。有兴趣的也可以点击阅读原文查看《Why LD_LIBRARY_PATH is bad》