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 <stdio.h>
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(简单共享名。Short for shared object name)
如今我们生成了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为not found呢。由于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 <stdio.h>
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
此时文件夹下有两个版本号的动态库。但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。
相同。假如我们的动态库有大的修改。编译动态库时指定了新的soname。例如以下
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.a,生成实际的动态库文件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,执行可执行程序
EOF
Linux程序编译链接动态库版本号的问题的更多相关文章
- ffmpeg学习笔记-Linux下编译Android动态库
Android平台要使用ffmpeg就需要编译生成动态库,这里采用Ubuntu编译Android动态库 文件准备 要编译生成Android需要以下文件 NDK ffmpeg源代码 NDK下载 NDK可 ...
- Linux GCC编译使用动态、静态链接库 (转)
原文出处:http://blog.csdn.net/a600423444/article/details/7206015 在windows下动态链接库是以.dll后缀的文件,二在Linux中,是以.s ...
- Linux gcc链接动态库出错:LIBRARY_PATH和LD_LIBRARY_PATH的区别
昨天在自己的CentOs7.1上写makefile的时候,发现在一个C程序在编译并链接一个已生成好的lib动态库的时候出错.链接命令大概是这样的: [root@typecodes tcpmsg]# g ...
- Makefile 编译动态库文件及链接动态库
本文为原创文章,转载请指明该文链接 文件目录结构如下 dynamiclibapp.c Makefile comm/inc/apue.h comm/errorhandle.c dynamiclib/Ma ...
- 【原创】Linux下编译链接中常见问题总结
前言 一直以来对Linux下编译链接产生的问题没有好好重视起来,出现问题就度娘一下,很多时候的确是在搜索帮助下解决了BUG,但由于对原因不求甚解,没有细细研究,结果总是在遇到在BUG时弄得手忙脚乱得. ...
- Linux 程序编译过程的来龙去脉
大家肯定都知道计算机程序设计语言通常分为机器语言.汇编语言和高级语言三类.高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类 ...
- linux程序编译过程
大家肯定都知道计算机程序设计语言通常分为机器语言.汇编语言和高级语言三类.高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类 ...
- [Linux][C][gcc] Linux GCC 编译链接 报错ex: ./libxxx.so: undefined reference to `shm_open'
本人原创文章,文章是在此代码github/note的基础上进行补充,转载请注明出处:https://github.com/dramalife/note. 以librt丶用户自定义动态库libxxx 和 ...
- linux下编译安装boost库
linux下编译安装boost库 linux下编译安装boost库 1.下载并解压boost 1.58 源代码 下载 解压 2.运行bootstrap.sh 3.使用b2进行构建 构建成功的提示 4. ...
随机推荐
- 虚拟机vmware10.0.0里设置Suse Linux Enterprise 11系统静态IP上网
http://blog.csdn.net/usbdrivers/article/details/50035615 首次在虚拟机里安装Suse Linux Enterprise 11,采用NET方式能够 ...
- mysql管理和基本操作
进去mysql:mysql –uroot –p 重启数据库:[root@nanaLinux ~]# /etc/init.d/mysqld restart 1.Mysql忘记root密码 // 查看my ...
- Java中的标记接口(zz)
1.什么是标记接口? Java中把没有定义任何方法和常量的接口称之为标记接口,我们经常使用的比较多的是“”Serializable“”,这个接口也是没有定义人任何方法和常量的. 2.标记接口的作用? ...
- CentOS6.9下安装 Pika 2.2.5(新增了拷贝安装版本的办法+对于PID的位置及数据库位置的理解)
一.环境准备 yum install -y snappy-devel protobuf-compiler protobuf-devel bzip2-devel zlib-devel bzip2 ...
- windows下tortoiseGit安装和使用
一.安装git for windows 首先下载git for windows客户端http://msysgit.github.io/ 安装过程没什么特别的,不停next就ok了 图太多就不继 ...
- 【cocos2d-js官方文档】九、cc.loader
概述 原来的cc.Loader被改造为一个单例cc.loader,采用了插件机制设计,让loader做更纯粹的事. 各种资源类型的loader可以在外部注册进来,而不是直接将所有的代码杂揉在cc.Lo ...
- Nodejs微信开发使用wechat-api回复多条消息
在上一往篇文章<Nodejs微信开发>中,微信后台能够正常的接收到客户端的消息,并能够简单的回复一条消息至客户端. 但我的目录是将微信与Bot Framework进行关联,那么肯定就有一些 ...
- 安装mezzanine时报:storing debug log for failure【已解决】
同时还提示: bz2 module is not found(貌似) 解决方法: 1.重新安装python wget http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz ...
- 洛谷 P3383 【模板】线性筛素数-线性筛素数(欧拉筛素数)O(n)基础题贴个板子备忘
P3383 [模板]线性筛素数 题目描述 如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示查询的范 ...
- 51nod 1183 编辑距离【线性dp+类似最长公共子序列】
1183 编辑距离 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个 ...