zoukankan      html  css  js  c++  java
  • 程序运行之动态链接二

    对于之前的动态链接生成的可执行文件,来看下进程的地址空间分布。首先在pro1.c中加入sleep(-1)进行延时,然后将可执行完文件转入后台处理,通过pid来查询进程的地址空间分布。

    cat /proc/3488/maps

    558a542d3000-558a542d4000 r-xp 00000000 08:03 39585204                   /home /c_prj/programmer/pro1

    558a544d3000-558a544d4000 r--p 00000000 08:03 39585204                   /home/c_prj/programmer/pro1

    558a544d4000-558a544d5000 rw-p 00001000 08:03 39585204                   /home/c_prj/programmer/pro1

    558a55835000-558a55856000 rw-p 00000000 00:00 0                          [heap]

    7fab9392e000-7fab93b15000 r-xp 00000000 08:01 13898368                   /lib/x86_64-linux-gnu/libc-2.27.so

    7fab93b15000-7fab93d15000 ---p 001e7000 08:01 13898368                   /lib/x86_64-linux-gnu/libc-2.27.so

    7fab93d15000-7fab93d19000 r--p 001e7000 08:01 13898368                   /lib/x86_64-linux-gnu/libc-2.27.so

    7fab93d19000-7fab93d1b000 rw-p 001eb000 08:01 13898368                   /lib/x86_64-linux-gnu/libc-2.27.so

    7fab93d1b000-7fab93d1f000 rw-p 00000000 00:00 0

    7fab93d1f000-7fab93d20000 r-xp 00000000 08:03 39585203                   /home/c_prj/programmer/Lib.so

    7fab93d20000-7fab93f1f000 ---p 00001000 08:03 39585203                   /home/c_prj/programmer/Lib.so

    7fab93f1f000-7fab93f20000 r--p 00000000 08:03 39585203                   /home/c_prj/programmer/Lib.so

    7fab93f20000-7fab93f21000 rw-p 00001000 08:03 39585203                   /home/c_prj/programmer/Lib.so

    7fab93f21000-7fab93f48000 r-xp 00000000 08:01 13898340                   /lib/x86_64-linux-gnu/ld-2.27.so

    7fab9412f000-7fab94132000 rw-p 00000000 00:00 0

    7fab94146000-7fab94148000 rw-p 00000000 00:00 0

    7fab94148000-7fab94149000 r--p 00027000 08:01 13898340                   /lib/x86_64-linux-gnu/ld-2.27.so

    7fab94149000-7fab9414a000 rw-p 00028000 08:01 13898340                   /lib/x86_64-linux-gnu/ld-2.27.so

    7fab9414a000-7fab9414b000 rw-p 00000000 00:00 0

    7fffd8c91000-7fffd8cb2000 rw-p 00000000 00:00 0                          [stack]

    7fffd8d29000-7fffd8d2c000 r--p 00000000 00:00 0                          [vvar]

    7fffd8d2c000-7fffd8d2e000 r-xp 00000000 00:00 0                          [vdso]

    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

    上面红色标红的部分就是Lib.so被链接进去后的地址,同时还可以看到C程序运行库libc-2.27.so也被链接进来。另外还有一个是ld-2.27.so,这个实际上是Linux下的动态连接器。在程序开始运行前,首先会将控制权交给动态链接器,由它完成所有的链接工作后再把控制权交给proc1。然后开始执行

    在同步看下Lib.so文件中的地址分布,发现动态链接模块的装载地址是从地址0x000000000000000开始的。这其实是个无效的地址,并且从上面的进程分布空间来看,Lib.so的最终装载地址并不是0x000000000000000。而是7fab93d1f000。因此我们可以判断,共享对象的装载地址在编译的时候是不确定的,而是在装载的时候,装载器根据当前地址空间的情况,动态分配一块足够大小的虚拟地址空间给响应的共享对象。

    readelf -l Lib.so

    Elf 文件类型为 DYN (共享目标文件)

    Entry point 0x530

    There are 7 program headers, starting at offset 64

    程序头:

      Type           Offset             VirtAddr           PhysAddr

                     FileSiz            MemSiz              Flags  Align

      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000

                     0x00000000000006f4 0x00000000000006f4  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   0x0000000000000654 0x0000000000000654 0x0000000000000654

                     0x0000000000000024 0x0000000000000024  R      0x4

      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000

                     0x0000000000000000 0x0000000000000000  RW     0x10

      GNU_RELRO      0x0000000000000e10 0x0000000000200e10 0x0000000000200e10

                     0x00000000000001f0 0x00000000000001f0  R      0x1

    为什么.so文件的装载地址和进程的会不一样呢,这是为了解决动态链接导致的共享对象地址冲突问题。在动态链接的情况下,单个程序来说,我们可以给不同的模块分配不同的地址,但是多个模块被多个程序使用,管理这些模块的地址就很复杂了,这会导致分配地址冲突。

    为了解决这个问题,有两种方法被引入了

    1 装载时重定位

    假设foobar相对于代码的其实地址是0x100, 当模块被装载到0x10000000时,假设代码段位于模块的最开始,也就是代码的装载地址也是0x10000000。那么就可以确定foobar的地址为0x10000100。这时候系统遍历模块中的重定位表,吧所有对foobar的地址引用都重定位至0x10000100。静态链接的时候这种称为链接重定位,而现在这种情况称为装载重定位。前面在产生共享对象的时候,使用了两个GCC参数”-shared”和”fPIC”,如果只使用shared,那么输出对象就是装载时重定位的方法。

    2 地址无关代码

    动态链接模块被装载映射至虚拟空间后,指令部分是在多个进程之间共享的。由于装载重定位的方法需要修改指令,所以没有一个方法做到同一份指令被多个进程共享。导致动态链接无法节省内存。因此我们采用地址无关的方式。ELF的做法是在数据段里面建立一个指向这些变量或者函数的指针数组。也被称为全局偏移表:GOT。当代码需要引用该全局变量或者函数的时候,可以通过GOT中相对应的项间接引用。链接器在装载模块的时候会查找每个变量或者函数的地址。然后填充GOT中的每个项,以确保每个指针指向的地址正确。并且每个进程都有独立的副本,相互不受影响。

    对于前面的Lib.so文件,通过objdump –h Lib.so。可以看到GOT在文件中的偏移是0x0fe0。

    再通过objdump –R Lib.so看下Lib.so需要在动态链接时重定位项:可以看到printf的地址位于0x1018。也就是GOT种的偏移28。相当于是GOT中的第7项(指针占据4个字节)

    延迟绑定(PLT)

    由于动态链接在处理模块间调用的时候要去查询GOT,然后再进行间接跳转。这样运行速度就比静态链接慢了很多。不过在一个程序运行过程中,可能很多函数在程序执行完时都不会被用到。比如异常或者是用户用得很少的模块,如果一开始就把所有的都链接好对性能确实是一种损失。所以ELF采用了一种叫延迟绑定的做法:函数第一次被调用的时候才进行绑定(符号查找,重定位等)。如果没有用到则不进行绑定,所以程序开始执行时,模块间的函数调用都没有进行绑定。而是需要用到的时候才由动态链接器负责绑定。这就可以大大加快程序的运行速度。

    具体做法如下:

    1 调用的时候不是直接根据GOT实现跳转,PLT为了实现延迟绑定,在这个过程中增加了一层间接跳转。每个外部函数在PLT中都有一个相应的项,比如bar函数在PLT中的项的地址称之为bar@plt。具体实现如下:

    bar@plt:

    jmp *(bar@GOT)

    push n

    push moduleID

    jump _dl_runtimt_resolve

    jmp *(bar@GOT)表示GOT中保存bar()这个函数相应的项。如果链接器在初始化阶段已经初始化该项。并且将bar()地址填入该项。那么这个跳转结果就是我们所期待的跳转到bar()。实现函数正确调用。但是为了实现延迟绑定,链接器在初始阶段并没有将bar地址放入GOT。所以从push n开始执行。数字n代表符号引用在重定位表 .rel.plt中的下标,接着又是一条push执行将moduleID压入到堆栈。然后跳转到_dl_runtimt_resolve。_dl_runtimt_resolve函数来完成符号解析和重定位工作。_dl_runtimt_resolve在进行查找和定位后将bar的真正地址填入到GOT中。一旦bar()函数解析完毕,当我们再次调用bar@plt的时候,第一条jmp指令就能够跳转到真正的bar函数,bar函数返回的时候会根据堆栈中保存的EIP直接返回到调用者,而不会执行bar@plt中第二条指令开始的那段代码。,那段代码只会在符号未被解析时执行一次。

  • 相关阅读:
    [CVPR2017]Online Video Object Segmentation via Convolutional Trident Network
    [CVPR2018]Context-aware Deep Feature Compression for High-speed Visual Tracking
    光栅图形学(二):圆弧的扫描转换算法
    光栅图形学(一):直线段的扫描转换算法
    Vector使用
    STL源码剖析 — 空间配置器(allocator)
    C++ traits技术浅谈
    OpenCv 2.4.9 (二) 核心函数
    vs2017 android demo
    asp.net webapi 自托管插件式服务
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/11018195.html
Copyright © 2011-2022 走看看