文章目录
  1. 共享库版本
    1. 共享库的兼容性
    2. 共享库版本命名
    3. SO-NAME
    4. 链接名
  2. 符号版本
  3. 共享库系统路径
  4. 共享库的查找
  5. 环境变量
  6. 共享库的创建与安装
    1. 创建共享库
    2. 清除符号信息
    3. 安装共享库
    4. 共享库构造与析构函数
    5. 共享库脚本

共享库版本

共享库的兼容性

在共享库升级时有如下4种修改会导致C语言的共享库二进制接口发生改变。

  • 导出函数的行为发生改变;
  • 导出函数被删除;
  • 导出数据的结构发生变化;
  • 导出函数的接口发生变化,如函数的返回值、参数变化;

共享库版本命名

共享库升级导致的不兼容是不可避免的,为此,只有对共享库进行版本命名:x.y.z;其中x为主版本号,y为次版本号,z为发布版本号;主版本号表示共享库的重大升级,不同主版本号的共享库之间不兼容;次版本号表示共享库的增量升级,主版本号相同时,次版本号高的共享库可兼容次版本号低的共享库。发布版本号表示库中错误的修正、函数性能的提升等,既不添加新的接口,也不对接口进行修改;相同主、次版本号的共享库完全兼容。

SO-NAME

在ELF文件中用SO-NAME来命名一个依赖的共享库,具体做法为去掉共享库名称中的次、发布版本号;如ELF依赖共享库libfoo.so.2.6.1,则这个共享库对应的SO-NAME为libfoo.so.2。在Linux系统中,系统会为每个共享库在其所在目录创建一个以其SO-NAME为名的软链接指向其自身。通过软链接可以定向到相同主版本号中最新版本的共享库。这是因为在Linux系统安装或更新一个共享库时,会运行ldconfig工具,该工具会遍历所有的默认共享库目录,如/lib、/usr/lib,并更新所有的软链接,将其指向最新版本的共享库。

链接名

需要链接一个libXXX.so.x.y.z的共享库,只需要在GCC编译时加上-lXXX选项即可!其中,XXX也被称为链接名。

符号版本

如果一个ELF可执行文件所依赖的共享库的次版本号高于系统中该共享库的次版本号(主版本号相等),SO-NAME机制也解决不了此时有可能发生的不兼容问题。为此,现在的操作系统使用一种更加精巧的机制——符号版本。

符号版本的基本思路是:对共享库中每个导入&导出的符号关联一个版本号,每次共享库升级时,给那些新添加/修改的符号关联升级后的共享库版本命名。

共享库系统路径

目前包括Linux在内的大部分开源操作系统都遵守FHS标准,这个标准规定了一个系统中的系统文件该如何存放,以及每个目录的组织与作用,共享库作为系统中重要的文件,也是如此。FHS规定:一个系统中主要有两个存放共享库的位置——/lib与/usr/lib,以及一个存放第三方程序共享库的位置/usr/local/lib。

  • /lib。存放系统中最关键的共享库,如动态链接器、C语言运行库、数学库等,这些库主要被/bin与/sbin下的程序用到,以及系统启动所需;
  • /usr/lib。存放开发时会用到的共享库;
  • /usr/local/lib。存放第三方应用程序所需的共享库;

共享库的查找

一个动态链接的可执行文件所依赖的共享库的路径保存在.dynamic段中,由DT_NEED类型的项表示;保存的路径可以是绝对路径或相对路径,考虑到程序的可移植性,一般使用相对路径。

为加快共享库的查找速度,ldconfig会建立SO-NAME缓存存放在/etc/ld.so.cache,/etc/ld.so.cache的特殊结构极其利于查找。若动态链接器未在/etc/ld.so.cache找到共享库,才会去遍历/lib与/usr/lib这两个目录。如果仍然没有找到,则会返回错误信息。

环境变量

  • LD_LIBRARY_PATH。临时改变某个应用程序的共享库查找路径;因此更准确地说,动态链接器最先去LD_LIBRARY_PATH指定的路径下查找共享库;
  • LD_PRELOAD。指定预先装载的共享库或目标文件,LD_PRELOAD指定的文件会在动态链接器按照既定规则搜索共享库进行装载之前被装载,无论程序是否依赖它们。
  • LD_DEBUG。设置该环境变量之后,动态链接器会在运行时打印各种有用的信息。

 

共享库的创建与安装

创建共享库

在GCC编译时,加上-shared-fPIC两个选项,就可以输出共享库;此外,还可以加上-WI选项,该选项可以将指定的参数传递给链接器,如-WI, -soname, my_soname就是将-soname my_soname传递给链接器,用来指定生成的共享库的SO-NAME。如下是一个GCC编译出共享库的例子。

1
gcc -shared -fPIC -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0.0 libfoo1.c libfoo2.c -lbar1 -lbar2

在链接器输出可执行文件时,只会将那些在链接时被其他共享模块引用到的符号放到动态符号表,则动态加载模块在被加载时无法引用主模块在链接时未被其他共享模块引用的符号,即反向引用失败。为避免上述问题,ld链接器提供了-export-dynamic参数,该参数指定ld链接器在输出可执行文件时将所有全局符号导出到全局符号表。

清除符号信息

strip工具可清除共享库/可执行文件的所有符号与调试信息。

安装共享库

安装共享库最简便的方法是将其复制到某个标准的共享库目录,再运行ldconfig。需要root权限。

共享库构造与析构函数

GCC提供了为共享库定义构造/析构函数的方法:在函数声明时加上__attribute__((constructor))__attribute__((destructor))修饰。同时,GCC还支持为构造/析构函数指定优先级,如__attribute__((constructor(5))),对于构造函数,优先级越小越先于其他构造函数执行,而对于析构函数与之相反。

共享库脚本

事实上,共享库除了是ELF共享对象文件外,还可以是链接脚本,例如将C运行库与数学库结合起来的共享库libfoo.so文件的内容可以是

1
GROUP( /lib/libc.so.6 /lib/libm.so.2)