说明
由于动态库支持动态链接和动态装载,使用中具有很强的动态性,在大型项目中如果缺乏约束,很容易出现版本问题,导致程序运行异常,例如:
不小心使用旧版本的动态库文件替代了新版本的动态库。 新版本动态库中的接口发生变化而引起,尽管在设计动态库时应该向下兼容,然而要保证动态库完全向下兼容却是不可能的。 新版动态库引入了一些新的Bug。 …
解决方法
解决版本问题的首要工作就是标识每一个版本,这就是版本号的作用,但是仅仅使用版本号并不能解决问题,甚至破坏了动态库的动态性,因为版本号不同,不同版本的动态库具有不同的库名,使用包含版本号的库名进行编译链接,这样无法更新动态库,完全丢弃了动态库的动态性。
linux解决方案
Real Name - 命名规范
Linux 采用版本号来标识每一个版本,并且对版本号的命名做了些规范,共享库文件的命名必须如"libname.so.x.y.z",最前面使用前缀"lib",中间是库的名字和后缀”.so”,最后三个数字是版本号, x是主版本号(Major Version Number),y是次版本号(Minor Version Number),z是发布版本号(Release Version Number), 并且它们具有以下要求。
主版本号(不兼容):重大升级,不同主版本的库之间的库是不兼容的,所以如果要保证向后兼容就不能删除旧的动态库的版本。 次版本号(向下兼容): 增量升级,增加一些新的接口但保留原有接口,高次版本号的库向后兼容低次版本号的库。 发布版本号(相互兼容):库的一些诸如错误修改、性能改进等,不添加新接口,也不更改接口,主版本号和次版本号相同的前提下,不同发布版本之间完全兼容。
SO-NAME
为了解决直接链接特定版本号的动态库导致破坏动态性的问题,linux引入了SO-NAME(Shortfor shared object name)机制。 生成动态库时,如果设置了SO-NAME,系统会在其所在的目录创建一个以SO-NAME命名并且指向它的软连接(Symbol Link),如果两个版本可以兼容它们的SO-NAME需要设置成一样的,不能兼容则设置成不同的值,例如:有两个版本的动态库分别为libtest.so.3.8.2和libtest.so.3.7.5,如果它们设置的SO-NAME都为libtest.so.3,编译app时链接的库名是libtest.so.3,这样就能兼容使用,如果它们设置的SO-NAME一个为libtest.so.3.8,另一个设置的libtest.so.3.7,编译app时链接的库名也是不同的(老版本是libtest.so.3.7,新版本是libtest.so.3.8),因此也不会导致异常。 简单来说该机制就是:SO-NAME是一个链接过程的中间介质,应用程序先链接到该软链接,再链接到真实的动态库,如果两个版本可以兼容则只需要将原有软链接指向最新的库即可,如果两个版本不能兼容,使用新版本动态库时,会链接到新的的SO-NAME软链接,也不会导致异常。
设置方式
gcc -g -Wall -fPIC -c hello.c -o hello.o
gcc -shared -W,soname,-libhello.so.0 -o libhello.so.0.0.0 hello.o
查看方式
如果编译动态库时设置了其SO-NAME,会在其ELF文件结构中的SONAME段保存其值,可通过以下命令查看:
readelf -d libhello.so.0.0.0
....
ox00000000000e(SONAME) library soname: [libhello.so.0]
....
Link name - APP链接名
不使用 SO-NAME 机制
一般情况下,使用动态库,为了保证动态性,我们不会直接链接到真实的动态库,而是创建一个不带版本号的软链接指向真正的动态库,链接到该软链接,如下:
ln -s libhello.so.0.0.0 libhello.so
gcc -o main main.o -lhello
如上操作,如果动态库版本更新(不同版本号),用户会发现无法动态更新到新版本动态库,APP运行时链接的依然是老版本号动态库,除非重新编译app,原因是APP的ELF文件结构中保存的NEEDED信息如下:
readelf -d main | grep libhello
....
ox000000000001(NEEDED) shared library: [libhello.so.0.0.0]
....
# 保存了真正的动态库名,所以无法链接到新版本的动态库,只能重新编译。
链接器通过编译命令-L. -lhello在当前文件夹查找libhello.so文件 读取libhello.so链接指向的实际文件。这里是libhello.so.0.0.0 将libhello.so.0.0.0记录到main程序的ELF文件结构中
使用 SO-NAME 机制
如果libhello.so.0.0.0 有设置SO-NAME(libhello.so.0),我们需要创建软链接链接到该SO-NAME,如下:
ln -s libhello.so.0 libhello.so
gcc -o main main.o -lhello
* 创建软链接链接到真实的动态库文件也是可以的
这样操作,APP的ELF文件结构中保存的NEEDED信息如下:
readelf -d main | grep libhello
....
ox000000000001(NEEDED) shared library: [libhello.so.0]
....
* APP链接到了SO-NAME软链接,这样动态库版本更新了,只需要SO-NAME一致就能链接到新版本动态库
链接器通过编译命令-L. -lhello在当前文件夹查找libhello.so文件 读取libhello.so链接指向的实际文件。这里是libhello.so.0.0.0 读取libhello.so.0.0.0中的SONAME,这里是libhello.so.0 将libhello.so.0记录到main程序的ELF文件结构中