静态库+动态库

    本文绝大部分知识来自Linux/Unix系统编程手册第44章

    很多情况下,要在多个项目复用代码,简单粗暴的方法有把代码拷来拷去。对于编译器来说,有一些重复的工作量(把c源代码编译成可执行文件,有编译和链接等过程)。它就是编译。因此降低工作量的做法只编译一次,然后把这些object(扩展名为.o)文件拷来拷去。如果复用的object文件成千上百。这么干似乎容易出错。有没有把这些object文件打包成一个文件的方法。答是静态库。

1.静态库

    1.1创建 r(替换)

gcc -g -c static_tst.c static_tst2.c
ar -r libstatic.a static_tst.o static_tst2.o
rm static_tst.o static_tst2.o

    1.2查看 t(查看) v(详细)

ar -tv libstatic.a

    1.3从静态库中删除一个object文件

ar -d libstatic.a static_tst.o

    1.4使用

gcc use_libstatic.c libstatic.a

2.动态库

    2.1创建

        2.1.1方法1

gcc -g -c -fPIC -Wall test1.c test2.c test3.c
gcc -g -shared -o libtest.so test1.o test2.o test3.o

        2.1.2方法2

gcc -g -fPIC -Wall test1.c test2.c test3.c -shared -o libtest.so

 

    2.2-fPIC

        -fPIC选项指定编译器应该生成位置独立的代码,这会改变编译器生成执行特写操作的代码的方式,包括访问全局,静态和外部变量,访问字符串常量,以及获取函数地址。这些变更使得代码可以在运行时被放置在任意一个虚拟地址处。

    如果.o文件在编译时加了-fPIC选项,用如下的命令都会有输出

nm test1.o |grep _GLOBAL_OFFSET_TABLE_ 
readelf -s test1.o |grep _GLOBAL_OFFSET_TABLE_

    2.3使用和运行

        使用

gcc -g -Wall uselibtest.c libtest.so

        运行

./a.out
./a.out: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

    解决这个问题就需要做第二件事情:动态链接,即在运行时解析内嵌的库名。这个任务是由动态链接器来完成的。动态链接器本身也是动态库。centos 6.5动态链接器位置。

[root@iZ11jvehtjhZ shared]# ll /lib64/ld-linux-x86-64.so.2 
lrwxrwxrwx 1 root root 10 1月  30 2015 /lib64/ld-linux-x86-64.so.2 -> ld-2.12.so

    动态链接器会检查程序所需的共享库清单并使用一组预先定义好的规则在文件系统上找出相关的库文件。其中一些规则指定了

一组存放共享库的标准目录。标准目录有/lib和/usr/lib中。之所以出现上面的错误是因为程序所需的目录位于当前工作目录中,而不是动态链接器搜索的标准目录清单中。

    2.4LD_LIBRARY_PATH环境变量

    相信大家都使用过env LD_LIBRARY_PATH=./libs ./a.out 这种写法。可为什么这么写,可能也没有深究过。设置LD_LIBRARY_PATH,那么动态链接器在查找标准库目录之前会先查找该环境变量列出的目录中的共享库。

    注LD_LIBRARY_PATH列表中的空目录(dirx::dirx中间的空目录)等价于.,即当前工作目录。将LD_LIBRARY_PATH的值设置为空字符串并不能达到同样效果。

    2.5别名(SONAME)

    可以使用别名来创建共享库,这种别名称为soname。如果共享库拥有soname,那么在静态链接阶段会将soname嵌入到可执行文件中,而不会使用真实名称,同时后面的动态链接器在运行时也会使用这个soname来搜索库。引入soname的目的是为了提供一个中间层,使得可执行程序能够在运行时使用与链接时使用的库不同的(但兼容的)共享库。

    使用soname创建动态库

gcc -fPIC -g -Wall -Wl,-soname,libtest.so.1 -shared -o libtest.so test1.c test2.c test3.c

    查看SONAME

[root@iZ11jvehtjhZ shared]# objdump -p libtest.so |grep SONAME
  SONAME               libtest.so.1
[root@iZ11jvehtjhZ shared]# readelf -d libtest.so |grep SONAME
0x000000000000000e (SONAME)             Library soname: [libtest.so.1]

   运行

[root@iZ11jvehtjhZ shared]# ./a.out 
./a.out: error while loading shared libraries: libtest.so.1: cannot open shared object file: No such file or directory

    2.6检查共享库的某些文件

    ldd命令显示了一个程序运行所需的共享库。

[root@iZ11jvehtjhZ shared]# ldd ./a.out 
	linux-vdso.so.1 =>  (0x00007fffc97d6000)
	libtest.so.1 => not found
	libc.so.6 => /lib64/libc.so.6 (0x0000003d49000000)
	/lib64/ld-linux-x86-64.so.2 (0x0000003d48800000)

   objdump命令反汇编一个动态库

[root@iZ11jvehtjhZ shared]# objdump -d libtest.so |head

libtest.so:     file format elf64-x86-64


Disassembly of section .init:

00000000000004c0 <_init>:
 4c0:	48 83 ec 08          	sub    $0x8,%rsp
 4c4:	e8 47 00 00 00       	callq  510 <call_gmon_start>
 4c9:	e8 e2 00 00 00       	callq  5b0 <frame_dummy>

    nm命令查看哪个动态库实现了strstr函数

[root@iZ11jvehtjhZ shared]# nm -A /lib64/* 2>/dev/null|grep strstr
/lib64/libc-2.12.so:0000003d4908e980 t __GI_strstr
/lib64/libc-2.12.so:0000003d4908e980 t __strstr_sse2
/lib64/libc-2.12.so:0000003d4912a4a0 t __strstr_sse42
/lib64/libc-2.12.so:0000003d4908e4c0 i strstr
/lib64/libc.so.6:0000003d4908e980 t __GI_strstr
/lib64/libc.so.6:0000003d4908e980 t __strstr_sse2
/lib64/libc.so.6:0000003d4912a4a0 t __strstr_sse42
/lib64/libc.so.6:0000003d4908e4c0 i strstr
/lib64/libnsl-2.12.so:                 U strstr@@GLIBC_2.2.5
/lib64/libnsl.so.1:                 U strstr@@GLIBC_2.2.5
/lib64/libnss_compat-2.12.so:                 U strstr@@GLIBC_2.2.5
/lib64/libnss_compat.so.2:                 U strstr@@GLIBC_2.2.5

    2.7共享库的命名规则

    共享库命名格式规范为libname.so.major-id.minor-id。主版本标识符由一个数字构成,这个数字随着库的每个兼容版本的发布而顺序递增。次要版本标识可以是任意字符串。但根据惯例,这要么是一个数字,要么是两个由点分隔的数字,其中一个数字标识出了次要版本,第二个数字表示该次要版本中的补丁号或修订号。如下所示

libtest.so.0.0.1
libtest.so.1.0.2

    当某个共享库有多个版本时。通常,每个库的主要版本的soname会指向在主要版本中最新的次要版本。由于静态链接时soname已被嵌入到可执行文件中(soname通常精确到主版本号libtest.so.1),只要改变soname符号链接指向的最新次版本号的共享库,可以确保可执行文件在运行时能够加载库的最新次要版本。

    除了真实名称和soname之外,通常还会为每个共享库定义第三个名称:链接名称,将可执行文件与共享库链接起来会用到这个名称。链接名称是一个只包含库名同时不包含主要或次要版本标识符的符号链接,因此其形式为libname.so。有了链接名称之后就可以构建自动使用共享库的正确版本的独立于版本的链接命令了。链接名称最好使用批向soname的链接,因此对soname所做的变更会自动反应到链名称上。

    2.8使用标准规范创建一个共享库

         创建动态库,真实名称为libtest.so.1.0.1,soname为libtest.so.1

gcc -g -fPIC -Wall test1.c test2.c test3.c -Wl,-soname,libtest.so.1 -shared -o libtest.so.1.0.1

    接着为soname和链接名称创建恰当的符号链接

[root@iZ11jvehtjhZ shared]# ln -s libtest.so.1.0.1 libtest.so.1
[root@iZ11jvehtjhZ shared]# ln -s libtest.so.1 libtest.so

    验证

[root@iZ11jvehtjhZ shared]# ll libtest.so*
lrwxrwxrwx 1 root root   12 10月 28 21:14 libtest.so -> libtest.so.1
lrwxrwxrwx 1 root root   16 10月 28 21:14 libtest.so.1 -> libtest.so.1.0.1
-rwxr-xr-x 1 root root 8606 10月 28 21:13 libtest.so.1.0.1

    2.9安装共享库

    前面将共享库创建在私有目录下,然后使用LD_LIBRARY_PATH环境变量来确保动态链接器会搜到该目录。任何用户都可以使用这种技术,但在生产应用程序中不应该采用这种技术。一般来讲,共享库及其关联的符号链接会被安装在其中一个标准目录中,标准库目录包括:

    /usr/lib,这是大多数标准库安装的目录。

    /lib,应该将系统启动时用到的库安装在这个目录中(因为在系统启动时可能还没有挂载/usr/lib)。

    /usr/local/lib,应该将非标准或实验性的库安装在这个目录中(对于/usr/lib是一个由多个系统共享的网络挂载与库文件位于同一个目录中)

    其中一个在/etc/ld.so.conf中列出的目录。

    2.10 ldconfig

    ldconfig解决了共享库的两个潜在问题。

        共享库可以位于各种目录中,如果动态链接器需要通过搜索所有这些目录来找出一个库并加载这个库,那么整个过程将非常慢。

        当安装了新版本的库或者删除了旧版本的库,那么soname符号链接就不是最新的。

    ldconfig程序通过扫行两个任务来解决这些问题。

    它搜索一组标准的目录并创建或更新一个缓存文件/etc/ld.so.cache使之包含在所有这些目录中的主要库版本(每个库的主要版本的最新的的次要版本)列表。动态链接器在运行时解析库名称时会轮流使用这个缓存文件。为了构建这个缓存,ldconfig会搜索在/etc/ld.so.conf中指定的目录,然后搜索/lib和/usr/lib。/etc/ld.so.conf文件由一个目录路径名列表构成,其中路径名之间用换行,空格,制表符,逗号或冒号分隔。

    ldconfig -p会显示/etc/ld.so.cache的当前内容

    它检查第个库的各个主要版本的最新次要版本(即具有最大的次要版本号的版本)以找出嵌入的soname,然后在同一目录中为每个soname创建(或更新)相对符号链接。

    为了能够正确执行这些动作,ldconfig要求库的名称要根据前面介绍的规范来命名—即库的真实名称包含主要和次要标识符,它们随着库的版本的更新而恰当的增长。

    默认情况下,ldconfig会执行上面的两个动作,但可以使用命令行选顶来指定它执行其中一个动作:-N选顶会防止缓存的重建,-X选项会阻止soname符号链接的创建。些外,-v(verbose)选项会使得ldconfig输出描述其所扫行的动作的信息。

    每当安装了一个新的库,更新删除了一个既有库,以及/etc/ld.so.conf中的目录列表被修改之后,都因该运行ldconfig。

   假设需要安装一个库的两个不同的主要版本,那么需要做下面的事情。

#唯一需要注意的是libtest.so.2.0.0的soname是libtest.so.2
[root@iZ11jvehtjhZ shared]#mv libtest.so.1.0.1 libtest.so.2.0.0 /usr/lib 
[root@iZ11jvehtjhZ lib]# ldconfig -v |grep libtest
	libtest.so.2 -> libtest.so.2.0.0 (改变)
	libtest.so.1 -> libtest.so.1.0.2 (改变)

    查看

ll /usr/lib/libtest.so*
lrwxrwxrwx 1 root root   12 10月 28 22:28 /usr/lib/libtest.so -> libtest.so.1
lrwxrwxrwx 1 root root   16 10月 28 22:30 /usr/lib/libtest.so.1 -> libtest.so.1.0.2
-rwxr-xr-x 1 root root 8606 10月 28 22:24 /usr/lib/libtest.so.1.0.1
-rwxr-xr-x 1 root root 8606 10月 28 22:28 /usr/lib/libtest.so.1.0.2
lrwxrwxrwx 1 root root   16 10月 28 22:30 /usr/lib/libtest.so.2 -> libtest.so.2.0.0
-rwxr-xr-x 1 root root 8607 10月 28 22:30 /usr/lib/libtest.so.2.0.0

    还需要为链接名称创建符号链接,如下面的命令所示

[root@iZ11jvehtjhZ lib]# unlink libtest.so
[root@iZ11jvehtjhZ lib]# ln -s libtest.so.2 libtest.so

    如果创建和使用的是一个私有库,那么可以通过使用-n选项让ldconfig创建soname符号链接。这个选项指定了ldconfig只处理在命令行中列出的目录中的库,而无需更新缓存文件。

#gcc -fPIC -g -Wall -Wl,-soname,libtest.so.1 -shared -o libtest.so.1.0.0 test1.c test2.c test3.c
#ln -s libtest.so.1 libtest.so.1.0.0
# ldconfig -nv .
.:
	libtest.so.1 -> libtest.so.1.0.1 (改变)

    2.11在目标文件中指定库搜索目录

    除了两种让可执行文件找到共享库的方式使用LD_LIBRARY_PATH环境变量和将共享库安装到其中一个标准目录中(/lib /usr/lib或在/etc/ld.so.conf中列出的其中一个目录)

    还存在第三种方式:在静态编译阶段可以在可执行文件中插入一个在运行时搜索共享库的目录列表。这种方式对于库位于一个固定的但不属于动态链接器搜索的标准位置的位置中时是非常有用的。要实现这种方式需要在创建可执行文件时使用-rpath链接选项。

    要实现这种方式需要在创建可执行文件时使用-rpath链接选项

gcc -g -Wall -Wl,-rpath,./libs uselibtest.c libs/libtest.so#方式1
gcc -g -Wall -Wl,-rpath,'$ORIGIN'/libs uselibtest.c libs/libtest.so#方式2

    2.12 ELF DT_RPATH和DT_RUNPATH条目

    在第一版ELF规范中,只有一种rpath列表能够被嵌入到可执行文件或共享库中,它对应于ELF文件中的DT_RPATH标签。

后续的ELF规范舍弃了DT_RPATH,同时引入了一种新的标签DT_RUNPATH来表示rpath列表。这两种rpath列表之间的差别在于当动态链接器在运行时搜索共享库时它们相对于LD_LIBRARY_PATH环境的变量的优先级:DT_RPATH的优先级更高,而DT_RUNPATH的优先级则更低。

    为了让链接器将rpath列表创建为DT_RUNPATH条目必须要额外使用--enable-new-dtags链接器选项。



你可能感兴趣的:(静态库+动态库)