关于动态链接库的分析

supersponge supersponge     2022-10-25     354

关键词:

 

关于动态链接库的分析

linux系统中动态链接库文件用.so后缀标记,一般命名规则为libxxx.so

 

链接产生动态库.so与编译源码产生的二进制文件.o的关系

现在有工程,源文件包括:

main1.cpp

myAPI.cpp

myAPI.h

其中myAPI.cpp,myAPI.h定义了两个函数ADD(), MINUS()

main1.cpp中则调用ADD(), MINUS()

<实验1>

1)编译产生myHandler.o

g++ -fPIC -c -o myAPI.o myAPI.cpp

2)myAPI.o文件编译链接产生main1

g++ main1.cpp myAPI.o -o main1

3)编译、链接、运行成功,然后用ldd main1查看可执行文件的依赖:

linux-vdso.so.1 =>  (0x00007fffed77c000)

libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f21fc26b000)

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f21fbea1000)

libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f21fbb98000)

/lib64/ld-linux-x86-64.so.2 (0x00007f21fc5ed000)

libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f21fb982000)

4)删除myAPI.o,再运行./main1

结果程序正常运行。

 

分析:这说明,main文件中包含有myAPI.o myHandler.o中的所有可执行代码,除了因为main.cpp中包含了<iostream>库所以需要在运行时动态加载一些.so文件外,没有依赖任何其他库文件。

 

<实验2>

1)编译产生myHandler.o

g++ -fPIC -c -o myAPI.o myAPI.cpp

2)myAPI.o文件打包产生libmyAPI.so

g++ -shared myAPI.o -o libmyAPI.so

3)用libmyAPI.somain1.cpp编译链接产生main2

g++ -o main2 main1.cpp -L. -lmyAPI 

3)编译、链接、运行成功,然后用ldd main2查看可执行文件的依赖:

linux-vdso.so.1 =>  (0x00007fffed77c000)

libmyAPI.so (0x00007fe029706000)

libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f21fc26b000)

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f21fbea1000)

libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f21fbb98000)

/lib64/ld-linux-x86-64.so.2 (0x00007f21fc5ed000)

libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f21fb982000)

4)删除myAPI.o,再运行./main2,程序正常运行

5)删除myAPI.so,再运行./main2,程序运行失败

./main2: error while loading shared libraries: libmyAPI.so: cannot open shared object file: No such file or directory

 

分析:在4)删除myAPI.o后程序运行成功,说明./main2的运行与myAPI.o无关。在4)删除myAPI.so后执行程序提示加载动态库失败,恢复myAPI.so后随机成功运行,直接说明main2依赖myAPI.so文件。原因是main2中缺少某部分执行代码,这部分代码包含在libmyAPI.so中,而main2记录这部分代码的来源,所以当使用ldd main2查看其依赖列表时有libmyAPI.so

 

根据<实验1><实验2>,可以得出结论,.so文件中完全包含了.o中多有可执行代码。另外.o文件不是可执行文件,而.so是可执行文件。.so文件允许程序一开始不加载他所包含的代码,当需要运行它包含的代码时,才根据文件中的路径动态家去加载它的代码。

 

注:

在编译链接main2时,遇到一个有意思的小麻烦:

能够成功编译链接的指令为:g++ -o main2 main1.cpp -L. -lmyAPI

但如果交换源文件和动态链接库的顺序:g++ -o main2 -L. -lmyAPI main1.cpp

g++会报错:

/tmp/ccvwLNKz.o:在函数‘main’中:

main1.cpp:(.text+0x14):对‘ADD(int, int)’未定义的引用

main1.cpp:(.text+0x4b):对‘MINUS(int, int)’未定义的引用

collect2: error: ld returned 1 exit status

原因不明,这可能与g++组织组织文件依赖关,总之记住以后让源文件在前,依赖在后。

 

2 .so文件的路径问题

现在有工程,源文件包括:

main1.cpp

myAPI.cpp

myAPI.h

其中myAPI.cpp,myAPI.h定义了两个函数ADD(), MINUS()

main1.cpp中则调用ADD(), MINUS()

<实验3>

1)首先产生libmyAPI.so,这一次将动态库放入./libs目录下

2)编译链接产生main3

g++ -o main3 main1.cpp -L. -L./libs -lmyAPI

3)成功产生目标,然而运行时,系统报错:

./main3: error while loading shared libraries: libmyAPI.so: cannot open shared object file: No such file or directory

4)ldd main3查看依赖列表

...

libmyAPI.so => not found

...#其他都正常,省略

发现main3的依赖列表中缺省libmyAPI.so的路径,

恩???步骤2)中明明给定了libmyAPI.so的找寻路径-L./libs,为什么依赖列表中会没有?更奇怪的是当我们将libmyAPI.so拷贝到main3所在的目录下时,又运行成功,这又是怎么回事?

分析:

首先排除g++在链接时不会去验证libmyAPI.so是否真实存在这种猜想,因为如果libmyAPI.so不存在,2)中的编译指令不会通过,直接提示缺少库文件。

实际上,原因在于连接器ld的工作模式,注意到2)中指定动态库的参数-lmyAPI而不是直接给其名称。在执行这条编译指令时,链接器ld此处有疑问:在执行g++指令时,到时会不会是用ld指令?)会自动将-lmyAPI扩展为libmyAPI.so然后根据参数中的库文件查找路径-L.-L./libs去寻找libmyAPI.so,可能编译器会验证下libmyAPI.so是否包含目标的依赖(包括直接依赖和间接依赖),如果包含则将参数指定的路径写入依赖表,不包含的直接忽略(此处有疑问,也可能连基本的验证一下libmyAPI.so是否包含源文件中依赖的代码都不会做,但从<实验5>结果看更有可能会验证),当遇到依赖文件不能完全满足编译目标的需求时,编译器不会报错,只有在需要链接时才会报错。又因为编译时依赖的对象是动态库,不会将libmyAPI.so中的代码全部打包到main3中,简单记录libmyAPI.so文件的路径,以便以后运行时动态去加载。然而,执行编译指令时,用户指定的查找路径-L.-L./libs应该只是用来验证,不会将这个查询路径连同文件名一起填写到main3的依赖列表中,此时只会填写为libmyAPI.so,要想加上路径,用户必须在编译参数中写上动态库的全称(当然也可以是绝对路径,但是这样做不利于程序移植)如:

g++ -o main3 main1.cpp ./libs/libmyAPI.so

此时再去查看main3的依赖列表:

...

./libs/libmyAPI.so (0x00007f07463ce000)

...

在真正运行main3时,ld会首先查找其配置目录/etc/ld.so.conf.d/下的若*.conf文件(可以是到某个.conf文件的链接),文件中配置了ld的默认查询路径,若默认路径下目录下有无libmyAPI.so文件,再直接查询依赖列表中的路径是否有效。

 

3 .so文件间依赖的问题

现在有工程,源文件包括:

main2.cpp

myAPI.cpp

myAPI.h

myHandler.cpp

myHandler.h

 

其中myAPI.cpp,myAPI.h定义了两个函数ADD(), MINUS()

myHandler.cpp,myHandler.h中也定义了两个函数mutil_ADD(), mutil_MINUS(),而两个函数分别调用了ADD(), MINUS()函数;

main.cpp中则调用mutil_ADD(), mutil_MINUS()

<实验4>

1)链接产生.so文件

g++ myAPI.o -shared -o libs/libmyAPI.so

g++ myHandler.o -shared -o libs/libmyHandler.so

2).so文件编译链接产生main4

g++ main2.cpp libs/libmyAPI.so libs/libmyHandler.so -o main4

结果链接失败:

./libs/libmyHandler.so:对‘ADD(int, int)’未定义的引用

./libs/libmyHandler.so:对‘MINUS(int, int)’未定义的引用

collect2: error: ld returned 1 exit status

3)产看libmyHandler.so的依赖列表ldd ./libs/libmyHandler.so

statically linked

表明,libmyHandler.so中没有依赖任何动态库。

然而,myHandler.cpp中不是调用了myAPI.cpp定义的函数么,为什么编译指令

g++ myHandler.o -shared -o libs/libmyHandler.so可以通过编译呢?

原因是这种情况下,编译器最多验证下指定的依赖文件(包括.so.a)是否存在,不会进一步验证依赖文件是否覆盖源文件的需求,只有当需要连接时才会验证依赖是否有效。

 

分析:由于myHandler.cpp文件使用了myAPI.cpp中定义的函数,然而在编译链接libmyHandler.so时既没有声明需要链接myAPI.o文件也没有链接libmyAPI.so,所以现在的libmyHandler.so中的ADD()MINUS()属于未定义的引用。

 

<实验5> 

1)链接产生.so文件

g++ -fPIC -c -o libs/myAPI.o myAPI.cpp

g++ -fPIC -c -o libs/myHandler.o myHandler.cpp

 

g++ libs/myAPI.o -shared -o libs/libmyAPI.so

g++ libs/myHandler.o libs/libmyAPI.so -shared -o libs/libmyHandler.so

2).so文件编译链接产生main5

g++ main2.cpp libs/libmyAPI.so libs/libmyHandler.so -o main5

# g++ main2.cpp libs/libmyHandler.so -o main5

3)编译、运行成功,产看main5依赖表

...

libs/libmyHandler.so (0x00007f1c5030e000)

libs/libmyAPI.so (0x00007f1aea515000)

...

???为啥2)中明明声明了libs/libmyAPI.solibs/libmyHandler.so两个依赖,为啥依赖列表中只有libs/libmyHandler.so

可能编译器会验证下libmyAPI.so是否包含目标的依赖(包括直接依赖和间接依赖),如果包含则将参数指定的路径写入依赖表,不包含的直接忽略。

4)产看ldd libs/libmyHandler.so 依赖表

...

libs/libmyAPI.so (0x00007f69ac934000)

...

此时的依赖层次图:

 

4同库新旧版同引用问题

考虑一种特殊情况,要在同一个程序中链接同一个库的新旧两个版本。

<实验6>

目录结构如下:

libs2/

   myAPI.cpp

   myAPI.h

libs1/

   myAPI.cpp

   myAPI.h

myHandler1.cpp

myHandler1.h

myHandler2.cpp

myHandler2.h

main4.cpp

其中libs2/myAPI.cpp与libs1/myAPI.cpp中都定义有命名为的ADD()MINUS()函数(参数列表,返回值,函数名均相同,区别是函数打印的信息不同),而myHandler2.cppmyHandler1.cpp则分别调用llibs2/myAPI.cpp与libs1/myAPI.cpp中的同名函数。main4.cpp中又调用myHandler.cppmyHandler1.cpp中定义的函数。

 

Makefile如下:

step4: 

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI.o libs1/myAPI.cpp

 

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1.o myHandler1.cpp

 

g++ libs2/myAPI.o -shared -o libs2/libmyAPI.so

g++ libs1/myAPI.o -shared -o libs1/libmyAPI.so

 

g++ libs2/myHandler2.o libs2/libmyAPI.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1.o libs1/libmyAPI.so -shared -o libs1/libmyHandler1.so

 

g++ main4.cpp libs1/libmyHandler1.so libs/libmyHandler.so -o main6

理论上依赖关系应该如下图:

 

ldd main6查看main6关系列表:

...

libs1/libmyHandler1.so (0x00007f61c7837000)

libs2/libmyHandler2.so (0x00007f61c7635000)

libs1/libmyAPI.so (0x00007f61c6ce7000)

libs2/libmyAPI.so (0x00007f61c6ae5000)

...

哇塞,貌似很科学的样子。然后运行发现,完全不正确:

libs1: ADD    #正确的话应该打印libs2: ADD

libs1: ADD    #正确的话应该打印libs2: ADD

mutil_ADD2(1,2,3) = 6

libs1: MINUS  #正确的话应该打印libs2: MINUS

libs1: MINUS  #正确的话应该打印libs2: MINUS

mutil_MINUS2(1,2,3) = -4

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

当注释掉main4.cpp调用myHandler1.cpp的代码后,编译运行:

libs2: ADD

libs2: ADD

mutil_ADD2(1,2,3) = 6

libs2: MINUS

libs2: MINUS

mutil_MINUS2(1,2,3) = -4

结果完全正确!!!

接着,注释掉调用myHandler2.cpp的代码后,编译运行,

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

结果还是完全正确!!!

为啥,单独编译就能得到正确结果,一起调用就不行呢???

猜测可能和libs1/libmyAPI.solibs2/libmyAPI.so文件名重复有关,接下来再进行第2种情况的实验。

<实验7>

不修改任何代码,仅修改Makefile

step4: 

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI.o libs1/myAPI.cpp

 

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1.o myHandler1.cpp

 

g++ libs2/myAPI.o -shared -o libs2/libmyAPI2.so

g++ libs1/myAPI.o -shared -o libs1/libmyAPI1.so

 

g++ libs2/myHandler2.o libs2/libmyAPI2.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1.o libs1/libmyAPI1.so -shared -o libs1/libmyHandler1.so

 

g++ main4.cpp libs1/libmyHandler1.so libs2/libmyHandler2.so -o main6

编译时让.so文件名不同,ldd main6查看main6关系列表:

...

libs1/libmyHandler1.so (0x00007f61c7837000)

libs2/libmyHandler2.so (0x00007f61c7635000)

libs1/libmyAPI1.so (0x00007f61c6ce7000)

libs2/libmyAPI2.so (0x00007f61c6ae5000)

...

依然很有道理的样子,然后编译,运行

libs1: ADD

libs1: ADD

mutil_ADD2(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS2(1,2,3) = -4

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

结果还是不对!!!

修改.so文件名行不通,那么修改.cpp.h文件名呢?于是有情况3

<实验8>

目录结构如下:

libs2/

   myAPI.cpp

   myAPI.h

libs1/

   myAPI1.cpp

   myAPI1.h

myHandler1_1.cpp

myHandler1_1.h

myHandler2.cpp

myHandler2.h

main5.cpp

其中libs2/myAPI.cpp与libs1/myAPI1.cpp中都定义有命名为的ADD()MINUS()函数(参数列表,返回值,函数名均相同,区别是函数打印的信息不同),而myHandler2.cppmyHandler1_1.cpp则分别调用llibs2/myAPI.cpp与libs1/myAPI1.cpp中的同名函数。main4.cpp中又调用myHandler.cppmyHandler1_1.cpp中定义的函数。

 

Makefile

step4: 

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

 

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

 

g++ libs2/myAPI.o -shared -o libs2/libmyAPI2.so

g++ libs1/myAPI1.o -shared -o libs1/libmyAPI1.so

 

g++ libs2/myHandler2.o libs2/libmyAPI2.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1_1.o libs1/libmyAPI1.so -shared -o libs1/libmyHandler1_1.so

 

g++ main5.cpp libs1/libmyHandler1_1.so libs2/libmyHandler2.so -o main7.out

编译,运行,结果还是不对,好吧,

看来问题就是,函数重名!!!

<实验9>

最后,修改Makefile,企图将所有代码均打包给main6

step4: 

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

 

g++ main4.cpp libs2/myAPI.o libs1/myAPI1.o libs2/myHandler2.o libs1/myHandler1_1.o -o main7.out

结果,编译报错:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

g++ main4.cpp libs2/myAPI.o libs1/myAPI1.o libs2/myHandler2.o libs1/myHandler1_1.o -o main7

libs1/myAPI1.o:在函数‘ADD(int, int)’中:

myAPI1.cpp:(.text+0x0): `ADD(int, int)‘被多次定义

libs2/myAPI.o:myAPI.cpp:(.text+0x0):第一次在此定义

libs1/myAPI1.o:在函数‘MINUS(int, int)’中:

myAPI1.cpp:(.text+0x43): `MINUS(int, int)‘被多次定义

libs2/myAPI.o:myAPI.cpp:(.text+0x43):第一次在此定义

collect2: error: ld returned 1 exit status

Makefile:2: recipe for target ‘step4‘ failed

make: *** [step4] Error 1

 

分析:果然,函数多次被定义,是不行的!!!即使c++支持重载,但是也要求参数列表不能相同,事实证明在同一个程序中不能同时引用相同库的不同版本,主要就是因为函数重复定义

采用dlopendlsymdlclose加载动态链接库

...便。本文先从使用上进行总结,涉及到基本的操作方法,关于动态链接库的本质及如何加载进来,需要进一步学习,后续继续补充。如何将程序设计为插件形式,挖掘出主题和业务之间的关系,需要进一步去学习 查看详情

android分析native库的加载过程及x86系统运行arm库的原理

本文主要讲述Android加载动态链接库的过程,为了分析工作中遇到的一个问题x86的系统是如何运行arm的动态链接库的。参考博客:https://pqpo.me/2017/05/31/system-loadlibrary/深入理解System.loadLibraryhttps://www.jianshu.com/p/bf8b4a90f825Android... 查看详情

静态链接库与动态链接库的区别及动态库的创建(转)

一、引言通常情况下,对函数库的链接是放在编译时期(compiletime)完成的。所有相关的对象文件(objectfile)与牵涉到的函数库(library)被链接合成一个可执行文件(executablefile)。程序在运行时,与函数库再无瓜葛,因为所有... 查看详情

静态链接库的动态 DLL

】静态链接库的动态DLL【英文标题】:DynamicDLLwhichstaticallylinksalibrary【发布时间】:2014-09-2322:49:38【问题描述】:我正在编写一个依赖于ODBC的共享库。我想静态链接ODBC库,以便我的库的用户不需要安装ODBC库。但是,当我的dll被... 查看详情

第1章linux系统编程入门:动态链接库的创建和使用(代码片段)

文章目录动态库的制作动态库的基本工作原理如何使用动态链接库?法一:添加环境变量法二:修改etc/ld.so.cache文件列表动态库的制作一、命名规则Linux:libxxx.solib:前缀(固定)xxx:库的名字,自己起.so:后缀... 查看详情

关于动态库的制作介绍

今天简单的介绍下动态的制作,主要介绍windows平台下借助vs进行动态库制作和使用,以及linux下动态库的制作和使用,闲话少说下面开始吧                             在windows环境下进行制作 ... 查看详情

静态和动态链接库

...ies【发布时间】:2013-08-2311:53:43【问题描述】:我有一些关于链接库的问题。链接器如何决定我要链接的库是静态链接还是动态链接?是由文件扩展名(.a/.so)决定的吗?是否可以动态链接.a库?是否可以在没有源的情况下将.a... 查看详情

windows编程进程的创建销毁和分析

...indows程序设计:进程进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,在Windows编程环境下,主要由两大元素组成:?一个是操作系统用来管理进程的内核对象。操作系统使用内核对象来存放关于进程的核心... 查看详情

windows编程进程的创建销毁和分析

...indows程序设计:进程进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,在Windows编程环境下,主要由两大元素组成:?一个是操作系统用来管理进程的内核对象。操作系统使用内核对象来存放关于进程的核心... 查看详情

动态链接库的加载

...载入内存,软件打开速度快,用户体验好。  显式加载动态链接库时, 查看详情

ios底层探索之dyld(下):动态链接器流程源码分析(代码片段)

1.回顾在上一篇博文中介绍了动态库和静态库的区别,对dyld动态链接器做了初步的探索分析,本篇博文就进一步的对dyld的源码进行分析。2.MachO在上篇文章中,已经找到了dyld的入口了,但是在分析源码之前,... 查看详情

动静态链接库(代码片段)

...1、静态库的优缺点2、静态库的打包3、静态库的使用三、动态库1、动态库的优缺点1、动态库的打包2、动态库的使用四、库的意义动静态链接库一、什么是库将多个目标文件(.o)打包成一个单独的文件,这样的文件被称为库。... 查看详情

delphi动态链接库的动态和静态调用(仔细读一下)

...tatic/35420330200952075114318/为了让人能快速的理解静态调用、动态调用,现在做一个函数封装在一个DLL中,然后在APPLICATIONform里面调用这个函数,这个函数处理两个数的和。用代码和图片说话:代码如下libraryProject1;{ImportantnoteaboutDLLm... 查看详情

qt5动态链接库的创建和使用

记录一下QT5动态链接库的创建和使用在文章的最后有完毕的代码供下载1.创建动态链接库先新建一个库项目选择chose进入下一下页面,类型选择共享库,输入一个名称:我输入的是sld再点击下一步到假设这里我们须要QtGui所以也勾... 查看详情

gcc编译使用动态链接库和静态链接库(代码片段)

1库的分类根据链接时期的不同,库又有静态库和动态库之分。静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行。有别于静态... 查看详情

ffi动态链接库的使用(代码片段)

...现归根结底是对ffi库的调用,什么是ffi库?说白一点:ffi动态库就是lua语言调用底层 查看详情

在动态链接库的 .cpp 中定义类的函数

】在动态链接库的.cpp中定义类的函数【英文标题】:Definingfunctionsofaclassin.cppofaDynamic-linkLibrary【发布时间】:2016-09-1022:07:39【问题描述】:最近我开始使用c++,我想知道。如果我正在处理DLL,我首先在标头中定义所有类及其函数... 查看详情

动态库的链接和链接选项-l,-rpath-link,-rpath

https://my.oschina.net/shelllife/blog/115958链接动态库如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置。linux的可执行程序在执行的时候默认是先搜索/lib和/usr/lib这两个目录,然后按照/etc/ld.so.conf里面的配置... 查看详情