服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - Linux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理

Linux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理

2023-12-20 17:14未知服务器之家 Linux

本文将演示4种各自独立的得到最终二进制文件的方式。代码采用C语言。 用gcc将C语言代码生成静态库.a文件,再与编译后的 main.o 合成最终的静态链接的可执行文件,查看运行结果。 用gcc将C语言代码生成动态库,待用。 用gcc将C语

本文将演示4种各自独立的得到最终二进制文件的方式。代码采用C语言。

  • 用gcc将C语言代码生成静态库.a文件,再与编译后的 main.o 合成最终的静态链接的可执行文件,查看运行结果。
  • 用gcc将C语言代码生成动态库,待用。
  • 用gcc将C语言代码编译并链接动态库,生成可执行文件main,运行时依赖动态库so文件。
  • 演示用ar如何转换 静态库文件得到动态库文件。可被用于可执行文件的链接。

Linux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理

本文代码文件内容

首先列出所有代码文件内容,一共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;
}

用gcc将C语言代码生成静态库 .a,被链接入最终可执行文件

(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

Linux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理

Linux从外到内剥开动态库,一个简单例子看懂Linux下的动态库开发原理

新安装的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-true
17K  12月12日 21:42 main-static

文件大小相差45倍。由于功能少所以这个倍数并不严谨。但大体上我们可以了解纯静态的可执行文件,确实会比dynamic linked的可执行文件大很多。不过由于现在硬盘和内存的扩大,成本降低,越来越多新编程语言默认采用纯静态编译可执行文件。以增强跨平台的运行能力。

用gcc将 drive.c 代码生成动态库文件 libdrive.so

将当前目录下的所有.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将C语言 main.c 编译并链接动态库,生成可执行文件main_share,运行时是否依赖动态库so文件?

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。

「完」

演示用ar如何转换 静态库文件得到动态库文件。可被用于可执行文件的链接。

先清理一下现场:删掉 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.so
00000000000010f9 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_a
main.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_a
Sum: 8

警告信息不管。运行效果符合预期,打印了3+5的结果。说明静态库转成动态库也是OK可以用的(实际场景gcc还要加-fPIC参数来生成动态库,以增强动态库的通用性不依赖于固定地址,本文不深入解析 -fPIC,请读者自己查信息 )。

总结一下,经过以上4个场景的演示验证,我们对Linux的动态库、静态库之间的关系,有了更深入的认知。

延伸 · 阅读

精彩推荐
  • Linux理解 Linux/Unix 登录脚本的技巧

    理解 Linux/Unix 登录脚本的技巧

    有一些常见的情况,例如从Debian的包管理程序到Iaas的管理中,很多任务需要设置环境变量才能正常运行。 有时,程序通常只需要在 登陆时运行一次,例如...

    未知1042023-05-12
  • Linuxlinux驱动程序开发详细介绍

    linux驱动程序开发详细介绍

    前提,一般来说内核代码的错误可能会引起一个用户进程的死亡,或者整个系统的瘫痪,更严重的后果,可能导致磁盘损伤~因此建议最好有一台实验机进行...

    Linux教程网5392019-12-17
  • Linuxlinux top命令详解

    linux top命令详解

    这篇文章主要介绍了linux top命令详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    sparkdev5622022-03-01
  • Linuxssh 登录很慢该如何解决

    ssh 登录很慢该如何解决

    这篇文章主要介绍了ssh 登录很慢该如何解决的相关资料,这里提供了两种方法,DNS反向解析及关闭ssh的gssapi认证的解决办法,需要的朋友可以参考下...

    linuxeye9922021-12-16
  • Linux在Linux系统中创建新的亚马逊AWS访问密钥的方法

    在Linux系统中创建新的亚马逊AWS访问密钥的方法

    如何在Linux系统中创建新的亚马逊AWS访问密钥?我在配置一个需要访问我的亚马逊AWS帐号的应用时被要求提供AWS访问密钥ID和秘密访问密钥,我怎样创建一个...

    Linux教程网6182019-10-30
  • Linux将 Linux 终端与 Nautilus 文件管理器结合起来

    将 Linux 终端与 Nautilus 文件管理器结合起来

    Nautilus 是 GNOME 桌面环境中的图形化文件浏览器。你可以使用它来访问和管理系统中的文件和文件夹。 尽管并非所有人都喜欢使用终端来管理文件和目录,...

    未知812023-08-08
  • Linuxlinux设置tomcat自启动的方法

    linux设置tomcat自启动的方法

    这篇文章主要介绍了linux设置tomcat自启动的方法,需要的朋友可以参考下...

    Linux教程网8512021-10-10
  • LinuxLinux系统下无法卸载挂载的目录怎么办?

    Linux系统下无法卸载挂载的目录怎么办?

    我们在日常运维中经常性会遇到需要进行磁盘的扩容、卸载、挂载等操作,但是有时候这个系统上跑的应用并没有停止或者有其他的运维同事在操作这个目...

    今日头条10302020-12-30