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

Linux动态库知识总结

时间:2023-03-12 07:42:09 科技观察

动态库和静态库在C/C++开发中非常常见。与直接编译成可执行程序的静态库相比,动态库是在运行时加载的,使可执行程序体积更小,更新更及时。动态库可以省去重新编译可执行程序等诸多好处。笔者是一名Linux后台开发人员,经常会用到这方面的知识,所以整理了这方面的知识。静态库比较简单,本文只关心Linux平台下的动态库。创建动态库这里我以将一个简短但有用的哈希函数编译成动态库为例。ELFhash用于散列字符串并返回无符号整数。//elfhash.h#includeunsignedlongELFhash(constchar*key);//elfhash.c#include"elfhash.h"unsignedlongELFhash(constchar*key){unsignedlongh=0,g;while(*key){h=(h>24;h&=~g;}returnh;}接下来用gcc编译上面的代码,用ld把编译好的目标文件链接成动态库gcc-fPIC-c-Wallelfhash.cld-sharedelfhash.o-olibelfhash.so其中-fPIC的意思是生成与位置无关的代码(PositionIndependentCode),适用于动态库,在多个进程中共享动态库的相同代码。ld的-shared选项告诉链接器创建一个动态库.gcc也可以间接调用ld生成动态库gcc-fPIC-shared-Wall-olibelfhash.soelfhash.c使用动态库动态库有两种使用方式,一种是隐式使用,另一种是显式使用。隐式使用的方法很简单#include"elfhash.h"intmain(){printf("%ldn",ElfHash("key-for-test"));return0;}显式使用动态库es需要以下函数#includevoid*dlopen(constchar*filename,intflag);//flag可以是RTLD_LAZY,undefinedsymbols在执行共享库代码时解析,RTLD_NOW是undefinedsymbols在dlopen返回前解析。char*dleerror(void);//发生错误时,返回错误信息void*dlsym(void*handle,constchar*symbol);//获取交易品种intdlclose(void*handle);//关闭上面几个Function,调用ELFhash实现和隐式调用一样的功能#include"elfhash.h"#include#includeintmain(){void*handle;unsignedlong(*hash)(constchar*);char*e??rror;handle=dlopen("./libelfhash.so",RTLD_LAZY);if(!handle){fputs(dleerror(),stderr);exit(1);}hash=dlsym(handle,"ElfHash");if(error=dleerror())!=NULL){fputs(error,stderr);exit(1);}printf("%ldn",(*hash)("key-for-test"));dlclose(handle);}到目前为止,了解以上知识可以让您创建和使用动态库。在实际应用中,我们可能还会遇到一些问题。在动态库加载和创建动态库部分,我演示了如何隐式使用动态库,所以尝试编译运行这段代码。gccmain.c-L./-lelfhash./a.out//执行可执行程序//输出结果如下。/a.out:errorwhileloadingsharedlibraries:libelfhash.so:cannotopensharedobjectfile:No此类文件或目录的结果是运行时报错,可执行程序找不到动态库。网上有一些说法是在编译的时候设置了-L选项,但是在Linux上证明是不行的(在SunOS上是可行的)。该选项只在编译链接时有效,允许使用-l,如上面的-lelfhash。使用readelf-da.out查看可执行文件所依赖的动态库信息。0x0000000000000001(NEEDED)Sharedlibrary:[libelfhash.so]可以看到里面没有包含动态库的路径信息。查看动态链接器的manld-linux.so文档,可以找到这么一句话(有的没有,版本问题)如果找到斜线,则依赖字符串被解释为(相对或绝对)路径名,和libraryThepassageisloadedusingthatpathname太长了,所以我只截取了一部分。基本上,当依赖中有/符号时,就会被解析成加载动态库的路径。隐式使用的例子是另一种编译方式。gccmain.c./libelfhash.so./a.out23621492//如果输出正常,用readelf-da.out查看,会发现依赖信息里有路径。0x0000000000000001(NEEDED)Sharedlibrary:[./libelfhash.so]这种方法虽然解决了问题,但是依赖中的路径是硬编码的,不是很灵活。动态链接器如何找到动态库需要进一步查阅文档。查找的顺序有点长,这里就不直接引用了,大致是这样:(仅限ELF文件)使用可执行文件中DT_RPATHlocale设置的属性,如果设置了DT_RUNPATH,则忽略DT_RPATH(在我的Linux中,对应的是RPATH和RUNPATH)。使用环境变量LD_LIBRARY_PATH,如果可执行文件中有set-user-id/set-group-id,将被忽略。(仅限ELF文件)使用可执行文件中DT_RUNPATHlocale设置的属性,从默认路径/lib、/usr/lib文件目录中搜索/etc/ld.so.cache缓存文件我们需要设置RPATH或RUNPATH,你可以这样做gccmain.c-Wl,-rpath,/home/xxx,--enable-new-dtags-L./-lelfhash这里的-Wl选项告诉链接器如果ld和-rpath做什么通过next(或者用-R)告诉ld动态库的路径信息(注意-Wl和后面的选项之间不能有空格)。如果没有--enable-new-dtags则只会设置RPATH,否则会同时设置RPATH和RUNPATH。使用readelf-da.out查看结果:0x000000000000000f(RPATH)Libraryrpath:[/home/xxx]0x000000000000001d(RUNPATH)Libraryrunpath:[/home/xxx]如果使用环境变量LD_LIBRARY_PATH,那么一般使用exportexportLD_LIBRARY_PATH=/home/xxx;$LD_LIBRARY_PATHRPATH和RUNPATH指定动态库的路径,使用简单,但也缺乏灵活性。LDLIBRARYPATH对于临时测试也很有用,但是在正式环境中直接使用它并不是一个好的做法,因为环境变量与用户的环境有很大关系。动态库不仅要考虑自己的使用,还要考虑分发给其他用户使用。比较通用的方法是使用ldconfig,有几种方法,先在/etc/ld.so.conf.d/目录下新建一个文件,然后把你的动态库路径写进去。或者把你的动态库放到/lib,/lib64(64-bit),/usr/lib,/usr/lib64(64-bit)然后运行??sudoldconfig重建/etc/ld.so.cache文件。动态库版本在使用第三方提供的动态库时,通常会有一个版本(文件命名)。你可以在/usr/lib64下看到很多这样的动态库。现在我重新编译动态库,这次添加版本信息。gcc-fPIC-shared-Wall-Wl,-soname,libelfhash.so.0-olibelfhash.so.0.0.0elfhash.c每个动态库都有一个名字,比如这里的libelfhash.so.0.0.0,就是调用实名,命名规则简单,通常是libxxx.so.MAJOR.MINOR.VERSION(有时会省略VERSION),如果动态库对接口的兼容性,比如删除接口或者修改接口参数,MAJOR增加,如果接口兼容,只做更新或bug修复,则MINOR和VERSION增加。也就是说,同一个MAJOR库接口是兼容的,反之,如果使用了不兼容的动态库,则需要重新编译可执行程序。在编译动态库时,可以通过给ld传递连接选项-soname来指定一个soname,比如libelfhash.so.0这里只保留MAJOR,可执行程序运行加载动态库时,会加载具有指定名称的库。动态库还有一个名字链接名。编译可执行程序时,传递链接器ld的动态库名,通常是.so结尾的文件名,不带版本号。一般的做法是为soname创建一个软链接。按照这个规则命名的动态库可以被ldconfig识别。我们把libelfhash.so.0.0.0放在/usr/lib64文件夹下,执行如下命令$sudoldconfig-v|greplibelfhash.solibelfhash.so.0->libelfhash。so.0.0.0根据libelfhash.so.0.0.0的信息可以发现ldconfig创建了一个soname指向真实名字的软链接。动态库更新时(添加MINOR,VERSION),将新库复制到对应位置,然后执行sudoldconfig会自动更新软链接指向***动态库,动态库更新为完全的。总结OK,Linux动态库知识整理到此结束。这些知识虽然很基础,很少涉及动态库的一些内部原理,但是却很常用。整理过程中,带着疑惑阅读了ld和ld-linux.so的文档,收获颇丰。再次,希望这篇文章可以帮助你解释你遇到的一些问题或疑惑。