分类: C/C++
2015-08-12 17:23:51
原文地址:我对动态链接库的一些理解 作者:
GNU体系下so的兼容性及so内各接口实现的兼容性主要通过两种措施来保证:
1) SONAME
2) 接口的符号版本
1. SONAME
GNU版本命名规范一般是
ELF spec对soname的支持是在type为DYNAMIC的section(一般为.dynamic)中增加了一个名为soname的tag,该tag对应的value为SONAME的字符串。SONAME的作用体现在两个方面:
1) 编译链接时:链接时对于动态链接要提供so以检查是否有undefined symbol及确定可执行程序加载需要的so,可执行程序加载需要的所有so都在.dynamic section的DT_NEED tag中描述,如果一个so有符号被链接过程引用,如果无soname,那么放在DT_NEED中的是so名;如果有soname,那么这里放的就是soname。
例: 程序:
无soname:
有soname:
2) 运行时.interp section指示的loader在加载executable 时会顺序加载ELF .dynamic NEED tag所指示的各个链接库,按照名字寻找各链接库加载(LD_LIBRARY_PATH, then ld.so.cache)。 link时指定的soname在load时也应该提供。Linux系统下soname一般是符号链接,其指向真正提供服务的so。如此,linux系统下magic version相同的同种so的soname相同,而真正提供服务的so由soname指向的so决定。Soname符号链接的指向一般由ldconfig决定,它会对ld.conf.conf中指定的所有目录的so按照其SONAME进行符号链接指定,一般指向版本号最高的so。参考/usr/lib下各库,soname一般为xx.so.x, 它指向xx.so.x.y.z,库的版本号直接嵌入在文件名中。
2. 接口的符号版本。
编译时用到的so和运行时用到的so很大情况上是不一样的,运行期和编译器的符号兼容性也有机制来保证。这便是glibc中使用的符号版本机制。
例子:
1) 运行期用到so源码:
符号脚本文件:
cc -fPIC -shared -Wl,-soname,"kinwin.so" -Wl,--version-script=test3.ver -o kinwin.so.3 test3.c 编译之。
2)编译时的so
分别用两个version script(一个指示KINWIN_1.0, 一个指示KINWIN_2.0)编译为两个so, 然后
和两个so分别连接得到test1和test2
3)结果
同样的对外接口,因为编译用到符号版本不一样而在运行时产生不同的行为保证运行和
编译时行为一致(实际上就是接口+后缀导致.dynsym中名字不同,类似于命名空间,再
加上.symver的alias作用)
3. so模块内符号对外的可见性
论及符号脚本,里面有个global和local,它决定了链接产生so中对外符号导出。即哪些符号会放到.dynsym中。但是这里所做的决定仅仅影响链接过程,不会影响到编译过程,所以这里做的导出决定不会影响到编译期可以进行的优化,更好的符号导出指示可使用visibility这个attribute。
一个.o中的符号在链接时导出否可以用static限定,同样一个so中的符号在动态链接时导出否由__attribute__((visibility))决定。
不需要被外界用到的符号设置visibility为hidden有两个好处:
1) so中每个global的符号都会生成相应的GOT项,如果是函数还会生成plt,以及可能的延迟绑定的东西。这样本来可能是同模块内部的符号都可能要经过一次跳转或两次跳转才能达到,每次都是。这对性能有一定影响。
2) global的符号被放在got段中,就表明该符号是Load时确定的,根据ld.so resolve symbol的过程,谁先提供这个符号就用谁的,这就会产生一些干扰,比如本来本库的某个内部函数实现被先前载入的库有同样的符号名导出,那么就会用其他的实现导致一些未知的问题。如:
而如果将a.c的foo1用hidden的visibility,则:
编译成动态链接库,有:
用readelf 看产生SO文件的重定位表,也可以看到加了hidden的so少了对foo1的重定位表项。
如果设置为hidden的visibility, 那么编译时编译器就知道该符号是模块内部使用,模块内其他函数对该符号的调用直接产生 call <相对偏移>; 而如果global,就会产生一个到plt的调用,而plt段又会调用.got.plt中保存的函数地址跳转过去执行。会中转两次。
4. so可执行的本质
1) so可以执行的本质,在于kernel在load elf bin时是这样的:
即对于所有的ELF TYPE(EXEC, RELOC, DYN, CORE等), 可执行文件和动态库都是可以被放过去的。
2) 用户空间的binutils在生成so时本意并不是让其可执行,因此编译时少了很多设置,包括interp section和入口项。这些都可以手动解决。加interp section主要是为了有出现动态解析地址的情况,如果完全static linked,也不需要这玩意。如: