本文将演示4种各自独立的得到最终二进制文件的方式。代码采用C语言。
首先列出所有代码文件内容,一共3个文件:drive.h,drive.c,main.c,分别为 动态库libdrive.so 的头文件、函数实现文件、主入口main()文件。内容分别如下。为了简明易懂,只以最简单的功能实现。
(1) drive.h 文件内容:
//声明 加法函数的函数入参和返回值。int dr(int a, int b);
(2) drive.c 文件内容:
#include <stdio.h>//定义一个加法函数int dr(int a, int b) { return a + b;}
(3) main.c 文件内容
//调用动态库内的add()函数,3+5,所以打印结果应当为8#include <stdio.h>#include "drive.h"int main() { // 调用加法函数 int result = add(3, 5); printf("Sum: %d/n", result); return 0;}
(1) 编译 drive.c 文件为.o 文件:
# gcc -c drive.c -o drive.o
gcc 的几个重要编译参数,上面用到了-c 、-o 等参数,下面还会用到所以再次贴上参数说明:
-c 编译、汇编到目标代码,不进行链接。 -o <文件> 输出到 <文件>。 -pie 生成动态链接的位置无关可执行文件。 -shared 生成一个共享库。 -static 告诉ld在链接的时候生成纯静态可执行文件。
(2) 使用编译drive.c得到的drive.o 文件作为材料,生成 libdrive.a 静态库文件。文件名前面加lib是为了gcc链接文件时 默认的约定找以lib开始的库文件:
# ar rcs libdrive.a drive.o
(3) 链接得到最终可执行文件:
# gcc main.c -L$PWD -ldrive -o main-static# file main-static main-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=84fa8514ed5a9ef043b88d1957de83248208dac2, for GNU/Linux 3.2.0, not stripped[lph@localhost 2023-12-12]$ ldd main-static linux-vdso.so.1 (0x00007ffd087f3000) libc.so.6 => /lib64/libc.so.6 (0x00007fb2cbb82000) /lib64/ld-linux-x86-64.so.2 (0x00007fb2cbd79000)
当前目录下只有libdrive.a 库,没有动态库,所以不会优先链接.so库,只能链接libdrive.a库。所以我使用file 命令查看main-static的成分,我以为它就是静态文件,结果发现怎么还是动态链接的?(链接了3个动态库文件)
虽然通过-ldrive 我们确实链接了libdrive.a静态文件进入 main-static文件内部(所以以上出现的链接信息里不会显示libdrive.so 或 libdrive.a,但file的结果说得到的main-static文件仍然是dynamically linked得的,为什么会这样呢?
sudo yum install glibc-static
新安装的libc的静态库文件的路径:
# rpm -q --list glibc-static/usr/lib64/libBrokenLocale.a/usr/lib64/libc.a/usr/lib64/libm-2.37.a/usr/lib64/libm.a/usr/lib64/libmvec.a/usr/lib64/libresolv.a
重新尝试实现彻底的静态链接:
[lph@localhost 2023-12-12]$ gcc -static main.c -L. -ldrive -o main-static-true[lph@localhost 2023-12-12]$ file main-static-true main-static-true: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=be4d6a0e422b3a1ae1ccf0a9b162f5e628eb47b3, for GNU/Linux 3.2.0, not stripped, too many notes (256)[lph@localhost 2023-12-12]$ ldd main-static-true 不是动态可执行文件
以上 ldd 的结果说明,main-static-true 文件才是真正静态链接的。
来对比一下 假的 main-static 跟真的可执行文件大小差距多大:
$ ls -lht main-static*776K 12月12日 22:01 main-static-true17K 12月12日 21:42 main-static
文件大小相差45倍。由于功能少所以这个倍数并不严谨。但大体上我们可以了解纯静态的可执行文件,确实会比 dynamic linked的可执行文件大很多。不过由于现在硬盘和内存的扩大,成本降低,越来越多新编程语言默认采用纯静态编译可执行文件。以增强跨平台的运行能力。
将当前目录下的所有.o文件(目前只有 drive.o一个文件)作为材料,生成最终的 libdrive.so动态库文件(实际相当于将 .o文件打了个压缩包到 .a文件里,并调整了链接符号)。
gcc -shared *.o -o libdrive.so
所以我们目录下截至现在已有这些文件:
# ls -lht总计 52K-rwxrwxr-x 1 lph lph 16K 12月12日 16:43 libdrive.so-rwxrwxr-x 1 lph lph 16K 12月12日 16:30 main-static-true-rwxrwxr-x 1 lph lph 16K 12月12日 16:20 main-static-rw-rw-r-- 1 lph lph 136 12月12日 16:18 main.c-rw-rw-r-- 1 lph lph 1.4K 12月12日 16:17 libdrive.a-rw-rw-r-- 1 lph lph 1.3K 12月12日 16:17 drive.o-rw-rw-r-- 1 lph lph 91 12月12日 16:12 drive.c-rw-rw-r-- 1 lph lph 91 12月12日 16:12 drive.h
既然是动态库,总要能被调用者直到你的暴露的借口。我们验证下这个libdrive.so动态库是否也暴露了add函数呢?
$ nm -D libdrive.so 00000000000010f9 T add w __cxa_finalize@GLIBC_2.2.5 w __gmon_start__ w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable
从结果看,确实暴露了add 函数。至于其他4个W是什么作用,我们目前暂时不管。
gcc main.c -L./ -ldrive -o main_share
上面说过,当libdrive.so 不存在时,libdrive.a 存在,则gcc 根据链接 -L./ -ldrive 参数,在对应目录下只找libdrive.a,所以以前只能链接.a文件。现在有了.so文件,那么-L./ -ldrive会优先链接libdrive.so。让我们用ldd 命令检验一下:
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ldd ./main_share linux-vdso.so.1 (0x00007ffccd74a000) libdrive.so (0x00007ff2cbb28000) libc.so.6 => /lib64/libc.so.6 (0x00007ff2cb933000) /lib64/ld-linux-x86-64.so.2 (0x00007ff2cbb2f000)
这里我们在命令中临时设置 库文件搜索路径包含当前路径$PWD。
从结果看,我们的main_share 确实链接了动态库 libdrive.so。
「完」
先清理一下现场:删掉 drive.o libdrive.so 这两个文件。
$ rm -f drive.o libdrive.so
这里使用现有的 libdrive.a 动态库,看能否转化成 libdrive.so 文件。
(1) 之前说过.a相当于.o 文件的压缩包,那么我们可以分离出 drive.o 文件
ar -x libdrive.a
(2) 编译生成 .so 文件
gcc -shared *.o -o libdrive.so
(3) 查看导出函数
$ nm -D libdrive.so00000000000010f9 T add w __cxa_finalize@GLIBC_2.2.5 w __gmon_start__ w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable$
结果与上上面的 nm -D libdrive.so 输出一致。作为验证我们将使用这个转化出的so动态库测试对main()的链接和运行效果:
$ gcc main.c -L./ -ldrive -o main_share_from_libdrive_amain.c: 在函数‘main’中:main.c:6:18: 警告:隐式声明函数‘add’ [-Wimplicit-function-declaration] 6 | int result = add(3, 5); | ^~~ $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./main_share_from_libdrive_aSum: 8
警告信息不管。运行效果符合预期,打印了3+5的结果。说明静态库转成动态库也是OK可以用的(实际场景gcc还要加-fPIC 参数来生成动态库,以增强动态库的通用性不依赖于固定地址,本文不深入解析 -fPIC,请读者自己查信息 )。
总结一下,经过以上4个场景的演示验证,我们对Linux的动态库、静态库之间的关系,有了更深入的认知。
本文链接:http://www.28at.com/showinfo-26-50765-0.htmlLinux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: SpringBoot中如何优雅地个性化定制Jackson
下一篇: C++中的RAII机制及其智能指针的应用