Linux下动态库的创建与更新

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。要注意的是
逗号的后面没有空格,有时我们会习惯性地加上一个空格,导致编译出错。

《Linux下动态库的创建与更新》

$ 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]
......

《Linux下动态库的创建与更新》

此时当前文件夹下的文件为

$ 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. 使用共享库

《Linux下动态库的创建与更新》

现在我们再写一个简单的代码,使用一下我们刚刚创建的共享库。

/*
* 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

《Linux下动态库的创建与更新》出现了运行时出错,这是因为动态加载器不知道到哪里加载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
    原文作者:dlh0313
    原文地址: https://blog.csdn.net/dlh0313/article/details/52094183
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞