相关代码在https://github.com/lzueclipse/learning/tree/master/c_cpp/diamond_link
1. 什么是菱形链接(diamond link)
菱形链接(diamond link)(参考文献 1)能十分清楚的描述出我们要讨论的问题。
如上图所示,我们的程序将要使用某厂家的共享库libvendor1.so,同时也要使用另外一个厂家的共享库libvendor2.so。
libvendor1.so和libvendor2.so都将使用某知名开源共享库libopensource.so.xxx(xxx表示版本)。
但是这两个厂家提供给我们的都是自己编译维护的libopensource.so.xxx。
我们遇到的问题是:
一个厂家使用了另外一个厂家提供libopensource.so.xxx,而不是自己提供的,出现兼容性问题。
这个问题扩展展开来:
1)如果libopensource.so.xxx的版本不相同,符号绑定(binding)的是哪个版本的?
2)如果libopensource.so.xxx的版本相同,符号绑定的是哪个版本的?
3)对于问题1)和2),采用系统默认加载和使用"dlopen"等API显式加载,又有什么不同?
如果这几个问题您没有答案或者觉得比较含糊,建议您跟随我的实验,我们一起探讨下。
因为我个人没看过连接器和加载器的源码,所以我们的探讨集中在我们看到的证据上,并试图给出一些粗浅的结论。
2.相关代码
具体代码在github中。 我们先弄清楚c文件的调用关系,以及编译脚本做了什么样的工作。
调用依赖: main.c<----vendor[1|2].c<--------opensource_v[1|2].c(函数opensource_print的不同实现)。
C源代码:
1)其中vendor1.c会被编译生成libvendor1.so,vendor2.c会被编译生成libvendor2.so;
opensource_v1.c会被编译生成./opensource_v1/libopensource.so.xxx(xxx值表示版本信息,后续实验会给定真实值),opensource_v2.c会被编译成./opensource_v2/libopensource.so.xxx;
libvendor1.so会依赖./opensource_v1/libopensource.so.xxx, libvendor2.so会依赖./opensource_v2/libopensource.so.xxx,
2)main.c链接libvendor1.so,libvendor2.so生成可执行文件
3)main.c有两种用法,一种"general"使用系统默认的加载共享库的方法,一种"dlopen"使用dlopen等API显式加载需要的共享库。
用于控制编译的Shell脚本(每个使用一个脚本,为了便于说清):
different_soname_without_default_symver.sh
different_soname_with_default_symver.sh
same_soname_without_default_symver.sh
same_soname_with_default_symver.sh
在这个实验里我们编译opensource_v1.c生成./opensource_v1/libopensource.so.1.0;编译opensource_v2.c生成./opensource_v2/libopensource.so.2.0。
libvendor1.so将依赖./opensource_v1/libopensource.so.1.0; libvendor2.so将依赖./opensource_v2/libopensource.so.2.0。
3.1符号表不带版本信息的
gcc编译的符号,默认是不带版本信息的。
3.1.1我们用different_soname_without_default_symver.sh 来编译
[root@node1 0004]# sh different_soname_without_default_symver.sh
Complile success
3.1.2 列出编译生成的文件
我们把libopensource.so.1.0相应3个文件放在"./opensource_v1"目录,把libopensource.so.2.0相应3个文件放在"./opensource_v2"目录:
3.1.3 用readelf查看编译生成的main,libvendor1.so,libvendor2.so
我们仅仅关注"NEEDED","RPATH"项。
"NEEDED"表示依赖的库,注意"NEEDED"并不是"libopensource.so.xxx.0"这样的完整名字,而是"libopensource.so.xxx"这样只包含大版本信息的名字(该名字术语叫"SONAME"),我们在编译libopensource共享库时通过"-Wl,-soname"指定"SONAME",参考文献 2中对"SONAME" "Major version" "Minor version" 有详细介绍; 编译时不指定""-Wl,-soname"的情况,这里不再讨论了(太多头绪,太乱),可以自己修改编译脚本做实验验证。
"RPATH"表示查找依赖库会从这些列出的路径查找(另外有个环境变量LD_LIBARARY_PATH也是类似的作用)。
更多细节所请自行Google。
[root@node1 0004]# readelf -d main
Dynamic section at offset 0xde8 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libvendor1.so]
0x0000000000000001 (NEEDED) Shared library: [libvendor2.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000f (RPATH) Library rpath: [./]
[root@node1 0004]# readelf -d libvendor1.so
Dynamic section at offset 0xde8 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libopensource.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libvendor1.so]
0x000000000000000f (RPATH) Library rpath: [./opensource_v1]
[root@node1 0004]# readelf -d libvendor2.so
Dynamic section at offset 0xde8 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libopensource.so.2]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libvendor2.so]
0x000000000000000f (RPATH) Library rpath: [./opensource_v2]
[root@node1 0004]# readelf -d opensource_v1/libopensource.so.1.0
Dynamic section at offset 0xe08 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libopensource.so.1]
[root@node1 0004]# readelf -d opensource_v2/libopensource.so.2.0
Dynamic section at offset 0xe08 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libopensource.so.2]