精华内容
下载资源
问答
  • 建立基于对话框的两个应用程序其界面,再建立一个动态连接库。
  • 本文摘抄于程序员的自我修养-链接装载与库7.1节,这段写的很好,直接拿过来来收藏 http://www.wq3028.top/technology/compile/20180727124/ 静态链接使得不同的程序开发者和部门能够相对独立地开发和测试自己的...

    本文摘抄于程序员的自我修养-链接装载与库7.1节,这段写的很好,直接拿过来来收藏
    http://www.wq3028.top/technology/compile/20180727124/

    静态链接使得不同的程序开发者和部门能够相对独立地开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先限制程序的规模也随之扩大。但是慢慢地静态链接的诸多缺点也逐步暴露出来,比如浪费内存和磁盘空间、模块更新困难等问题,使得人们不得不寻找一种更好的方式来组织程序的模块。

    内存和磁盘空间
    静态链接这种方法的确很简单,原理上很容易理解,实践上很难实现,在操作系统和硬件不发达的早期,绝大部分系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快就暴露出来了,那就是静态连接的方式对于计算机内存和磁盘的空间浪费非常严重。特别是多进程操作系统情况下,静态链接极大地浪费了内存空间,想象一下每个程序内部除了都保留着printf()函数、scanf()函数、strlen()等这样的公用库函数,还有数量相当可观的其他库函数及它们所需要的辅助数据结构。在现在的Linux系统中,一个普通程序会使用到的C语言静态库至少在1 MB以上,那么,如果我们的机器中运行着100个这样的程序,就要浪费近100 MB的内存;如果磁盘中有2 000个这样的程序,就要浪费近2 GB的磁盘空间,很多Linux的机器中,/usr/bin下就有数千个可执行文件。

    比如图7-1所示的Program1和Program2分别包含Program1.o和Program2.o两个模块,并且它们还共用Lib.o这两模块。在静态连接的情况下,因为Program1和Program2都用到了Lib.o这个模块,所以它们同时在链接输出的可执行文件Program1和Program2有两个副本。当我们同时运行Program1和Program2时,Lib.o在磁盘中和内存中都有两份副本。当系统中存在大量的类似于Lib.o的被多个程序共享的目标文件时,其中很大一部分空间就被浪费了。在静态链接中,C语言静态库是很典型的浪费空间的例子,还有其他数以千计的库如果都需要静态链接,那么空间浪费无法想象。

    这里写图片描述
    图7-1 静态链接时文件在内存中的副本

    程序开发和发布
    空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多麻烦。比如程序Program1所使用的Lib.o是由一个第三方厂商提供的,当该厂商更新了Lib.o的时候(比如修正了lib.o里面包含的一个Bug),那么Program1的厂商就需要拿到最新版的Lib.o,然后将其与Program1.o链接后,将新的Program1整个发布给用户。这样做的缺点很明显,即一旦程序中有任何模块更新,整个程序就要重新链接、发布给用户。比如一个程序有20个模块,每个模块1 MB,那么每次更新任何一个模块,用户就得重新获取这个20 MB的程序。如果程序都使用静态链接,那么通过网络来更新程序将会非常不便,因为一旦程序任何位置的一个小改动,都会导致整个程序重新下载。

    动态链接
    要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想。

    还是以Program1和Program2为例,假设我们保留Program1.o、Program2.o和Lib.o三个目标文件。当我们要运行Program1这个程序时,系统首先加载Program1.o,当系统发现Program1.o中用到了Lib.o,即Program1.o依赖于Lib.o,那么系统接着加载Lib.o,如果Program1.o或Lib.o还依赖于其他目标文件,系统会按照这种方法将它们全部加载至内存。所有需要的目标文件加载完毕之后,如果依赖关系满足,即所有依赖的目标文件都存在于磁盘,系统开始进行链接工作。这个链接工作的原理与静态链接非常相似,包括符号解析、地址重定位等,我们在前面已经很详细地介绍过了。完成这些步骤之后,系统开始把控制权交给Program1.o的程序入口处,程序开始运行。这时如果我们需要运行Program2,那么系统只需要加载Program2.o,而不需要重新加载Lib.o,因为内存中已经存在了一份Lib.o的副本(见图7-2),系统要做的只是将Program2.o和Lib.o链接起来。很明显,上面的这种做法解决了共享的目标文件多个副本浪费磁盘和内存空间的问题,可以看到,磁盘和内存中只存在一份Lib.o,而不是两份。另外在内存中共享一个目标文件

    这里写图片描述

    图7-2 动态链接时文件在内存中的副本

    模块的好处不仅仅是节省内存,它还可以减少物理页面的换入换出,也可以增加CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上。上面的动态链接方案也可以使程序的升级变得更加容易,当我们要升级程序库或程序共享的某个模块时,理论上只要简单地将旧的目标文件覆盖掉,而无须将所有的程序再重新链接一遍。当程序下一次运行的时候,新版本的目标文件会被自动装载到内存并且链接起来,程序就完成了升级的目标。当一个程序产品的规模很大的时候,往往会分割成多个子系统及多个模块,每个模块都由独立的小组开发,甚至会使用不同的编程语言。动态链接的方式使得开发过程中各个模块更加独立,耦合度更小,便于不同的开发者和开发组织之间独立进行开发和测试。

    程序可扩展性和兼容性
    动态链接还有一个特点就是程序在运行时可以动态地选择加载各种程序模块,这个优点就是后来被人们用来制作程序的插件(Plug-in)。比如某个公司开发完成了某个产品,它按照一定的规则制定好程序的接口,其他公司或开发者可以按照这种接口来编写符合要求的动态链接文件。该产品程序可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展。动态链接还可以加强程序的兼容性。一个程序在不同的平台运行时可以动态地链接到由操作系统提供的动态链接库,这些动态链接库相当于在程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间依赖的差异性。比如操作系统A和操作系统B对于printf()的实现制不同,如果我们的程序是静态链接的,那么程序需要分别链接成能够在A运行和在B运行的两个版本并且分开发布;但是如果是动态链接,只要操作系统A和操作系统B都能提供一个动态链接库包含printf(),并且这个printf()使用相同的接口,那么程序只需要有一个版本,就可以在两个操作系统上运行,动态地选择相应的printf()的实现版本。当然这只是理论上的可能性,实际上还存在不少问题,我们会在后面继续探讨关于动态链接模块之间兼容性的问题。

    从上面的描述来看,动态链接是不是一种“万能膏药”,包治百病呢?很遗憾,动态链接也有诸多的问题及令人烦恼和费解的地方。很常见的一个问题是,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致了原有的程序无法运行。这个问题在早期的Windows版本中尤为严重,因为它们缺少一种有效的共享库版本管理机制,使得用户经常出现新程序安装完之后,其他某个程序无法正常工作的现象,这个问题也经常被称为“DLLHell”。

    动态链接的基本实现
    动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个个单独的可执行文件。那么我们能不能按照前面例子中所描述的那样,直接使用目标文件进行动态链接呢?这个问题的答案是:理论上是可行的,但实际上动态链接的实现方案与直接使用目标文件稍有差别。我们将在后面分析目标文件和动态链接文件的区别。动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也会有一些微妙的变化。目前主流的操作系统几乎都支持动态链接这种方式,在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件;而在Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),它们通常就是我们平时很常见的以“.dll”为扩展名的文件。

    从本质上讲,普通可执行程序和动态链接库中都包含指令和数据,这一点没有区别。在使用动态链接库的情况下,程序本身被分为了程序主要模块(Program1)和动态链接库(Lib.so),但实际上它们都可以看作是整个程序的一个模块,所以当我们提到程序模块时可以指程序主模块也可以指动态链接库。在Linux中,常用的C语言库的运行库glibc,它的动态链接形式的版本保存在“/lib”目录下,文件名叫做“libc.so”。整个系统只保留一份C语言库的动态链接文件“libc.so”,而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序被装载的时候,系统的动态链接器会将程序所需要的所有动态链接库(最基本的就是libc.so)装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。程序与libc.so之间真正的链接工作是由动态链接器完成的,而不是由我们前面看到过的静态链接器ld完成的。也就是说,动态链接是把链接这个过程从本来的程序装载前被推迟到了装载的时候。可能有人会问,这样的做法的确很灵活,但是程序每次被装载时都要进行重新进行链接,是不是很慢?的确,动态链接会导致程序在性能的一些损失,但是对动态链接的链接过程可以进行优化,比如我们后面要介绍的延迟绑定(Lazy Binding)等方法,可以使得动态链接的性能损失尽可能地减小。据估算,动态链接与静态链接相比,性能损失大约在5%以下。当然经过实践的证明,这点性能损失用来换取程序在空间上的节省和程序构建和升级时的灵活性,是相当值得的。

    展开全文
  • 深入浅出静态链接和动态链接

    万次阅读 多人点赞 2018-05-06 09:24:48
    作为一名C/C++程序员,对于编译链接的过程要了然于胸。首先大概介绍一下,编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;...

            作为一名C/C++程序员,对于编译链接的过程要了然于胸。首先大概介绍一下,编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;然后进行编译,这个过程主要是进行词法分析、语法分析和语义分析等,生成*.s的汇编文件;最后进行汇编,这个过程比较简单,就是将对应的汇编指令翻译成机器指令,生成可重定位的二进制目标文件。以上就是编译的过程,下面主要介绍两种链接方式--静态链接和动态链接。

            静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时,下面来详细介绍这两种链接方式。

    一、静态链接

    1.为什么要进行静态链接

            在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接

    2.静态链接的原理

             由很多目标文件进行链接形成的是静态库,反之静态库也可以简单地看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件,如下图,使用ar命令的-a参数查看静态库的组成:


            这里的*.o目标文件在我前面的博客《从编写源代码到程序在内存中运行的全过程解析》中已经讲的很清楚了,不清楚的可以看一下。

            以下面这个图来简单说明一下从静态链接到可执行文件的过程,根据在源文件中包含的头文件和程序中使用到的库函数,如stdio.h中定义的printf()函数,在libc.a中找到目标文件printf.o(这里暂且不考虑printf()函数的依赖关系),然后将这个目标文件和我们hello.o这个文件进行链接形成我们的可执行文件。


            这里有一个小问题,就是从上面的图中可以看到静态运行库里面的一个目标文件只包含一个函数,如libc.a里面的printf.o只有printf()函数,strlen.o里面只有strlen()函数。

            我们知道,链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那个目标文件链接进来,如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中。由于运行库有成百上千个函数,数量非常庞大,每个函数独立地放在一个目标文件中可以尽量减少空间的浪费,那些没有被用到的目标文件就不要链接到最终的输出文件中。

    3.静态链接的优缺点

            静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

    问题:

    二、动态链接
    1.为什么会出现动态链接

            动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。下面介绍一下如何解决这两个问题。

    2.动态链接的原理

            动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:

            假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。

    3.动态链接的优缺点

            动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

            据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。

    4.动态链接地址是如何重定位的呢?

            前面我们讲过静态链接时地址的重定位,那我们现在就在想动态链接的地址又是如何重定位的呢?虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。

    展开全文
  • DOBOT的动态链接库是越疆科技下的产品dobot机械臂的动态链接库,用于二次开发DOBOT机械臂,主要为不满足于DOBOTSTuDIO的开发者准备。学生党研究进行二次开发。
  • 动态链接详解

    千次阅读 2015-08-21 22:46:21
    动态链接  动态链接的诞生:  动态链接产生最主要的原因就是静态链接空间浪费过于巨大,更重要的是现阶段各种软件都是模块化开发,不同模块都是由不同厂商开发的,一旦一个模块发生改变,整个软件就需要重新编译...

                                                                                    动态链接

     动态链接的诞生:

      动态链接产生最主要的原因就是静态链接空间浪费过于巨大,更重要的是现阶段各种软件都是模块化开发,不同模块都是由不同厂商开发的,一旦一个模块发生改变,整个软件就需要重新编译(静态链接的情况下)。

     

    动态链接主要思想:

         把链接这个过程推迟到了运行时再运行,这就是动态链接(Dynamic Linking)的基本思想。

     

    动态链接的好处:

         1.动态链接将共享对象放置在内存中,不仅仅节省内存,它还可以减少物理页面的换进换出,也可以提高CPU缓存的命中率,因为不同进程间的数据与指令都集中在同一个共享模块上。

         2.当一个软件模块发生改变的时,只需要覆盖需要更新的文件,等程序下一次运行时自动链接更新那么,就算是跟新完成了。

         3.增加程序的可扩展性和兼容性,它可以在运行时动态的加载各种程序模块,就是后来的插件(plug-in).

     

    动态链接的基本实现:

         动态链接的基本思想就是把程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完成的程序,而不是像静态链接把所有的模块都链接成一个单独的可执行文件。再Linux下ELF动态链接被称为动态共享库(DSO)。动态共享文件的制作参照之前的一片博文,后边将会对这个制作参数——FPIC 进行详细的介绍。

     

    动态链接的数据结构:

           使用readelf 工具可以详细的查看动态链接文件(.so)文件,发现动态链接模块的装载地址是从0x0000000000开始的,但是这个地址是无效的地址,所以共享对象的最终装载地址在编译时是不确定的,而是在装载时根据当前的地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享对象。

     

    地址无关代码(fPIC)

           首先看看固定装载地址的困扰,如果都是装载地址都是固定好的,那么它的地址就是在编译时候确定的,那么当这个程序再需要增加模块或者程序段的时候,地址就会发生冲突,过去有人提出了改进这种问题的方法,然而这并不是一种好方法,叫做静态共享库,这种方法将分配的指空间的任务交给了操作系统,操作系统在适当的时候给需要使用的模块预留地址空间,这样导致了很多其它的问题产生,这样就需要一直保持全局变量的地址不变,升级了软件后全局变量的地址依然不能变化,如果增加代码,变量等东西,会导致地址不够用。

    简而言之,共享对象在编译时不能假设自己在进程虚拟地址空间中的为位置。

             重定位是一个解决问题的好方法,以重定位为基础开发了地址无关代码的技术,就是编译生成共享对象的那个参数,-fPIC 。

          首先我们分析下模块中各种类型的地址引用方式。我们把共享对象模块中的地址引用按照是否需要跨模块分成两类:模块内部引用,模块外部引用。按照不同的引用方式又可以分为指令引用和数据访问。

    1.第一种是模块内部的函数调用,调转等。

    2.第二种是模块内部的数据访问,比如模块内部的全局变量,静态变量等。

    3.第三种是模块外部的函数调用,调转等。

    4.第四种是模块外部的数据访问,比如其它模块中定义的全局变量。

     

    下面详细描述一下这四种访问方式:

    第一种类型:

           第一种类型是最简单的就是模块内部的调用或调转,因为被调函数与调用函数都在函数内部,这在编译链接时他们的相对位置时确定的,这是相对地址调用,或者是基于寄存器的相对调用,所以这种调用是不需要重定位的。

    Call 指令地址:对于32位来说,第一个字节是指令的地址码,后边的四个字节是目的地址相对于当前指令的下一条指令的偏移。

    第二种类型:

           第二种类型是数据模块内部的数据访问,很明显,指令中不能直接包含数据的绝对地址,那么唯一的办法就是相对寻址。但是如何获得当前指令的地址是一个问题,因为现代操作系统,数据的相对寻址往往没有相对于当前指令的寻址方式,所以ELF 采用了一种巧妙的方法来获取当前指令地址(PC)。在汇编层面会调用一个_i686.get_pc_thunk.cx  的函数,作用是把返回地址的值放在ecx寄存器就是把call 的下一条指令地址放到ecx寄存器中。使用这个这种方法可以确定模块内部的变量以及函数的入口。

     

    第三种类型:

           模块间的数据访问要更加复杂一些,首先还是需要地址无关的属性,毕竟是在多个模块中调用函数,变量。所以最好只让它拥有一个父本,但是多个部分都能引用的上,所以ELF 在数据段里面建立一个指向这些变量的指针数组(*),也被称之为全局偏移表(GlobalOffset Table, GOT),当代码需要引用全局变量的时候通过GOT中相应的项间接引用。

    当程序需要访问变量的时候,首先通过第二种方式得到PC的值,然后根据偏移量(偏移量在编译链接时就是已知的)找到GOT 段,在GOT中偏移就可以得到变量的地址,每一个变量对应一个一个地址(4字节)至于对应的顺序是由编译器决定的。

     

    第四种类型:

           模块间的函数调转与调用,和第三种类型相似,GOT段同样保存着各个函数的地址,同样运用第二种方法首先找到PC 地址,然后加上一个偏移得到函数地址在GOT中的偏移,然后得到一个间接的调用。

     另:

           -fpic与 -fPIC ,这两个参数都是gcc 产生地址无关代码,唯一的区别是“-fPIC”产生的代码要大,而“-fpic”产生的代码相对较小,而且较快,但是fPIC在某些硬件平台上会有一些限制,比如全局符号的数量或者代码长度等而是“-fPIC”则没有这样的限制,所以为了方便都使用“-fPIC”参数来产生地址无关代码。

    Ps: 区分一个共享对象,是否为PIC:

           使用工具readelf 可以辨识。

        Readelf -d   foo.so | grep TEXTREL

         如果有输出那就不是PIC 的,否则就是PIC 的。PIC 的DSO 是不会包含任何代码段重定位表的,TEXTREL 表示代码段重定位表地址。

    地址无关代码技术还可以使用在可执行文件上,一个以地址无关可执行方式产生参数位fpie.

    延迟绑定(PLT):

     动态链接虽然节省了内存,但是牺牲了一些性能,所以为了改进性能就使用了新的技术延迟绑定技术(PLT),基本思想是:当函数第一次被使用时才进行绑定.

    基本原理:当我们调用外部函数时,一般是通过访问GOT段查找相应的项,进行调转。PLT(ProcedureLinkage Table)为了实现延时绑定,在中间又添加了一层调转。调用函数并不通过GOT调转,而是通过一个叫PLT的项进行调转,每个外部函数在PLT中都有一个对应的项称之为test@plt,假设由一个外部函数叫test( );那么在就会有这几行代码:

    test@plt:

    jmp*(test@plt)

    push n

    push moduleID

    jump dl_runtime_resolve

     

     

    test@plt:表示GOT中保存的test中的项。

    这个项的第一条指令jmp指令是一条通过GOT之间调转的指令。

    如果连接器在初始化的时候已经初始化了这个项,那么目标函数的地址已经填入该项,这就是我们期望的,直接调用就好还省的我们去GOT中找了,但是为了实现延时绑定,连接器并没有将函数地址放进去,而是将第二条指令push n 的地址加入到第一条指令中,这个步骤不需要查找任何一个符号表,代价很低,也很值的。

     

    第二条指令将一个数字N(test 这个符号在rel.plt中的下标)压入栈中。

    第三条指令将模块ID 压入堆栈。

    第四条指令:调转到dl_runtime_resolve

    这就是实现lookup(module,fun)函数的调用。

     

    先将决议符号下标压入栈中,再将模块ID压入堆栈,然后调用连接器的dl_runtime_resolve()函数完成符号解析和重定位工作。这个函数在一系列的操作之后将test()函数的真正地址填入test@GOT,当中。此时GOT中才有了函数真正的函数,解也就解答了当时的一个问题,既然连接器可以把函数的地址填入GOT,为什么还有延时绑定,其实只有经历这些过程后函数地址才能被绑定。这就是PLT方式的基本实现,实际延时绑定的工作要比这样复杂的多。

     

    GOT段的一些 信息:

      ELF 将GOT 段分成了两个部分,其中一个叫  . got  段,另一个为.got.plt .其中got 段用来保存全局变量引用的地址,got.plt 用来保存函数引用的地址,对于外部函数的引用全部被分离出来放到了got.plt 段表中。另外还有一个特殊的地方,是它的前三项是有特殊意义的:

    第一项保存的是.dynamic 的地址,这个段描述了本模块动态链接相关信息。

    第二项保存的是本模块的ID。

    第三项保存的是_dl_runtime_reslove()函数的地址。

     

    其中二三项是由动态链接器在装载共享模块的时候将负责将他们初始化。

     

    动态链接相关结构:

           动态链接器:在interp 段保存了一些字符串,这些字符串就是动态链接器的地址。

    其中动态链接结构中还包含dynamic段,动态符号表,动态链接重定位表,辅助信息组等信息,(初始化堆栈时初始化的一些信息,包括程序头,大小,类型,入口地址等)。

     

    动态连接的步骤和实现:

           基本分成三步:先是启动动态链接器本身。然后装载所有需要的共享对象,最后是重定为位和初始化。

    首先是启动动态连接器本身,这就是动态连接器自举:

           动态链接器本身就是一个共享对象,它自己需要启动自己,别人启动靠他,但是他启动靠谁呢?,答案是只能靠他自己,动态链接器不能依赖任何其它共享对象,其次它本身的全局变量,局部变量的重定位工作都由他自己完成,完成这些要求的代码非常精细与精巧,这种代码往往被为自举。动态连接器的入口地址就是字句代码的入口,当操作系统将控制权给动态连接器后,自举代码开始执行,首先访问自己的GOT段。然后找到dynamic段, 找到自身的符号表,重定位表等从而得到动态链接器本身的重定为入口,先将他们重定位。从这一步开始时用自己的全局变量和静态变量。实际上在动态连接器在自举的过程中,除了不可以使用全局变量和静态变量之外甚至不能调用函数,时用PIC 模式编译的共享对象,对于模块内部函数调用和外部函数调用的方式是一样的,时用GOT/PLT方式,所以在GOT/PLT没有被重定位之前,自举代码不可以使用任何全局变量。

    装载共享对象:

           完成基本的自举以后,动态连接器将可执行文件和链接器本身的符号都合并到一个符号表当中,我们可以称之为全局变量。如果将这些装载的过程看作是一个图的遍历过程,使用的算法一般都是广度优先。

             当一个符号表需要被加入全局符号表时,如果相同的符号已经存在,则后加入的符号被忽略。

    重定位和初始化:

           首先进行GOT/PLT重点定位完成后,如果某个段有init 段,那么动态链接器就会执行init段中的代码,用以实现共享对象的初始化过程,比如C++的全局变量和静态对象,就需要通过init 来初始化,相应的可能还有 finit 段,当进程执行完成后就会执行实现C++全局对象析构之类的操作。

     

    Q&A:

    动态链接器本身是静态还是动态?

    其实动态链接器本身是静态的。

      

    动态链接器本身必须是IPC的吗?

    是否是IPC 的对于动态链接器并不影响,但是如果是IPC 的就会更加的简单,实际上动态链接器就是PIC 的。

    动态链接器可以被当成可执行文件,那么它的装载地址是多少?

    动态链接器和普通的文件都是一样的,加载地址都是0x0000000000,这个地址是一个无效的地址,作为一个共享库,内核在装载后会为其选择一个合适的装载地址。

    运行时加载:

     

    其实还有一种更加灵活的加载方式,叫做显示运行时链接,有时候也叫做运行时加载,以共有四个函数。实际上是以库的形式调用的,基本是先打开库然后根据需要加载的函数符号加载相应的函数。所以一共有这四个函数。

    Dlopen( ) :用来打开一个动态库,并将其加载到进程的在地址空间

    dlsym( ):这是加载的核心,我们可以通过这个函数找到所需要的符号

    dlerror( ):错误处理,都可以用来判断上一次调用是否成功。

    dlclose( ):将一个已经加载的模块卸载。系统会维持一个加载引用技术器,每次使用加载模块时,相应计数器加一,当使用这个函数卸载一个模块时数字就减一。

    展开全文
  • VS2019 使用 C/C++ 动态链接库 并 进行调用

    千次阅读 多人点赞 2020-03-03 23:07:40
    1. VS中生成动态链接库的三种方式(导出函数) 开发环境:VS2019 创建动态链接库项目 新建项目,搜索DLL ,选择 “具有导出项的(DLL)动态链接库 ”, 输入项目名称,选择项目所在目录,然后点击 创建 ...

     

    vs2019生成dll并调用的实现示例:https://www.jb51.net/article/179759.htm

     

    vs2017创建linux c++程序:https://blog.csdn.net/qingyulove/article/details/86659726

    vs2019编写Linux c/c++项目:https://blog.csdn.net/mmmsss987/article/details/103518996

    Visual Studio 2019 基于Linux平台的C++开发:https://www.cnblogs.com/sgawscd/p/12916544.html

     

     

    1. VS 中生成动态链接库的三种方式(导出函数)

     

    开发环境:VS2019

     

    创建动态链接库项目

    新建项目,搜索 DLL ,选择 “ 具有导出项的(DLL)动态链接库 ”,

    输入项目名称,选择项目所在目录,然后点击 创建

    生成如下图所示的文件结构:

     

     

    导出动态链接库

     

    方法一 :声明导出

     

    1、以 C++ 的方式声明导出。

    在 CPP_DLL.cpp 文件中添加下面的函数,在函数声明前添加 _declspec(dllexport) 关键字

    // C++ 的方式声明导出
    _declspec(dllexport) int addFunc(int a, int b);
    int addFunc(int a, int b)
    {
        return a + b;
    }

    示例截图:

     

    2、以 C 的方式声明导出。

    在 CPP_DLL.cpp 文件中添加下面的函数,在函数声明前添加 extern "C"  _declspec(dllexport) 关键字

    // C 的方式声明导出
    extern "C"
    {
    	_declspec(dllexport) int subFunc(int a, int b);
    	int subFunc(int a, int b)
    	{
    		return a + b;
    	}
    
    }

    示例截图:

    然后点击 “ 生成解决方案 ” ,就可以在工程目录的 debug 目录或者 release 目录下(这取决你生成的是debug版本还是release版本)生成了动态链接库的相关文件。第三方调用时关键的文件为 .lib文件.dll文件 以及工程目录下的 .h头文件

     

     

    方法二: 模块定义文件导出

    在项目中定义CPP_DLL.def 文件,该文件为模块导出文件

    在 CPP_DLL.cpp 文件中添加下面的函数

    LIBRARY
    EXPORTS
      mulFunc
      divFunc

    示例截图:

     

    如果是 VS 平台,必须要在 连接器 中添加 .def 文件

    然后点击 “ 生成解决方案 ” ,就可以在工程目录的 debug 目录或者 release 目录下(这取决你生成的是debug版本还是release版本)生成了动态链接库的相关文件。第三方调用时关键的文件为 .lib文件.dll文件 以及工程目录下的 .h头文件

    对应 的 DLL 和 lib 文件

     

     

     

    2. 调用前面开发的动态链接库

     

    1. 新建一个C/C++项目(test)

     

     

    2. 将第三方库的 .h文件、.lib文件、.dll文件 复制进工程项目中

            .dll文件是程序运行需要载入的动态链接库,VS中调试时可以通过 项目->属性->调试->环境 栏目添加.dll文件的 path 而成功调试,但在独立运行.exe程序是须将.dll文件放到同一目录下。

            因此建议直接将 .dll文件 放入debug目录下或release目录下.h头文件.lib库文件 可以随意放置,只要是能够通过路径找到即可,为了方便管理,建议建立文件夹,放置在项目目录下。

     

     

    3. 在项目中调用第三方库

     

    有三种方法可以调用第三方库。

     

    (1)直接在代码前添加引用

    因为直接引用 CPPDLL.h 头文件,所以需要把 动态链接库的 函数声明放在头文件中。

    修改 CPP_DLL 的 .h 文件 和 cpp 文件,把 函数 声明放在 .h 中,函数定义放在 cpp 文件中。

    CPP_DLL.h

    // C++ 的方式声明导出
    _declspec(dllexport) int addFunc(int a, int b);
    
    // C 的方式声明导出
    extern "C"
    {
    	_declspec(dllexport) int subFunc(int a, int b);
    }
    
    int mulFunc(int a, int b);
    double divFunc(double a, double b);

    CPP_DLL.cpp

    int addFunc(int a, int b)
    {
        return a + b;
    }
    
    int subFunc(int a, int b)
    {
    	return a - b;
    }
    
    
    // def 文件形式
    int mulFunc(int a, int b)
    {
    	return a * b;
    }
    
    double divFunc(double a, double b)
    {
    	return a / b;
    }

    重新生成 DLL 文件,并将 DLL  、lib 、.h 文件拷贝到对应目录

     

    C++ 测试代码:

    #include <iostream>
    #include "./CPP_DLL.h"   //通过相对路径或绝对路径添加头文件
    #pragma comment (lib,"./CPPDLL.lib")  // 添加 lib 文件
    
    
    int main()
    {
        std::cout << "Hello World!\n";
        std::cout << addFunc(1, 2) << std::endl;
        std::cout << subFunc(3, 4) << std::endl;
        std::cout << mulFunc(5, 6) << std::endl;
        std::cout << divFunc(7, 8) << std::endl;
    }

    运行结果截图:

     

     

    (2)在解决方案管理面板中添加头文件和资源文件

    添加一个现有项头文件,在文件夹中找到第三方库的头文件( CPP_DLL.h ),添加进新建立的项目。
    添加一个现有项资源文件,在文件夹中找到第三方库的库文件( CPPDLL.lib ),添加进新建立的项目。

     

     

    (3)在 项目属性 -> 设置 中 添加 头文件库文件

    • 项目->属性->VC++目录->包含目录 中添加第三方库的 头文件
    • 库目录 下 添加 第三方库 的 库文件(.lib文件)
    • 项目->属性->链接器->输入->附加依赖项中输入 库文件名称

    在函数中调用第三方库中的函数

    #include <iostream>
    
    
    int main()
    {
        std::cout << "Hello World!\n";
        std::cout << addFunc(1, 2) << std::endl;
        std::cout << subFunc(3, 4) << std::endl;
        std::cout << mulFunc(5, 6) << std::endl;
        std::cout << divFunc(7, 8) << std::endl;
    }

     

     

    4. 直接在代码里 load 动态库dll文件即可

    这种方法不需要 include .h文件,不需要添加 lib库 和 lib库路径,

    • 引入 windows.h(必须)
    • 在 main 函数写下列语句调用 dll

            因为 C++ 声明 的 动态链接库会发生 Name Mangling,导致 编译后的函数名字会发生变化,所以需要使用 工具 查看 编译编译后的 动态链接库 对应的函数名。

            而 extern "C" 声明的 和 def 文件声明的,编译后的函数名不会发生变化,可以直接使用。

    VS2019 自带的工具 dumpbin.exe 可以查看编译后的 动态链接库对应的 函数名。

    打开命令行,输入命令 dumpbin -exports CPPDLL.dll

    结果截图:

    所以 addFunc 不能直接使用,只能用被 name Mangling 后的名字 ,这里  addFunc 编译后的名字是 ?addFunc@@YAHHH@Z

    示例程序代码:

    #include <iostream>
    #include <windows.h>
    
    
    // 加、减、乘 都是 int 类型
    typedef int(*lpFunc)(int a, int b); //后边为参数,前面为返回值
    
    // 除法 是 double 类型
    typedef double(*lpFuncD)(double a, double b); //后边为参数,前面为返回值
    
    
    int main()
    {
        std::cout << "Hello World!\n";
    
    	HMODULE hModule;
    	hModule = LoadLibrary(L"CPPDLL.dll"); //调用DLL	
    
    	lpFunc lpfunc = NULL;
    
    	// GetProcAddress为获取该函数的地址 
    	// "?addFunc@@YAHHH@Z" 这个就是 C++  Name Mangling后的 addFunc 的函数名
    	lpfunc = (lpFunc)GetProcAddress(hModule, "?addFunc@@YAHHH@Z");
        std::cout << lpfunc(1, 2) << std::endl;
    
    	/* 使用 C extern 和 def 文件定义的动态链接库,函数名不会发生变化 */
    	lpfunc = (lpFunc)GetProcAddress(hModule, "subFunc");
    	std::cout << lpfunc(3, 4) << std::endl;
    
    	lpfunc = (lpFunc)GetProcAddress(hModule, "mulFunc");
    	std::cout << lpfunc(5, 6) << std::endl;
    
    	lpFuncD lpfuncd = NULL;
    	lpfuncd = (lpFuncD)GetProcAddress(hModule, "divFunc");
    	std::cout << lpfuncd(7, 8) << std::endl;
    	
    	//释放
    	FreeLibrary(hModule);	
    }

    运行结果:

     

     

    方法 5:使用 lib 文件 和 dll 文件

    • CPPDLL.dll 文件放到 debug 目录下,
    • 然后在项目中引入 CPPDLL.lib 文件。 链接器 -> 输入 -> 附加依赖项 -> 编辑

    示例代码:

    #include <iostream>
    
    
    // addFunc 是使用 C++ 方式声明的,
    _declspec(dllimport) int addFunc(int a, int b);
    
    //subFunc 是 使用 extern "C" 声明的
    extern "C" _declspec(dllimport) int subFunc(int a, int b);
    
    // mulFunc 和 divFunc 是 使用 def 声明的
    _declspec(dllimport) int mulFunc(int a, int b);
    _declspec(dllimport) double divFunc(double a, double b);
    
    
    int main()
    {
        std::cout << "Hello World!\n";
    
        std::cout << addFunc(1, 2) << std::endl;
        std::cout << subFunc(3, 4) << std::endl;
        std::cout << mulFunc(5, 6) << std::endl;
        std::cout << divFunc(7, 8) << std::endl;
    
        return 0;
    }

    运行结果:

    还可以结合 第一种方法,使用  #pragma comment (lib,"./CPPDLL.lib")  //添加 lib 文件 

    这样就不用 手动设置 添加 lib 文件了

    #include <iostream>
    #pragma comment (lib,"./CPPDLL.lib")
    
    
    // addFunc 是使用 C++ 方式声明的,
    _declspec(dllimport) int addFunc(int a, int b);  // 声明 addFunc 函数
    
    //subFunc 是 使用 extern "C" 声明的
    extern "C" _declspec(dllimport) int subFunc(int a, int b);  // 声明 subFunc 函数
    
    // mulFunc 和 divFunc 是 使用 def 声明的
    _declspec(dllimport) int mulFunc(int a, int b);             // 声明 mulFunc 函数
    _declspec(dllimport) double divFunc(double a, double b);    // 声明 divFunc 函数
    
    
    int main()
    {
        std::cout << "Hello World!\n";
    
        std::cout << addFunc(1, 2) << std::endl;
        std::cout << subFunc(3, 4) << std::endl;
        std::cout << mulFunc(5, 6) << std::endl;
        std::cout << divFunc(7, 8) << std::endl;
    
        return 0;
    }

     

     

     

     

     

    展开全文
  • VS2019开发简单的C/C++动态链接库并进行调用

    万次阅读 多人点赞 2019-05-11 09:40:15
    小白提升:VS2019开发简单的C/C++动态链接库并解决方案中进行调用 一、 vs2019简单动态链接库的开发 1.VS2019新建立一个空项目(DLL1) VS有提供dll项目的模板,可是对于我来说反而搞不懂模板中的文档,于是建立...
  • 什么是链接? 对于初学C语言的朋友,可能对链接...静态、动态链接? 1、什么是静态链接? 静态链接是由链接器链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由...
  • 静态链接库和动态链接库的区别

    千次阅读 2018-04-23 20:45:30
    静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件。 VS2015平台上,创建一个静态库(.lib)项目方案,选择【创建项目/Win32/Win32控制台应用程序/静态库(.lib)】...
  • 栈帧 动态链接

    千次阅读 2019-09-22 21:08:25
    符号引用和直接引用运行时进行解析和链接的过程,叫动态链接。 一个方法调用另一个方法,或者一个类使用另一个类的成员变量时, 需要知道其名字 符号引用就相当于名字, 这些被调用者的名字就存放Java字节...
  • 动态链接的整个过程

    万次阅读 多人点赞 2017-08-29 20:57:37
    1.静态链接的缺点 (1)同一...动态链接的基本思想 动态就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基
  • 动态代理的好处是什么

    千次阅读 2018-06-09 12:59:48
    静态代理:了解设计模式中的代理模式的童鞋应该都知道如果想要为被代理类生成代理,需要让代理类和被代理类共同实现同一个接口,代理类中增加额外逻辑来实现代理模式,这种方式编译期间就已经确认了哪个类是代理...
  • Linux动态链接

    千次阅读 2018-05-16 11:03:57
    可以参考博主的这两篇博客Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程Linux加载启动可执行程序的过程(二)解释器完成动态链接我们可以知道,识别二进制映像以及文件映射到进程虚拟地址空间这个过程...
  • C++ 动态链接库和静态链接库

    千次阅读 多人点赞 2019-09-23 15:59:58
      今天对C++生成动态链接路和静态链接库非常感兴趣,必须搞定,否则都没有心情干其他事了。Let’s go~ 文章目录源程序编译链接生成文件格式预编译编译和优化编译优化生成目标文件链接什么是库?动态静态的区别...
  • 静态链接和动态链接及其区别

    千次阅读 2020-01-11 23:11:24
    并最终链接成可执行程序或者静态库或动态库,静态库一般都是指的由静态链接的目标文件集合,而动态库一般是动态链接目标生成的目标集合,那么静态链接和动态链接到底是什么 什么是静态链接 什么动态链接 区别 ...
  • springboot+mybatis多数据源+动态数据源配置(连接池),mysql数据库,代码实现了简单的读写分离,但是不建议这种操作,建议使用数据库中间件进行读写分离,例如使用mycat进行读写分离主从热备,使用该代码对于多个...
  • mysql.data.dll是C#操作MYSQL的动态链接库,是c#连接mysql必要插件,使c#语言更简洁的操作mysql数据库。
  • 链接器会将程序中使用到函数的代码从库文件中拷贝到应用程序中,一旦链接完成生成可执行文件之后,执行程序的时候就不需要静态库了。 特性: 由于每个使用静态库的应用程序都需要拷贝所用函数的代码,所以静态...
  • 静态链接和动态链接的区别

    千次阅读 2016-01-02 11:21:23
    静态链接把要调用的库函数直接链接到目标程序。...l 动态链接所调用的库函数代码并没有拷贝到程序的可执行文件中。它仅仅exe文件中加入了调用的函数所在文件模块(DLL)和调用函数文件中的位置等信
  • 使用gdb调试动态链接

    千次阅读 2019-05-10 08:59:04
    使用gdb进行调试之前,必须保证编译的可执行程序和想要调试的动态库编译包含了-g选项。这里还有一个坑,有时候我们虽然指定-g进行了编译,但是编译完成后又使用strip命令去除了调试信息,那么最终的程序和库也是...
  • “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与...
  • Linux-动态链接与静态链接对比(动态库和静态库)

    千次阅读 多人点赞 2017-12-14 17:52:46
    一、库的基础概念: windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。...按照库的使用方式又可分为动态库和静态库,不同平台下...
  • Kettle数据库动态连接

    千次阅读 2020-07-16 18:18:20
    KETTLE的动态数据库连接 kettle数据库连接使用变量 Kettle 数据库连接采用文本文件进行配置
  • 如果有CUDA,要使用GPU加速,要用OpenCV,要生成动态链接库,那么打开Makefile文件,修改下面的部分: # GPU=0 GPU = 1 # CUDNN=0 CUDNN = 1 CUDNN_HALF = 0 # OPENCV=0 OPENCV = 1 AVX = 0 OPENMP = 0 ...
  • 什么是库文件? 库文件是事先编译好的方法的合集。比如:我们提前写好一些数据公式的实现,将其打包成库文件,以后使用只需要库文件就可以,不需要重新编写。 库文件分两种:静态库和动态库(也叫共享库) Windows...
  • 一文弄懂Java和C中动态链接机制

    千次阅读 2019-01-31 03:08:59
    那么,为什么要使用动态链接呢?动态链接是为了解决静态链接的维护和资源利用问题而出现的。那么,什么是静态链接呢?静态链接是指将符号从库拷贝到目标产物中的一种链接方式。那么再进一步,链接又是什么意思?模块...
  • 1. 链接、静态链接和动态链接的概念 程序设计追求的是模块化,一个复杂软件由许多模块组成,通常将每个模块进行单独编译,然后将它们组装起来,组装的过程就是链接。链接处理的是把各个模块之间相互应用的部分都...
  • 我们使用 go help buildmode 可以看到 go 可以以多种方式进行构建,默认使用静态链接库. ➜ src go help buildmode The 'go build' and 'go install' commands take a -buildmode argument which indicates ...
  • 装入时动态链接是指:用户源程序编译后,得到一组目标模块,装入内存时,采用边装入,边链接的方式。即装入一个目标模块时,若发生一个外部模事件,将引起装入程序去找出相应的外部目标模块,并将他装入内存。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,131,647
精华内容 452,658
关键字:

动态链接是在什么进行的