《程序员的自我修养》第8章个人学习笔记
8.1共享库的版本
8.1.1 共享库的兼容
共享库的更新可以被分为两类:
兼容更新。原有接口不变。
不兼容更新。改变了原有的接口,使用该共享库原有的接口的程序可能不能运行或运行不正常。
实际上,这些对接口兼容的考虑是ABI上的。主要包括堆栈结构、符号命名、参数规则、数据构造的内存分布等方面。
导致c语言的共享库ABI改变的行为主要有如下4个:
1.导出函数的行为发生变化。
2.导出函数被删除。
3.导出数据的结构发生变化。
4.导出函数的接口发生变化,如函数返回值、参数被更改。
其实以上的不兼容都可以从汇编的角度思考,我们写好的c语言源代码经过编译后的汇编代码就已经根据我们的编译器写好,这些已经固定(不妨打开ida看看),假如我们的库变了,但汇编因为已经完成,无法改变,就会出现不兼容问题。试想一下,我们共享库函数的第一个第二个变量位置顺序变了,但可执行文件没变,将会产生怎样的效果。从这里也能看出,脚本语言相比编译语言的优越性,其的可兼容性。
8.1.2 共享库版本命名
Linux规定共享库的文件命名规则:
libname.so.x.y.z //x主版本号,y次版本号,z发布版本号
主版本号表示库的重大升级,不同主版本号无法兼容。
次版本号表示库的增量升级,如增加接口。向下兼容。
发布版本号表示库的一些bug修正、性能改进。不对接口更改。
linux中的c语言库,动态链接器等命名比较特殊。如我们平常接触的libc2.23.so。
8.1.3 SO-NAME
在linux系统中,系统会为每个共享库在它所在的目录创建一个跟“SO-NAME"相同的并指向它的软连接。这个SO-NAME即共享库的文件名去掉次版本号和发布版本号,保留主版本号。
例如,一个共享库叫lib.so.2.6.1,那么其SO-NAME就叫lib.so.2
由于历史原因,动态链接器和c语言库的共享对象文件名规则不按linux标准的命名方式。如我们现在的libc库的软连接都叫libc.so.6。
这些软连接会指向目录中主版本号相同,次版本号和发布版本号最新的共享库。
这样一来,我们可执行文件在转移时就不用天天修改各种.dynamic段了,只要大家都遵循上述标准就行,libc库名统一软连接名就行。
用ida打开某程序的汇编下最开头就可以看到interpret段和.dynamic段记录依赖的库。
Linux中提供一个工具叫Idconfig,当系统中新安装或更新了一个共享库时,就运行它遍历,更新所有或创建新的软连接。
8.2 符号版本
有一种问题叫做此版本交会问题。例如我们的可执行文件用到了2.6版本新增加的符号。但该机器上只有2.5版本,因此链接时通过软连接的最新库也就是2.5了,这是动态链接器运行时就会报错,为了解决这个问题,这时引入基于符号的版本机制。
8.2.1 基于符号的版本机制
其实就是为符号添加一个标签,来分明这个符号是哪个版本引入的
8.2.2 Solaris中的符号版本机制
见p237举例。好处是引入了符号的范围机制,比如说我希望某符号不要被其他模块访问,我可以把其变成局部符号,不再对外显示。
链接器会计算出该可执行文件所用到的最高版本符号,记录在可执行文件中,从而规避版本交会问题。
8.2.3 Linux中的符号版本
详见p239,p240有Linux系统中符号版本机制实践示例。
8.3 共享库系统路径
目前大多数包括linux在内的开源操作系统都遵守一个叫做FHS的标准。
具体在p241.
8.4 共享库查找过程
动态链接器在查找.dynamic段给出的模块路径时按照以下规则。
如果该项保存的是绝对路径,那么动态链接器就按照这个路径去查找;如果保存的是相对路径,那么动态链接器就会在/lib、/usr/lib和由/etc/ld.so.conf配置文件指定的目录中查找。(p205讲解dynamic段,其中如果是相对路径,实际上保存的只是共享文件的SO-NAME,不像.interpret保存了相对地址的路径,这也是为什么我们需要遍历这些目录,这一点算是暂时解惑了)
但这样挨个目录找很麻烦,所以又用到了Idconfig程序。这个程序除了之前说的会建立或刷新SO-NAME以外,还会收集起来,放到/etc/ld.so.cache里。当寻找时,直接在这里面找,如果找不着了,再去上述的地方遍历找。
8.5 环境变量
LD_LIBRARY_PATH
该环境变量由若干个路径组成,每个路径之间由冒号隔开。默认情况下为空。
感觉书中讲的不够详细,( 我理解能力不够,找个时间把这块再学学。
LD_PRELOAD
类似上面的,这个是提前装载共享库(在ld加载其他共享库前),可以利用全局符号介入机制去改写部分函数,但不影响整体运行。
LD_DEBUG
打开动态链接器的调试功能。
参数:
files 打印出整个装载过程,显示程序依赖的共享库,装载步骤和初始化,共享库装载时的地址。
bindings 显示动态链接的符号绑定过程
…
详见p244
8.6 共享库的创建和安装
8.6.1 共享库的创建
记得利用SO-NAME,不过也可以利用rpath在链接可执行文件时修改成绝对地址。
8.6.2 清除符号信息
方法:strip工具,gcc中的 -W1,-S或s
8.6.3 共享库的安装
使用Idconfig指定共享库所在的目录。在编译程序时,也需要指定共享库的位置。
注:时时刻刻注意编译器与动态链接器的区别,书里说的很笼统,并不分明!
8.6.4 共享库构造和析构函数
指定共享库的构造和析构函数的方法p247
8.6.5 共享库脚本