文档章节

静态库+动态库

guonaihong
 guonaihong
发布于 2015/10/26 22:44
字数 3241
阅读 74
收藏 1

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

    本文绝大部分知识来自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链接器选项。



© 著作权归作者所有

guonaihong

guonaihong

粉丝 6
博文 83
码字总数 27591
作品 1
徐汇
程序员
私信 提问
Linux — 浅析静态库和动态库

浅析静态库和动态库 我们编写的程序一般需要经过预处理,编译,汇编和链接这几个步骤才变成可执行的程序. 在实际的软件开发中,对于一些需要被许多模块反复使用的 公共代码,我们将它们编译为...

Dawn_sf
2018/01/01
0
0
创建C语言静态库与动态库

“程序库”就是包含了一些同音函数的数据和二进制可执行机器码的文件。这些文件是目标文件的一种,其不能单独执行。但是如果将其与其他的可执行代码结合起来就可以执行。这些目标文件通常可以...

大道无名
2016/10/24
474
0
静态库和动态库

静态库和动态库 ASPIRE2017-01-1127 阅读 gccLinux =Start= 缘由: 补充系统知识 正文: 参考解答: 我们通常把一些公用函数制作成函数库,供其它程序使用(代码的复用)。函数库可分为静态库...

ASPIRE
2017/01/11
0
0
Qt动态库静态库的创建、使用、多级库依赖等详细说明

本文描述的是windows系统下,通过qtcreator在pro文件中添加动态库与静态库的方法: 1、添加动态库(直接添加动态库文件.dll,非子项目) 通过qtcreator创建动态库的方法就不在此处赘述了。 ...

苦涩的茶
2018/08/16
0
0
在Linux中创建静态库和动态库范例 (hello.c)

我们通常把一些公用函数制作成函数库,供其它程序使用。 函数库分为静态库和动态库两种。 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。 动态库在程序编译时并不...

AlphaJay
2010/05/31
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

类比思想歪解Java线程

在操作系统的概念里,有内核态,用户态。其实,操作系统的最小执行单位是进程,而进程是分类型的,有两种类型,内核进程,用户进程。 内核进程由操作系统启动时创建,用户进程是由用户程序启...

萧默
45分钟前
2
0
Git推送错误“ [[远程拒绝]主机->主机(分支当前已签出)”)

昨天,我发布了一个有关如何将Git存储库从我的一台计算机克隆到另一台计算机的问题 , 如何从另一台计算机“ git clone”? 。 现在,我可以成功地将Git存储库从源(192.168.1.2)克隆到目标...

javail
55分钟前
4
0
Selenium 4.0 Alpha更新日志

早在2018年8月,整个测试自动化社区就发生了一件重大新闻:Selenium的创始成员Simon Stewart在班加罗尔Selenium会议上正式确认了Selenium 4的发布日期和一些重要更新。 Selenium 4.0 Alpha版...

八音弦
今天
7
0
2、编写程序求Sn=a+aa+aaa+…+aa…aa的值,其中a是1—9之间的一位数字,n表示 a的位数

//编写程序求Sn=a+aa+aaa+…+aa…aa的值,其中a是1-9之间的一位数字, //n表示 a的位数 #include<stdio.h> int main() { int a,n,i,Sn=0,Z=0; printf("please intput a:\n"); scanf("%d",&a......

201905021729吴建森
今天
5
0
Git中的HEAD是什么?

您会看到Git文档说出类似 分支必须在HEAD中完全合并。 但是到底什么是Git HEAD ? #1楼 了解正确答案的一种好方法是运行git reflog HEAD ,您可以获得HEAD所指向的所有位置的历史记录。 #2楼...

技术盛宴
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部