解决Linux程序编译链接动态库版本的相关问题
更新时间:2017年01月25日08:34:38作者:littlewhite
这篇文章主要介绍了解决Linux程序编译链接动态库版本的相关问题linux系统介绍,文中给出了详尽的介绍和示例代码,相信对你们具有一定的参考借鉴价值,有须要的同事们下边来一起瞧瞧吧。
序言
不同版本的动态库可能会不兼容,假如程序在编译时指定动态库是某个低版本,运行是用的一个高版本,可能会造成难以运行。Linux上对动态库的命名采用libxxx.so.a.b.c的格式,其中a代表大版本号,b代表小版本号,c代表更小的版本号,我们以Linux自带的cp程序为例,通过ldd查看其依赖的动态库
$ ldd /bin/cp linux-vdso.so.1 => (0x00007ffff59df000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb3357e0000) librt.so.1 => /lib64/librt.so.1 (0x00007fb3355d7000) libacl.so.1 => /lib64/libacl.so.1 (0x00007fb3353cf000) libattr.so.1 => /lib64/libattr.so.1 (0x00007fb3351ca000) libc.so.6 => /lib64/libc.so.6 (0x00007fb334e35000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fb334c31000) /lib64/ld-linux-x86-64.so.2 (0x00007fb335a0d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb334a14000)
左侧是依赖的动态库名子,右侧是链接指向的文件,再查看libacl.so相关的动态库
$ ll /lib64/libacl.so* lrwxrwxrwx. 1 root root 15 1月 7 2015 /lib64/libacl.so.1 -> libacl.so.1.1.0 -rwxr-xr-x. 1 root root 31280 12月 8 2011 /lib64/libacl.so.1.1.0
我们发觉libacl.so.1实际上是一个软链接,它指向的文件是libacl.so.1.1.0,命名方法符合我们里面的描述。也有不按这些方法命名的,例如
$ ll /lib64/libc.so* lrwxrwxrwx 1 root root 12 8月 12 14:18 /lib64/libc.so.6 -> libc-2.12.so
不管如何命名,只要依照规定的方法来世成和使用动态库,就不会有问题。并且我们常常是在机器A上编译程序,在机器B上运行程序,编译和运行的环境似乎是有略微不同的。下边就谈谈动态库在生成和使用过程中的一些问题
动态库的编译
我们以一个简单的程序作为反例
// filename:hello.c #include void hello(const char* name) { printf("hello %s!n", name); } // filename:hello.h void hello(const char* name);
采用如下命令进行编译
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
须要注意的参数是-Wl,soname(中间没有空格),-Wl选项告诉编译器将前面的参数传递给链接器,
-soname则指定了动态库的soname(简单共享名,Shortforsharedobjectname)
如今我们生成了libhello.so.0.0.1,当我们运行ldconfig-n.命令时,当前目录会多一个软联接
$ ll libhello.so.0 lrwxrwxrwx 1 handy handy 17 8月 17 14:18 libhello.so.0 -> libhello.so.0.0.1
这个软链接是怎样生成的呢,并不是截取libhello.so.0.0.1名子的后面部份,而是按照libhello.so.0.0.1编译时指定的-soname生成的。也就是说我们在编译动态库时通过-soname指定的名子,早已记载到了动态库的二补码数据上面。不管程序是否按libxxx.so.a.b.c格式命名,但Linux上几乎所有动态库在编译时都指定了-soname,我们可以通过readelf工具查看soname,例如文章开头列出的两个动态库
$ readelf -d /lib64/libacl.so.1.1.0 Dynamic section at offset 0x6de8 contains 24 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libattr.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000e (SONAME) Library soname: [libacl.so.1]
这儿省略了一部份,可以见到最后一行SONAME为libacl.so.1,所以/lib64就会有一个这样的软联接
再看libc-2.12.so文件,该文件并没有采用我们说的命名方法
$ readelf -d /lib64/libc-2.12.so Dynamic section at offset 0x18db40 contains 27 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2] 0x000000000000000e (SONAME) Library soname: [libc.so.6]
同样可以见到最后一行SONAME为libc.so.6,虽然该动态库没有按版本号的形式命名,但仍然有一个软链指向该动态库,而该软链的名子就是soname指定的名子
所以关键就是这个soname,它相当于一个中间者,当我们的动态库只是升级一个小版本时,我们可以让它的soname相同,而可执行程序只认soname指定的动态库,这样依赖这个动态库的可执行程序不需重新编译才能使用新版动态库的特点
可执行程序的编译
还是以hello动态库为例,我们写一个简单的程序
// filename:main.c #include "hello.h" int main() { hello("handy"); return 0; }
如今目录下是如下结构
├── hello.c ├── hello.h ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main.c
libhello.so.0.0.1是我们编译生成的动态库,libhello.so.0是通过ldconfig生成的链接,采用如下命令编译main.c
$ gcc main.c -L. -lhello -o main /usr/bin/ld: cannot find -lhello
报错找不到hello动态库,在Linux下,编译时指定-lhello,链接器会去找寻libhello.so这样的文件,当前目录下没有这个文件,所以报错。构建这样一个软链,目录结构如下
├── hello.c ├── hello.h ├── libhello.so -> libhello.so.0.0.1 ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main.c
让libhello.so链接指向实际的动态库文件libhello.so.0.0.1,再编译main程序
gcc main.c -L. -lhello -o main
这样可执行文件就生成了。通过以上测试我们发觉,在编译可执行程序时,链接器会去找它依赖的libxxx.so这样的文件,因而必须保证libxxx.so的存在
用ldd查看其依赖的动态库
$ ldd main linux-vdso.so.1 => (0x00007fffe23f2000) libhello.so.0 => not found libc.so.6 => /lib64/libc.so.6 (0x00007fb6cd084000) /lib64/ld-linux-x86-64.so.2 (0x00007fb6cd427000)
我们发觉main程序依赖的动态库名子是libhello.so.0,既不是libhello.so也不是libhello.so.0.0.1。虽然在生成main程序的过程有如下几步
链接器通过编译命令-L.-lhello在当前目录查找libhello.so文件读取libhello.so链接指向的实际文件,这儿是libhello.so.0.0.1读取libhello.so.0.0.1中的SONAME,这儿是libhello.so.0将libhello.so.0记录到main程序的二补码数据里
也就是说libhello.so.0是早已储存到main程序的二补码数据里的,不管这个程序在那里,通过ldd查看它依赖的动态库都是libhello.so.0
而为何这儿ldd查看main显示libhello.so.0为notfound呢,由于ldd是从环境变量$LD_LIBRARY_PATH指定的路径里来查找文件的,我们指定环境变量再运行如下
$ export LD_LIBRARY_PATH=. && ldd main linux-vdso.so.1 => (0x00007fff7bb63000) libhello.so.0 => ./libhello.so.0 (0x00007f2a3fd39000) libc.so.6 => /lib64/libc.so.6 (0x00007f2a3f997000) /lib64/ld-linux-x86-64.so.2 (0x00007f2a3ff3b000)
可执行程序的运行
如今测试目录结果如下
├── hello.c ├── hello.h ├── libhello.so -> libhello.so.0.0.1 ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 ├── main └── main.c
这儿我们把编译环境和运行环境混在一起了,不过没关系,只要我们晓得其中原理,就可以将其理清楚
后面我们早已通过ldd查看了main程序依赖的动态库,但是指定了LD_LIBRARY_PATH变量,如今就可以直接运行了
$ ./main hello Handy!
看上去很顺利。这么假如我们要布署运行环境,该如何布署呢。其实,源代码是不须要的,我们只须要动态库和可执行程序。这儿新建一个运行目录,并拷贝相关文件,目录结构如下
├── libhello.so.0.0.1 └── main
这时运行会main会发觉
$ ./main ./main: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory
报错说libhello.so.0文件找不到,也就是说程序运行时须要找寻的动态库文件名虽然是动态库编译时指定的SONAME,这也和我们用ldd查看的一致。通过ldconfig-n.构建链接,如下
├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main
再运行程序,结果都会符合预期了
从里面的测试看出,程序在运行时并不须要晓得libxxx.so,而是须要程序本身记载的该动态库的SONAME,所以main程序的运行环境只须要以上三个文件即可
动态库版本更新
假定动态库须要做一个小小的改动,如下
// filename:hello.c #include void hello(const char* name) { printf("hello %s, welcom to our world!n", name); }
因为改动较小,我们编译动态库时依然指定相同的soname
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2
将新的动态库拷贝到运行目录,此时运行目录结构如下
├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 └── main
此时目录下有两个版本的动态库sogou pinyin linux,但libhello.so.0指向的是老本版,运行ldconfig-n.后我们发觉,链接指向了新版本,如下
├── libhello.so.0 -> libhello.so.0.0.2 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 └── main
再运行程序
$ ./main hello Handy, welcom to our world!
没有重新编译就使用上了新的动态库,wonderful!
同样,如果我们的动态库有大的改动,编译动态库时指定了新的sonamelinux 获取动态库版本信息,如下
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0
将动态库文件拷贝到运行目录,并执行ldconfig-n.,目录结构如下
├── libhello.so.0 -> libhello.so.0.0.2 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 ├── libhello.so.1 -> libhello.so.1.0.0 ├── libhello.so.1.0.0 └── main
这时侯发觉,生成了新的链接libhello.so.1,而main程序还是使用的libhello.so.0,所以未能使用新版动态库的功能,须要重新编译才行
总结
在实际生产环境中,程序的编译和运行常常是分开的,但只要认清楚这一系列过程中的原理,就不怕被动态库的版本搞晕。简单来说,按如下形式来做
编译动态库时指定-Wl,-soname,libxxx.so.a,设置soname为libxxx.so.alinux 获取动态库版本信息,生成实际的动态库文件libxxx.so.a.b.c,编译可执行程序时保证libxx.so存在,倘若是软链,必须指向实际的动态库文件libxxx.so.a.b.c运行可执行文件时保证libxxx.so.a.b.c文件存在,通过ldconfig生成libxxx.so.a链接指向libxxx.so.a.b.c设置环境变量LD_LIBRARY_PATH,运行可执行程序
好了,以上就是这篇文章的全部内容了,希望本文的内容对你们的学习或则工作能带来一定的帮助,假如有疑惑你们可以留言交流。