8.1 共享库的版本
共享库的更新可以被分为两类:
兼容更新。所有的更新只是在原有的共享库基础上添加以内容,所有原有的接口都保持不变
不兼容更新,共享库更新改变了原有的接口,使用该共享库原有接口的程序可能不能运行或运行不正常
这里讨论的接口是二进制接口,ABI
导致C语言的共享库ABI改变的行为主要有4个:
1) 导出函数的行为发生改变
2) 导出函数被删除
3) 导出数据的结构发生变化
4) 导出函数的接口发生变化,如函数返回值、参数被改变
共享库版本命名:
Linux有一套规则链命名系统中的每一个共享库,libname.so.x.y.z
“x”表示的主版本号,"y"表示次版本号,"Z"表示发布版本号
SO-NAME
Solaris和Linux系统都采用一种叫做SO-NAME的命名机制来记录共享库的依赖关系,每一个共享库都会有一个对应的“SO-NAME”,这个SO-NAME就是共享库去掉次版本号和发布版本号,只保留主版本号的名字
Linux系统中,系统会为每个共享库在它所在的目录创建一个“SO-NAME”的指向共享库的软链接
建立以“SO-NAME”为名字的软连接的目的是:使得所有依赖于某个共享库的模块,在编译、链接和运行时,都是用共享库的SO-NAME,而不使用详细的版本号
Linux中提供了一个工具叫做“ldconfig”,当系统中安装或更新一个共享库时,该工具会创建或者更新软连接指向这个共享库
8.2 符号版本
Linux中的符号版本
Linux系统下的共享库的符号版本机制并没有被广泛使用,主要使用于Glibc软件包中所提供的20多个共享库。
这些共享库比较有效地利用了符号版本机制来表示符号的版本演化机利用范围机制来屏蔽一些不希望暴露给共享库使用者的符号
Linux系统下共享库符号版本机制并没有被广泛应用,主要使用共享库符号版本机制的是Glibc软件包中所提供的20多个共享库
GCC允许使用一个叫做“.symver”的汇编宏指令来指定符号的版本,这个宏汇编指令可以被用在GAS汇编中,也可以在GCC的C/C++源代码中以嵌入式汇编指令的模式使用
asm(“ .symver add , [email protected]_1.1”)
int add(int a,int b)
{
return a + b;
}
GCC 允许多个版本的同一符号存在于一个共享库中,也就是说,在链接层面提供了一种符号重载的机制,如下:
asm(" .symver old_printf, [email protected]_1.1")
asm(" .symver new_printf, [email protected]_1.2")
int old_printf(){ …… }
int new_printf(){ …… }
在Linux下,当我们使用ld链接一个共享库时,可以使用“--version-script”参数,如果使用GCC,则可以使用“-Xlinker”参数加“--version-script”,相当于把“--version-script”传递
给ld链接器
gcc -shared -fPIC lib.c -Xlinker --version-script lib.ver -o lib.so
VERS_1.2{
global:
foo;
local:
*;
}
8.3 共享库的系统路径
Linux 系统遵守FHS标准,FHS规定,一个系统中主要有3个存放共享库的位置
/lib,/usr/lib ,/usr/local/lib
程序所以来的共享对象全部由动态连接器负责装载和初始化
动态连接器对于模块的查找有一定的规则,如果DT_NEED里面保存的是绝对路径,那么动态链接器就按照这个路径去查找,如果DT_NEED里面保存的是相对路径,那么动态连接器会在/lib
/usr/lib和由/etc/ld.so.conf 配置文件指定的目录中查找共享库
Linux系统中都有一个叫做ldconfig的程序,这个程序除了会将共享目录下的共享库创建、删除或更新相应的SO-NAME外,还会将这些SO-NAME收集起来,集中存放到/etc/ld.so.cache文件里,建立一个SO-NAME的缓存
如果动态连接器在/etc/ld.so.cache里面没有找到所需要的共享库,那么它还会遍历/lib和/usr/lib 这两个目录
8.4 环境变量
LD_LIBRARY_PATH
Linux系统提供了 LD_LIBRARY_PATH 环境变量可以用于改变共享库查找路径,这个方法可以临时改变某个应用程序的共享库查找路径,而不会影响系统中的其他程序
在Linux系统中,LD_LIBRARY_PATH 是一个由若干路径组成的环境变量,每个路径之间由冒号隔开
动态连接器会按照如下顺序依次装载或查找共享对象:
由环境变量LD_LIBRARY_PATH 指定的路径
由路径缓存文件/etc/ld.so.cache指定的路径
默认共享库目录,先/usr/lib,然后/lib
LD_PRELOAD
系统中还有一个环境变量叫做LD_PRELOAD,这个环境变量文件中我们可以指定预先装载的一个共享库或者是目标文件
比LD_LIBRARY_PATH优先 LD_PRELOAD 里面指定的共享库或目标文件都会被装载
由于全域符号介入的机制存在,LD_PRELOAD 里执行的共享库或目标文件中的全局符号就会覆盖后面加载的同名全局符号,这使得我们可以很方便地坐到改写标准的C库中的某个或某几个函数而不影响其他函数,对于程序的测试或者调试非常有用
系统配置文件中有一个文件时/etc/ld.so.preload 它的作用与LD_PRELOAD 一样。这个文件里面记录的共享库或目标文件的效果LD_PRELOAD里指定的一样
LD_DEBUG
这个变量可以打开动态链接器的调试功能,当我们设置这个变量时,动态链接器会在运行时打印出各种有用的信息,对于我们开发和调试共享库有很大的帮助
LD_DEBUG可以设置很多值:
files 动态链接器会打印出整个装载过程,显式程序所依赖于哪些共享库并按照什么步骤装载和初始化,共享库装载时的地址
bindings 显示动态链接的符号绑定过程
libs 显示共享库的查找过程
versions 显示符号的版本依赖关系
reloc 显示重定位过程
symbols 显示符号表查找过程
statics 显示动态链接过程中的各种统计信息
all 显示以上所有信息
help 显示上面的各种可选值的帮助信息
8.5 共享库的创建
创建共享库的过程跟创建一般的共享对象的过程基本一致,最关键的是使用GCC的两个参数,即“-shared”和 "-fPIC" “-shared”表示输出的结果是共享库类型的,“-fPIC”表示使用地址无关代码来产生输出文件。另外还有一个参数是“-W1”参数,这个参数可以将指定的参数传递给链接器,比如:我们使用“-W1,-soname,-my_soname”时,GCC会将“-soname my_soname”传递给链接器
ld的参数 “-rpath”选项可以指定链接器产生目标程序的共享库查找路径,比如我们使用如下命令行产生一个可执行文件
ld -rpath /home/mylib -o programe.out programe.o -lsomelib
ld链接器提供了一个“-export-dynamic”的参数,这个参数表示链接器在生产可执行文件时,将所有全局符号到处到动态库符号表
清除符号信息:
使用strip工具,可以清除共享库或者可执行文件的所有符号和调试信息
strip libfoo.so
共享库的安装
创建共享库以后我们必须将它们安装在系统中,以便各种程序都可以共享它,最简单的方法就是将共享库复制到某个标准额共享库目录,如:/lib 、/usr/lib等,然后运行ldconfig即可
共享库构造和析构函数
GCC提供了一种共享库的构造函数,只要在函数声明时加上“__arttribute__(construcor)”的属性,即指定该函数为共享库的构造函数,拥有这种属性的函数会在共享库加载时被执行,即在程序的main函数之前执行,
与共享库相对应的是析构函数,我们可以在使用的函数声明时加上“__attribute__(destructor)”的属性,这种函数会在main()函数执行完毕之后执行
声明构造函数和析构函数的格式如下:
void __attribute__((constructor)) init_function(void);
void __attribute__((destructor)) fini_function();
GCC为我们提供了一个参数叫做优先级,我们可以指定某个构造函数或析构函数的优先级:
void __attribute__((constructor(5))) init_funciton1(void);
void __attribute__((constructor(10))) init_function2(void);