Linux下动态库(libname.x.y.z)的创建与更新
由于主程序和它依赖的共享库是由不同的开发者开发的。共享库的开发者会不停地更新共享库的版本,以修正bug,增加功能或改进性能。版本多了之后,就可能会引起不兼容问题。在Windows上,这叫做“DLL HELL”。Linux下同样也会遇到相似问题,Linux对动态库采用如“libname.so.x.y.z”的命名规则来解决兼容问题。其中x代表主版本号,y代表次版本号,z代表发行号。根据约定,只要主版本号x相同,就不会引起兼容问题,即libname.so.x,Linux称之为SO-NAME。
程序会把它依赖的共享库的名字和主版本号(即SO-NAME)记录在dynamic段中。程序加载时,动态加载器会去dynamic段中寻找这些SONAME,以加载适当版本的依赖库,从而解决兼容问题。可以通过readelf -d programname 或者 ldd programname查看。
1. 创建共享库
共享库代码:
/*
* filename:hello.c
* version:1.0.0
*/
#include <stdio.h>
void hello(void)
{
printf("HelloWorld\n");
}
/*
* filename:hello.h
* version:1.0.0
*/
#ifndef HELLO_H_
#define HELLO_H_
void hello(void);
#endif
在Ubuntu15中,使用如下命令把hello.c编译成动态库,版本1.0.0。要注意的是
逗号的后面没有空格,有时我们会习惯性地加上一个空格,导致编译出错。
$ gcc -shared -fPIC -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0 hello.c
- -shared表示输出结果是动态共享库
- -fPIC表示生成位置无关码
- -Wl表示把后面的两个参数(soname 和 libhello.so.1)传递给链接器。注意这里的l是love的首字母,且是小写
此时通过readelf查看新生成的目标文件,如下图所示,可以发现SONAME已经被保存到了目标文件中。
$ readelf -d libhello.so.1.0.0
Dynamic section at offset 0xf04 contains 25 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000e (SONAME) Library soname: [libhello.so.1]
......
此时当前文件夹下的文件为
$ tree
.
├── hello.c
├── hello.h
└── libhello.so.1.0.0
0 directories, 3 files
共享库创建完成后,我们需将它安装到系统中,以便其他程序能够使用它。通常有两种方法
- 在root权限下,将目标文件拷贝到/lib 或者/usr/lib目录下,然后再运行一下ldconfig命令,生成一个符号链接。这是最简单的方法,但需要管理员权限
- 在非root权限下,运行ldconfig -n shared_library_directory。其中-n表示在当前目录下创建符号链接。当然后续还需将当前目录导出
我们这里使用的是方法2。得到如下结果。可以发现生成了一个符号链接,指向我们的共享库。动态加载时(注意不是链接时)依靠的就是这个SONAME。
$ tree
.
├── hello.c
├── hello.h
├── libhello.so.1 -> libhello.so.1.0.0
└── libhello.so.1.0.0
0 directories, 4 files
2. 使用共享库
现在我们再写一个简单的代码,使用一下我们刚刚创建的共享库。
/*
* filename:test.c
* version:1.0.0
*/
#include "hello.h"
int main(void)
{
hello();
return 0;
}
然后编译链接该测试程序
$ gcc test.c -L. -lhello -o test
/usr/bin/ld: cannot find -lhello
collect2: error: ld returned 1 exit status
其中“-L. -lhello”表示在当前目录下查找libhello.so。当前文件夹下确实没有libhello.so,所以出错。这时我们需要通过ln -s 命令创建一个符号链接,让它指向我们的共享库。
$ ln -s libhello.so.1.0.0 libhello.so
现在我们再次编译(gcc test.c -L. -lhello -o test)就通过了。这时通过ldd命令查看可执行文件test的依赖关系。
$ ldd test
linux-gate.so.1 => (0xb7795000)
libhello.so.1 => not found
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75be000)
/lib/ld-linux.so.2 (0x80040000)
可以发现libhello.so.1状态是not found,所以此时如果运行程序,一定会出错。
$ ./test
./test: error while loading shared libraries: libhello.so.1:
cannot open shared object file: No such file or directory
出现了运行时出错,这是因为动态加载器不知道到哪里加载libhello.so.1。
解决方法
- 编辑 /etc/ld.so.conf, 将本次测试用目录加入文件中,然后执行ldconf命令
- 执行 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH,临时方法。
我们这只是测试,所以使用的是临时方法。此时,我们运行测试程序,就会输出正确结果了。
3. 模拟发布动态库
我们将libhello.so.1.0.0和test.c 拷贝到另一个文件夹中。因为程序加载动态库时会寻找符号链接libhello.so.1,所以我们需要执行一下 “ldconfig -n .”,在新文件夹下生成一个符号链接。此时如果运行报错,有可能就是你把终端关了,重新开一个新终端,那么我们就需要再次执行一下刚才那个导出LD_LIBRARY_PATH命令,然后再运行程序。
4. 更新共享库,升级次版本号
此时回到我们先前的目录,不是我们测试发布动态库那个目录了。修改如下代码。
/*
* filename:hello.c
* version:1.1.0
*/
#include <stdio.h>
void hello(void)
{
printf("Minor Version Number was modified!\n");
}
然后执行
$ gcc -shared -fPIC -Wl,-soname,libhello.so.1 -o libhello.so.1.1.0 hello.c
注意:我们这里把libhello.so.1.0.0升级到了libhello.so.1.1.0。前面我们曾经提到,只要libname.x,y.z中,x不变,用户程序就不必修改,即我们这里的test.c无需修改。
再把新生成的libhello.so.1.1.0拷贝到那个模拟发布目录下。把终端切换到模拟发布目录下,执行“ldconfig -n .”,重新生成符号链接。此时的该目录下的文件结构如下:
$ tree
.
├── libhello.so.1 -> libhello.so.1.1.0
├── libhello.so.1.0.0
├── libhello.so.1.1.0
└── test
0 directories, 4 files
执行程序,输出修改后的结果。
5. 更新版本库 升级主版本号
再次修改hello.c
/*
* filename:hello.c
* version:2.0.0
*/
#include <stdio.h>
void hello(void)
{
printf("Major Version Number was modified!\n");
}
再次编译共享库
$ gcc -shared -fPIC -Wl,-soname,libhello.so.2 -o libhello.so.2.0.0 hello.c
然后把libhello.so.2.0.0拷贝到那个模拟发行目录,执行ldconfig -n .,然后再查看此时的文件结构
$ tree
.
├── libhello.so.1 -> libhello.so.1.1.0
├── libhello.so.1.0.0
├── libhello.so.1.1.0
├── libhello.so.2 -> libhello.so.2.0.0
├── libhello.so.2.0.0
└── test
0 directories, 6 files
可以发现生成了两个符号链接,但此时执行程序的话,仍然输出“Minor Version Number was modified!”,这是因为我们并没有重新编译test.c,它保存的SONAME仍然是”libhello.so.1″,而不是“libhello.so.2″。
本博客参考了以下两个资源,在此致敬
- 程序员的自我修养—-链接,装载与库(俞甲子 石凡 潘爱明)
- http://littlewhite.us/archives/301?utm_source=tuicool&utm_medium=referral