精华内容
下载资源
问答
  • 老规矩,先抛出几个问题:为什么进行动态链接?如何进行动态链接什么是地址无关代码技术?什么是延迟绑定技术?如果程序运行过程中进行显式链接?为什么进行动态链接因为静态链接有缺点:浪费内存和磁盘空间...

    c5ec97ea6343e418e21ff39fb447b5e0.png

    在前面的文章中程序喵已经介绍过静态链接的原理,这篇文章我们来解密动态链接。

    老规矩,先抛出几个问题:

    • 为什么要进行动态链接?
    • 如何进行动态链接?
    • 什么是地址无关代码技术?
    • 什么是延迟绑定技术?
    • 如果在程序运行过程中进行显式链接?

    为什么要进行动态链接

    因为静态链接有缺点:

    1. 浪费内存和磁盘空间:如下图,

    51b9a9d706aaf8708133a1cfc198485d.png

    Program1和Program2分别包含Program1.o和Program2.o两个模块,他们都需要Lib.o模块。静态链接情况下,两个目标文件都用到Lib.o这个模块,所以它们同时在链接输出的可执行文件Program1和program2中有副本,同时运行时,Lib.o在磁盘和内存中有两份副本,当系统中有大量类似Lib.o的多个程序共享目标文件时,就会浪费很大空间。

    1. 静态链接对程序的更新部署和发布很不友好:假如一个模块依赖20个模块,当20个模块其中有一个模块需要更新时,需要将所有的模块都找出来重新编译出一个可执行程序才可以更新成功,每次更新任何一个模块,用户就需要重新获得一个非常大的程序,程序如果使用静态链接,那么通过网络来更新程序也会非常不便,一旦程序任何位置有一个小改动,都会导致整个程序重新下载。

    为了解决静态链接的缺点,所以引入了动态链接,动态链接的内存分布如图,

    9c4e6447149486d8ccd4240b5ad057e6.png

    多个程序依赖同一个共享目标文件,这个共享目标文件在磁盘和内存中仅有一份,不会产生副本,简单来讲就是不像静态链接一样对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接,把链接这个过程推迟到运行时才执行。动态链接的方式使得开发过程中各个模块更加独立,耦合度更小,便于不同的开发者和开发组织之间独立的进行开发和测试。

    如何进行动态链接

    看如下代码:

    // lib.c
    #include <stdio.h>
    
    void func(int i) {
        printf("func %d n", i);
    }
    // Program.c
    void func(int i);
    
    int main() {
        func(1);
        return 0;
    }
    

    编译运行过程如下:

    $ gcc -fPIC -shared -o lib.so lib.c
    $ gcc -o test Program.c ./lib.so
    $ ./test
    $ func 1
    

    通过-fPIC和-shared可以生成一个动态链接库,再链接到可执行程序就可以正常运行。

    通过readelf命令可以查看动态链接库的segment信息:

    ~/test$ readelf -l lib.so
    
    Elf file type is DYN (Shared object file)
    Entry point 0x530
    There are 7 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x00000000000006e4 0x00000000000006e4  R E    0x200000
      LOAD           0x0000000000000e10 0x0000000000200e10 0x0000000000200e10
                     0x0000000000000218 0x0000000000000220  RW     0x200000
      DYNAMIC        0x0000000000000e20 0x0000000000200e20 0x0000000000200e20
                     0x00000000000001c0 0x00000000000001c0  RW     0x8
      NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8
                     0x0000000000000024 0x0000000000000024  R      0x4
      GNU_EH_FRAME   0x0000000000000644 0x0000000000000644 0x0000000000000644
                     0x0000000000000024 0x0000000000000024  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000000e10 0x0000000000200e10 0x0000000000200e10
                     0x00000000000001f0 0x00000000000001f0  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
       01     .init_array .fini_array .dynamic .got .got.plt .data .bss
       02     .dynamic
       03     .note.gnu.build-id
       04     .eh_frame_hdr
       05
       06     .init_array .fini_array .dynamic .got
    

    可以看见动态链接模块的装载地址从0开始,0是无效地址,它的装载地址会在程序运行时再确定,在编译时是不确定的。

    改一下程序:

    // Program.c
    #include <stdio.h>
    void func(int i);
    
    int main() {
        func(1);
        sleep(-1);
        return 0;
    }
    

    运行读取maps信息:

    ~/test$ ./test &
    [1] 126
    ~/test$ func 1
    cat /proc/126/maps
    7ff2c59f0000-7ff2c5bd7000 r-xp 00000000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so
    7ff2c5bd7000-7ff2c5be0000 ---p 001e7000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so
    7ff2c5be0000-7ff2c5dd7000 ---p 000001f0 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so
    7ff2c5dd7000-7ff2c5ddb000 r--p 001e7000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so
    7ff2c5ddb000-7ff2c5ddd000 rw-p 001eb000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so
    7ff2c5ddd000-7ff2c5de1000 rw-p 00000000 00:00 0
    7ff2c5df0000-7ff2c5df1000 r-xp 00000000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so
    7ff2c5df1000-7ff2c5df2000 ---p 00001000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so
    7ff2c5df2000-7ff2c5ff0000 ---p 00000002 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so
    7ff2c5ff0000-7ff2c5ff1000 r--p 00000000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so
    7ff2c5ff1000-7ff2c5ff2000 rw-p 00001000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so
    7ff2c6000000-7ff2c6026000 r-xp 00000000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so
    7ff2c6026000-7ff2c6027000 r-xp 00026000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so
    7ff2c6227000-7ff2c6228000 r--p 00027000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so
    7ff2c6228000-7ff2c6229000 rw-p 00028000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so
    7ff2c6229000-7ff2c622a000 rw-p 00000000 00:00 0
    7ff2c62e0000-7ff2c62e3000 rw-p 00000000 00:00 0
    7ff2c62f0000-7ff2c62f2000 rw-p 00000000 00:00 0
    7ff2c6400000-7ff2c6401000 r-xp 00000000 00:00 189023             /mnt/d/wzq/wzq/util/test/test
    7ff2c6600000-7ff2c6601000 r--p 00000000 00:00 189023             /mnt/d/wzq/wzq/util/test/test
    7ff2c6601000-7ff2c6602000 rw-p 00001000 00:00 189023             /mnt/d/wzq/wzq/util/test/test
    7fffee96f000-7fffee990000 rw-p 00000000 00:00 0                  [heap]
    7ffff6417000-7ffff6c17000 rw-p 00000000 00:00 0                  [stack]
    7ffff729d000-7ffff729e000 r-xp 00000000 00:00 0                  [vdso]
    

    可以看到,整个进程虚拟地址空间中,多出了几个文件的映射,lib.so和test一样,它们都是被操作系统用同样的方法映射到进程的虚拟地址空间,只是它们占据的虚拟地址和长度不同,从maps里可以看见里面还有libc-2.27.so,这是C语言运行库,还有一个ld-2.27.so,这是Linux下的动态链接器,动态链接器和普通共享对象一样被映射到进程的地址空间,在系统开始运行test前,会先把控制权交给动态链接器,动态链接器完成所有的动态链接工作后会把控制权交给test,然后执行test程序。

    当链接器将Program.o链接成可执行文件时,这时候链接器必须确定目标文件中所引用的func函数的性质,如果是一个定义于其它静态目标文件中的函数,那么链接器将会按照静态链接的规则,将Program.o的func函数地址进行重定位,如果func是一个定义在某个动态链接共享对象中的函数,那么链接器将会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,将这个过程留在装载时再进行。

    动态链接的方式

    动态链接有两种方式:装载时重定位和地址无关代码技术。

    装载时重定位:在链接时对所有绝对地址的引用不作重定位,而把这一步推迟到装载时完成,也叫基址重置,每个指令和数据相当于模块装载地址是固定的,系统会分配足够大的空间给装载模块,当装载地址确定后,那指令和数据地址自然也就确定了。然而动态链接模块被装载映射到虚拟空间,指令被重定位后对于每个进程来讲是不同的,没有办法做到同一份指令被多个进程共享,所以指令对不同的进程来说有不同的副本,还是空间浪费,怎么解决这个问题?使用fPIC方法。

    地址无关代码:指令部分无法在多个进程之间共享,不能节省内存,所以引入了地址无关代码的技术。我们平时编程过程中可能都见过-fPIC的编译选项,这个就代表使用了地址无关代码技术来实现真正的动态链接。基本思想就是使用GOT(全局偏移表),这是一个指向变量或函数地址的指针数组,当指令要访问变量或者调用函数时,会去GOT中找到相应的地址进行间接跳转访问,每个变量或函数都对应一个地址,链接器在装载模块的时候会查找每个变量和函数的地址,然后填充GOT中的各个项,确保每个指针指向的地址正确。GOT放在数据段,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本,相互不受影响。

    tips:

    -fpic和-fPIC的区别:它们都是地址无关代码技术,-fpic产生的代码相对较小较快,但是在某些平台会有些限制,所以大多数情况下都是用-fPIC来产生地址无关代码。

    -fPIC和-fPIE的区别:一个作用于共享对象,一个作用于可执行文件,一个以地址无关方式编译的可执行文件被称作地址无关可执行文件。

    -fpie和-fPIE的区别:类似于-fpic和-fPIC的区别

    延迟绑定技术

    在程序刚启动时动态链接器会寻找并装载所需要的共享对象,然后进行符号地址寻址重定位等工作,这些工作会减慢程序的启动速度,如果解决?

    使用PLT延迟绑定技术,这里会单独有一个叫.PLT的段,ELF将 GOT拆分成两个表.GOT和.GOT.PLT,其中.GOT用来保存全局变量的引用地址,.GOT.PLT用来保存外部函数的地址,每个外部函数在PLT中都有一个对应项,在初始化时不会绑定,而是在函数第一次被用到时才进行绑定,将函数真实地址与对应表项进行绑定,之后就可以进行间接跳转过去。

    显式运行时链接

    支持动态链接的系统往往都支持显式运行时链接,也叫运行时加载,让程序自己在运行时控制加载的模块,在需要时加载需要的模块,在不需要时将其卸载。这种运行时加载方式使得程序的模块组织变得很灵活,可以用来实现一些诸如插件、驱动等功能。

    通过这四个API可以进行显式运行时链接:

    dlopen():打开动态链接库
    dlsym():查找符号
    dlerror():错误处理
    dlclose():关闭动态链接库
    

    参考这段使用代码:

    #include <stdio.h>
    #include <dlfcn.h>
    
    int main() {
    
        void *handle;
        void (*f)(int);
        char *error;
    
        handle = dlopen("./lib.so", RTLD_NOW);
        if (handle == NULL) {
            printf("handle null n");
            return -1;
        }
        f = dlsym(handle, "func");
        do {
            if ((error = dlerror()) != NULL) {
                printf("errorn");
                break;
            }
            f(100);
        } while (0);
        dlclose(handle);
    
        return 0;
    }
    

    编译运行:

    $ gcc -o test program.c -ldl
    $ ./test
    func 100
    

    总结

    为什么要进行动态链接?为了解决静态链接浪费空间和更新困难的缺点。

    动态链接的方式?装载时重定位和地址无关代码技术。

    地址无关代码技术原理?通过GOT段实现间接跳转。

    延迟加载技术原理?对外部函数符号通过PLT段实现延迟绑定及间接跳转。

    如果进行显式运行时链接?通过<dlfcn.h>头文件中的四个函数,代码如上。

    参考资料

    https://www.ibm.com/developerworks/cn/linux/l-dynlink/index.html

    http://chuquan.me/2018/06/03/linking-static-linking-dynamic-linking/

    https://www.cnblogs.com/tracylee/archive/2012/10/15/2723816.html

    《程序员的自我修养:链接装载与库》 更多文章,请关注我的V X 公 主 号:程序喵大人,欢迎交流~

    展开全文
  • 什么进行动态链接 静态链接使得不同的程序开发者能够相对独立地开发和测试自己的程序和模块,从某种意义上来讲大大促进了程序开发的效率,但是慢慢静态链接的诸多缺点就暴露出来,比如浪费内存和磁盘空间、模块...

    为什么要进行动态链接

    静态链接使得不同的程序开发者能够相对独立地开发和测试自己的程序和模块,从某种意义上来讲大大促进了程序开发的效率,但是慢慢静态链接的诸多缺点就暴露出来,比如浪费内存和磁盘空间、模块更新困难等等

    空间浪费

    特别是在多进程操作系统情况下,静态链接极大地浪费内存空间,想象一下每个程序内部都保留着printf、scanf等等这样的公用库函数,还有更多的其他库函数。在现在的Linux系统中,一个普通程序会使用到的c语言静态库至少在1MB以上,当有很多个程序运行的时候,这个大小就很可观了。

    发布和维护

    空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多的麻烦。

    动态链接

    要解决空间浪费和更新困难者两个问题最简单的方法就是把程序的模块相互分割开,形成独立的文件,而不将他们静态地连接到一起。也就是不对安歇组成程序的目标文件进行连接,等到程序要运行时才进行连接。

    就是把链接这个过程推迟到运行时再进行,这就是动态链接。

    在内存中共享一个目标文件的好处不仅仅是节省内存,它还可以减少物理页面的换入换出,也可以增加CPU缓存命中率,因为不同进程间的数据和指令访问都几种在了同一个共享模块上。

    动态链接还有一个特点就是程序在运行时可以动态地选择加载各种程序模块,这个优点就是制作程序插件。

    动态链接还可以加强程序的兼容性,一个程序在不同的平台运行时可以动态地连接到由操作系统提供的动态链接库,这些动态链接库相当于程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间的依赖的差异性。

    基本实现

    动态链接涉及到运行时的链接及多个文件的装载,必须要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也有一些微妙的变化。

    在Linux系统中,ELF动态链接文件被称为动态共享对象,简称共享对象,它们一般都以.so为扩展名的一些文件;而在Windows系统中,动态链接文件称为动态链接库,就是我们常见的以.dll为扩展的文件。

    常用的C语言库的运行库文glibc,它的动态链接形式的版本保存在/lib目录下,文件名叫做lib.so。整个系统值保留一份C语言库的动态链接文件libc.so,而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序被装载的时候,系统的动态链接器会将程序所需要的所有动态链接库(最基本的就是libc.so)装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。

    程序与libc.so之间的真正的链接工作由动态链接器完成,而不是由静态链接器ld完成。也就是将链接这个过程从本来程序装载前被推迟到了装载的时,虽然在性能上有一些损失,但是对动态链接的过程可以进行优化,比如延迟绑定技术,可以使得动态链接的性能损失尽可能减小。

    展开全文
  • 老规矩,先抛出几个问题:为什么进行动态链接?如何进行动态链接什么是地址无关代码技术?什么是延迟绑定技术?如何程序运行过程中进行显式链接?为什么进行动态链接?因为静态链接有缺点:浪费内存和磁盘...

    9de73a98b24e6013b022025b2268da58.png83918775fdaefb40a0399398ba0f8fcf.png

    ☞ 当当年中庆,又有羊毛可以薅了!!!

    在前面的文章中程序喵已经介绍过静态链接的原理,这篇文章我们来解密动态链接。

    老规矩,先抛出几个问题:

    • 为什么要进行动态链接?

    • 如何进行动态链接?

    • 什么是地址无关代码技术?

    • 什么是延迟绑定技术?

    • 如何在程序运行过程中进行显式链接?

    为什么要进行动态链接?

    因为静态链接有缺点:

    1. 浪费内存和磁盘空间:如下图,

    7dab19da74f71e254ca19a785e1573dd.png

    Program1和Program2分别包含Program1.o和Program2.o两个模块,他们都需要Lib.o模块。静态链接情况下,两个目标文件都用到Lib.o这个模块,所以它们同时在链接输出的可执行文件Program1和program2中有副本,同时运行时,Lib.o在磁盘和内存中有两份副本,当系统中有大量类似Lib.o的多个程序共享目标文件时,就会浪费很大空间。

    1. 静态链接对程序的更新部署和发布很不友好:假如一个模块依赖20个模块,当20个模块其中有一个模块需要更新时,需要将所有的模块都找出来重新编译出一个可执行程序才可以更新成功,每次更新任何一个模块,用户就需要重新获得一个非常大的程序,程序如果使用静态链接,那么通过网络来更新程序也会非常不便,一旦程序任何位置有一个小改动,都会导致整个程序重新下载。

    为了解决静态链接的缺点,所以引入了动态链接,动态链接的内存分布如图,

    b177244f2ea10a33d16af6eec78ec8fb.png

    多个程序依赖同一个共享目标文件,这个共享目标文件在磁盘和内存中仅有一份,不会产生副本,简单来讲就是不像静态链接一样对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接,把链接这个过程推迟到运行时才执行。动态链接的方式使得开发过程中各个模块更加独立,耦合度更小,便于不同的开发者和开发组织之间独立的进行开发和测试。

    如何进行动态链接?

    看如下代码:

    // lib.c#include void func(int i) {   printf("func %d \n", i);}
    // Program.cvoid func(int i);int main() {   func(1);   return 0;}

    编译运行过程如下:

    $ gcc -fPIC -shared -o lib.so lib.c$ gcc -o test Program.c ./lib.so$ ./test$ func 1

    通过-fPIC和-shared可以生成一个动态链接库,再链接到可执行程序就可以正常运行。

    通过readelf命令可以查看动态链接库的segment信息:

    ~/test$ readelf -l lib.soElf file type is DYN (Shared object file)Entry point 0x530There are 7 program headers, starting at offset 64Program Headers: Type           Offset             VirtAddr           PhysAddr                FileSiz            MemSiz              Flags  Align LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000                0x00000000000006e4 0x00000000000006e4  R E    0x200000 LOAD           0x0000000000000e10 0x0000000000200e10 0x0000000000200e10                0x0000000000000218 0x0000000000000220  RW     0x200000 DYNAMIC        0x0000000000000e20 0x0000000000200e20 0x0000000000200e20                0x00000000000001c0 0x00000000000001c0  RW     0x8 NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8                0x0000000000000024 0x0000000000000024  R      0x4 GNU_EH_FRAME   0x0000000000000644 0x0000000000000644 0x0000000000000644                0x0000000000000024 0x0000000000000024  R      0x4 GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000                0x0000000000000000 0x0000000000000000  RW     0x10 GNU_RELRO      0x0000000000000e10 0x0000000000200e10 0x0000000000200e10                0x00000000000001f0 0x00000000000001f0  R      0x1Section to Segment mapping: Segment Sections...  00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame  01     .init_array .fini_array .dynamic .got .got.plt .data .bss  02     .dynamic  03     .note.gnu.build-id  04     .eh_frame_hdr  05  06     .init_array .fini_array .dynamic .got

    可以看见动态链接模块的装载地址从0开始,0是无效地址,它的装载地址会在程序运行时再确定,在编译时是不确定的。

    改一下程序:

    // Program.c#include void func(int i);int main() {   func(1);   sleep(-1);   return 0;}

    运行读取maps信息:

    ~/test$ ./test &[1] 126~/test$ func 1cat /proc/126/maps7ff2c59f0000-7ff2c5bd7000 r-xp 00000000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5bd7000-7ff2c5be0000 ---p 001e7000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5be0000-7ff2c5dd7000 ---p 000001f0 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5dd7000-7ff2c5ddb000 r--p 001e7000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5ddb000-7ff2c5ddd000 rw-p 001eb000 00:00 516391             /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5ddd000-7ff2c5de1000 rw-p 00000000 00:00 07ff2c5df0000-7ff2c5df1000 r-xp 00000000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so7ff2c5df1000-7ff2c5df2000 ---p 00001000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so7ff2c5df2000-7ff2c5ff0000 ---p 00000002 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so7ff2c5ff0000-7ff2c5ff1000 r--p 00000000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so7ff2c5ff1000-7ff2c5ff2000 rw-p 00001000 00:00 189022             /mnt/d/wzq/wzq/util/test/lib.so7ff2c6000000-7ff2c6026000 r-xp 00000000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6026000-7ff2c6027000 r-xp 00026000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6227000-7ff2c6228000 r--p 00027000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6228000-7ff2c6229000 rw-p 00028000 00:00 516353             /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6229000-7ff2c622a000 rw-p 00000000 00:00 07ff2c62e0000-7ff2c62e3000 rw-p 00000000 00:00 07ff2c62f0000-7ff2c62f2000 rw-p 00000000 00:00 07ff2c6400000-7ff2c6401000 r-xp 00000000 00:00 189023             /mnt/d/wzq/wzq/util/test/test7ff2c6600000-7ff2c6601000 r--p 00000000 00:00 189023             /mnt/d/wzq/wzq/util/test/test7ff2c6601000-7ff2c6602000 rw-p 00001000 00:00 189023             /mnt/d/wzq/wzq/util/test/test7fffee96f000-7fffee990000 rw-p 00000000 00:00 0                 [heap]7ffff6417000-7ffff6c17000 rw-p 00000000 00:00 0                 [stack]7ffff729d000-7ffff729e000 r-xp 00000000 00:00 0                 [vdso]

    可以看到,整个进程虚拟地址空间中,多出了几个文件的映射,lib.so和test一样,它们都是被操作系统用同样的方法映射到进程的虚拟地址空间,只是它们占据的虚拟地址和长度不同,从maps里可以看见里面还有libc-2.27.so,这是C语言运行库,还有一个ld-2.27.so,这是Linux下的动态链接器,动态链接器和普通共享对象一样被映射到进程的地址空间,在系统开始运行test前,会先把控制权交给动态链接器,动态链接器完成所有的动态链接工作后会把控制权交给test,然后执行test程序。

    当链接器将Program.o链接成可执行文件时,这时候链接器必须确定目标文件中所引用的func函数的性质,如果是一个定义于其它静态目标文件中的函数,那么链接器将会按照静态链接的规则,将Program.o的func函数地址进行重定位,如果func是一个定义在某个动态链接共享对象中的函数,那么链接器将会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,将这个过程留在装载时再进行。

    动态链接的方式

    动态链接有两种方式:装载时重定位和地址无关代码技术。

    装载时重定位:在链接时对所有绝对地址的引用不作重定位,而把这一步推迟到装载时完成,也叫基址重置,每个指令和数据相当于模块装载地址是固定的,系统会分配足够大的空间给装载模块,当装载地址确定后,那指令和数据地址自然也就确定了。然而动态链接模块被装载映射到虚拟空间,指令被重定位后对于每个进程来讲是不同的,没有办法做到同一份指令被多个进程共享,所以指令对不同的进程来说有不同的副本,还是空间浪费,怎么解决这个问题?使用fPIC方法。

    地址无关代码:指令部分无法在多个进程之间共享,不能节省内存,所以引入了地址无关代码的技术。我们平时编程过程中可能都见过-fPIC的编译选项,这个就代表使用了地址无关代码技术来实现真正的动态链接。基本思想就是使用GOT(全局偏移表),这是一个指向变量或函数地址的指针数组,当指令要访问变量或者调用函数时,会去GOT中找到相应的地址进行间接跳转访问,每个变量或函数都对应一个地址,链接器在装载模块的时候会查找每个变量和函数的地址,然后填充GOT中的各个项,确保每个指针指向的地址正确。GOT放在数据段,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本,相互不受影响。

    tips

    dd97c8c9810119ea454f0fcbd9364295.png

    -fpic和-fPIC的区别:它们都是地址无关代码技术,-fpic产生的代码相对较小较快,但是在某些平台会有些限制,所以大多数情况下都是用-fPIC来产生地址无关代码。

    -fPIC和-fPIE的区别:一个作用于共享对象,一个作用于可执行文件,一个以地址无关方式编译的可执行文件被称作地址无关可执行文件。

    -fpie和-fPIE的区别:类似于-fpic和-fPIC的区别

    3351d506af6e22260f6e0e99e822fe4b.png

    延迟绑定技术

    在程序刚启动时动态链接器会寻找并装载所需要的共享对象,然后进行符号地址寻址重定位等工作,这些工作会减慢程序的启动速度,如果解决?

    使用PLT延迟绑定技术,这里会单独有一个叫.PLT的段,ELF将 GOT拆分成两个表.GOT和.GOT.PLT,其中.GOT用来保存全局变量的引用地址,.GOT.PLT用来保存外部函数的地址,每个外部函数在PLT中都有一个对应项,在初始化时不会绑定,而是在函数第一次被用到时才进行绑定,将函数真实地址与对应表项进行绑定,之后就可以进行间接跳转。

    显式运行时链接

    支持动态链接的系统往往都支持显式运行时链接,也叫运行时加载,让程序自己在运行时控制加载的模块,在需要时加载需要的模块,在不需要时将其卸载。这种运行时加载方式使得程序的模块组织变得很灵活,可以用来实现一些诸如插件、驱动等功能。

    通过这四个API可以进行显式运行时链接:

    dlopen():打开动态链接库dlsym():查找符号dlerror():错误处理dlclose():关闭动态链接库

    参考这段使用代码:

    #include #include int main() {   void *handle;   void (*f)(int);   char *error;   handle = dlopen("./lib.so", RTLD_NOW);   if (handle == NULL) {       printf("handle null \n");       return -1;  }   f = dlsym(handle, "func");   do {       if ((error = dlerror()) != NULL) {           printf("error\n");           break;      }       f(100);  } while (0);   dlclose(handle);   return 0;}

    编译运行:

    $ gcc -o test program.c -ldl$ ./testfunc 100

    总结

    为什么要进行动态链接?为了解决静态链接浪费空间和更新困难的缺点。

    动态链接的方式?装载时重定位和地址无关代码技术。

    地址无关代码技术原理?通过GOT段实现间接跳转。

    延迟加载技术原理?对外部函数符号通过PLT段实现延迟绑定及间接跳转。

    如果进行显式运行时链接?通过头文件中的四个函数,代码如上。

    参考资料

    https://www.ibm.com/developerworks/cn/linux/l-dynlink/index.html
    http://chuquan.me/2018/06/03/linking-static-linking-dynamic-linking/
    https://www.cnblogs.com/tracylee/archive/2012/10/15/2723816.html
    《程序员的自我修养:链接装载与库》

    良许个人微信

    添加良许个人微信即送3套程序员必读资料

    → 精选技术资料共享

    → 高手如云交流社群

    13ca6c8b6699b4acd1a7a25a4125bdf2.png


    本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取!

    推荐阅读:

    Linux下如何寻找相同文件?

    有点厉害!用12万行代码堆出来个"蔡徐坤",编译还能通过!

    Linus 的开发电脑,配置是这样的!

    5T技术资源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,单片机,树莓派,等等。在公众号内回复「1024」,即可免费获取!!

    1671ba08777d1f49eb00074462f395d4.png

    展开全文
  • 动态链接库,就是将很多很多函数集合一块,进而形成库文件,最后,将这些库文件进行共享给需要的人或者组织使用。这,就是动态链接库了。 那么,既然知道了动态链接什么东西后,自然而然,也许会有人询问,...

    1.对于Dll文件,字面上的意思是动态链接库。可是,动态链接库,又是什么呢?
    回答这个问题前,先需要说明下,Dll只是动态链接库的其中一种,不是说动态链接库只有DLl。

    2.动态链接库是什么?动态链接库,就是将很多很多函数集合在一块,进而形成库文件,最后,将这些库文件进行共享给需要的人或者组织使用。这,就是动态链接库了。
    那么,既然知道了动态链接库是个什么东西后,自然而然,也许会有人询问,动态链接库该怎么用?

    3.首先,动态链接库的调用,有静态调用与动态调用。其次,它们都有着相应的使用方向与范围,各有千秋;最后,动态链接库,可以理解成是一种封装,然后无论是静态调用,抑或是动态调用,其实就是对这种经过封装好的函数,直接调用即可。
    这样做的好处,不仅可以简化代码,而且,还可以对代码进行重构,与拓展新的函数,看到这里,是不是觉得,动态链接库,非常的好了。相信,看到这里,对动态链接库有了个了解了。

     

    介绍:
    DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。[1]

    意义:
    DLL文件中存放的是各类程序的函数(子过程)实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。另外,使用DLL文件还可以减小程序的体积。

    优点:
    (1) 更加节省内存并减少页面交换;

    (2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;

    (3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;

    (4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

    (5)节约磁盘空间:当应用程序使用动态链接时,多个应用程序可以共享磁盘上单个DLL副本。相比之下,当应用程序使用静态链接库时,每个应用程序要将库代码作为独立的副本链接到可执行镜像中。

    缺点:
    使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统死掉。
     

     


     

    展开全文
  • 1、静态链接: 静态库编译链接期间被...阶段二:加载时,可执行文件依据动态库信息进行动态链接。 当可执行文件加载(可执行文件复制到内存)完成后,程序开始运行之前,操作系统就会查找可执行文件依赖的动态库信
  • 动态链接

    2018-05-27 01:17:20
    动态链接就是不对那些组成程序的目标文件进行链接,而是程序运行时才进行链接。动态链接的基本思想就是把程序按照模块拆分成各个相对独立部分,程序运行时才将它们链接一起形成一个完整的程序,而...
  • 静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接进行则是在程序执行时。 一、静态链接 1.为什么进行静态链接 多个源文件之间存在多种依赖关系,一个源文件...
  • 动态链接 静态链接

    2013-04-17 22:24:16
    静态链接:静态链接是由链接器链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。 动态链接(Dynamic Linking...
  • 动态链接和静态链接

    2019-05-16 22:31:23
    动态链接和静态链接 什么是库文件? 为了防止相同功能的程序每次都要进行编译耗费时间,因此对这些调用函数接口进行编译汇编生成一个二进制文件 <gcc默认使用动态链接库> 什么是静态链接? ...
  • 静态链接:静态链接是由链接器链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。 动态链接(Dynamic Linking...
  • 动态链接相关

    2019-09-27 19:55:16
    由于需要动态链接的共享对象被装载映射至虚拟空间之后,指令部分是在多个进程之间共享的,由于装载时重定位的方法需要修改指令,所以没有办法做到同一份指令被多个进程共享,因为指令在被重定位之后对于每个进程来说...
  • 什么是动态库与静态库 我们生成可执行文件时,都会链接一些基础库及自己的需要的一些开发库。 这里我们可以对一个简单binary的符号表进行分析 图1 从图1可以看出test包含的符号表包含 dynamic_b, static_a, puts,...
  • 我们要进行制作报表或者数据分析的前提条件都要有数据,而实际用户系统最常见的就是将数据保存数据库中,并且不断的更新,数据连接就是创建BI工具与业务数据库之间的链接,使用数据库数据来制作报表,并且报表...
  • 要想了解分清楚什么是动态链接库,什么是静态链接库,就要先分清楚什么是库文件。 首先先给一个计算机操作系统的结构图如下,便于理解库文件。 所谓库文件就是将内核预留的一些对硬件的操作的接口整合组成一些...
  • 动态链接是什么呢,简单的说,不把各个模块连接一起,而分成各自独立的模块,运行之前不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是把链接的过程推迟到了运行时再链接,这就是动
  • 动态链接是把链接这个过程从本来的程序装载前被推迟到了装载的时候。动态链接文件的最终装载地址编译时是不确定的。 地址无关代码固定装载地址产生干扰装载时重定位:进行基址重置(Rebasing) 但是由于装载时重定位...
  • 动态链接是什么呢,简单的说,不把各个模块连接一起,而分成各自独立的模块,运行之前不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是把链接的过程推迟到了运行时再链接,这
  • 动态链接是什么动态链接是与静态链接相对的一种程序执行方式与模块组织的方式。说到动态链接,则需要和静态链接进行对比,才更好的解释动态链接。静态链接的缺点静态链接对于目标文件的组织是将所有应用到的的代码...
  • 动态链接库 dll

    2009-09-10 10:17:00
    装载时链接是指该动态链接是在程序装入时进行加载链接的,而运行时链接是指该动态链接是在程序运行时执行LoadLibrary(或LoadLibraryEx,下同)函数动态加载的。因此,由于动态链接库有这两种链接方式,所以在...
  • C语言静态/动态链接库的用法小记

    千次阅读 2017-08-11 13:38:20
    什么有静态库和动态库的区别,我简单的介绍一下我所理解的什么是静态库,什么是动态库。静态库Linux系统下后缀名为*.a Windows系统下后缀名为*.lib调用静态库,编译器进行编译过程中,有需要调用到库文件...
  • 动态链接能够解决以上两个问题,动态链接的思想即将程序和模块分隔开来,等到运行时才进行链接。所以内存中,多个程序可以共享一个模块,共享内存不仅节约内存,也能够减少物理页面的换入换出,增加CPU缓存命中...
  • 本文把用python调用C++的动态链接库 中,搜集了不少资料,有些博客讲的非常好,所以就把这些内容进行了一定的汇总和总结。 1.本文不讲如何编译动态链接库, 这个问题要自己学习一下如何编译,如 cmake 2.本文只...
  • 而且对底层开发也相对友好,为什么这么说主要因为它不仅可以面向C语言以及C++程序而且可以直接接触底层指针,相对而言功能比较全面的一门语言,本片文章主要介绍一下C#使用P-Invoke调用C/C++动态链接库. ...
  • 动态链接的基本思想把程序按照模块拆分成各个相对独立的部分,程序运行时才将它们连接一起形成完整的程序。 动态链接库:Linux的DSO,Windows的DLL。普通可执行程序和动态链接库都报刊指令和数据,
  • 为了了解什么是静态链接库和动态链接库,我们首先需要了解什么是库。由于经常有许多代码需要重复利用,库就是指创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从...
  • 所谓静态库,就是静态编译时由编译器到指定目录寻找并且进行链接,一旦链接完成,最终的可执行程序中就包含了该库文件中的所有有用信息,包括代码段、数据段等。所谓动态库,就是应用程序运行时,由操作系统根据...
  • 对于像调度系统这样一类系统而言,为了保持其自身的稳定和简单,一般会用外挂...为什么使用动态链接库动态库程序编译时并不会被连接到目标代码中,而是程序运行才被载入。不同的应用程序如果调用相同的库,那...
  • 什么是静态链接库 静态链接库就是你使用的.lib文件,库中的代码最后需要连接到你的可执行文件中去。 所以静态连接的可执行文件一般比较大一些,需要使用静态库时把.h与.lib文件...什么是动态链接动态链接库(Dy
  • 动态链接库,存放的各类程序的函数实现过程,当程序需要调用函数时,需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处程序不需要运行之初加载所有代码,只有程序需要某个函数的时候才从...
  • dll什么意思动态链接库,存放的各类程序的函数实现过程,当程序需要调用函数时,需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处程序不需要运行之初加载所有代码,只有程序需要某个函数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 644
精华内容 257
关键字:

动态链接是在什么进行的