动态链接

1 动态链接的简介

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

1.1 动态链接库的好处:

  1. 各个进程共享同一个动态链接库,不仅仅节省内存,而且还可以减少物理页面的换入换出,也可以增加CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上。
  2. 可以使程序的升级变得更加容易,当我们升级共享模块时,只需要重新编译共享模块,而不用再将共享模块和程序重新链接。这种情况只能是改了函数内部实现,如果是改了函数名或者变量,则程序和共享库还得重新链接。

1.2 动态链接的基本实现:

动态链接的基本思想就是把程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完成的程序,而不是像静态链接把所有的模块都链接成一个单独的可执行文件。再Linux下ELF动态链接被称为动态共享库(DSO)。

2 动态链接库的例子:

program1.c

#include"lib.h"
int main(){
    foobar(1);
    return 0;
}

program2.c

#include"lib.h"
int main(){
    foobar(2);
    return 0;
}

lib.c

#include<stdio.h>
#include<unistd.h>
void foobar(int i){
    printf("printing from lib.so %d\n",i);
    sleep(-1);
}

lib.h

#ifndef LIB_H
#define LIB_H
void foobar(int i);
#endif

gcc -fPIC -shared -o lib.so lib.c
gcc -o program1 program1.c lib.so
这里写图片描述
运行program1的时候,会提示找不到lib.so,所以我们必须把lib.so拷到动态库的默认寻找路径,例如/lib目录下。
查看program 运行时的内存分布

tl@tl-vm:~$ cat /proc/10871/maps
00400000-00401000 r-xp 00000000 08:01 1341564                            /home/tl/program/c/testlll/test_dynamic_link/program1
00600000-00601000 r--p 00000000 08:01 1341564                            /home/tl/program/c/testlll/test_dynamic_link/program1
00601000-00602000 rw-p 00001000 08:01 1341564                            /home/tl/program/c/testlll/test_dynamic_link/program1
0172e000-0174f000 rw-p 00000000 00:00 0                                  [heap]
7fc2427c1000-7fc242981000 r-xp 00000000 08:01 1713694                    /lib/x86_64-linux-gnu/libc-2.23.so
7fc242981000-7fc242b81000 ---p 001c0000 08:01 1713694                    /lib/x86_64-linux-gnu/libc-2.23.so
7fc242b81000-7fc242b85000 r--p 001c0000 08:01 1713694                    /lib/x86_64-linux-gnu/libc-2.23.so
7fc242b85000-7fc242b87000 rw-p 001c4000 08:01 1713694                    /lib/x86_64-linux-gnu/libc-2.23.so
7fc242b87000-7fc242b8b000 rw-p 00000000 00:00 0 
7fc242b8b000-7fc242b8c000 r-xp 00000000 08:01 1704033                    /lib/lib.so
7fc242b8c000-7fc242d8b000 ---p 00001000 08:01 1704033                    /lib/lib.so
7fc242d8b000-7fc242d8c000 r--p 00000000 08:01 1704033                    /lib/lib.so
7fc242d8c000-7fc242d8d000 rw-p 00001000 08:01 1704033                    /lib/lib.so
7fc242d8d000-7fc242db3000 r-xp 00000000 08:01 1713692                    /lib/x86_64-linux-gnu/ld-2.23.so
7fc242f8b000-7fc242f8f000 rw-p 00000000 00:00 0 
7fc242fb2000-7fc242fb3000 r--p 00025000 08:01 1713692                    /lib/x86_64-linux-gnu/ld-2.23.so
7fc242fb3000-7fc242fb4000 rw-p 00026000 08:01 1713692                    /lib/x86_64-linux-gnu/ld-2.23.so
7fc242fb4000-7fc242fb5000 rw-p 00000000 00:00 0 
7ffd36fc9000-7ffd36fea000 rw-p 00000000 00:00 0                          [stack]
7ffd36feb000-7ffd36fee000 r--p 00000000 00:00 0                          [vvar]
7ffd36fee000-7ffd36ff0000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

可以看到,运行时,程序除了加载program.o外,还加载了lib.so,ld-2.23.so,libc-2.23.so。
ld-2.23.so是动态链接器,当program运行时,将segment加载到内存中后,控制权就交给动态链接器,由它进行动态链接,动态链接其实和静态链接很相似,可以简单理解就是将编译时的未知符号填上。
因为program1是c程序,所以需要c动态库libc-2.23.so,然后program1用了lib.so中的foobar,所以也需要lib.so。
既然链接在运行时完成,那gcc -o program1 program1.c lib.so的时候,为什么还需要lib.so呢?
原因是,链接器如何知道foobar是一个静态符号还是一个动态符号呢,这就要靠gcc -o program1 program1.c的时候,加上lib.so了,lib.so保存了完整的符号信息(在动态符号段中),这样,链接器就知道foobar是一个动态符号,在装载时,再重新链接。
我们看一下lib.so文件

tl@tl-vm:~/program/c/testlll/test_dynamic_link$ readelf -l lib.so

Elf 文件类型为 DYN (共享目标文件)
入口点 0x5e0
共有 7 个程序头,开始于偏移量 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000007b4 0x00000000000007b4  R E    200000
  LOAD           0x0000000000000e00 0x0000000000200e00 0x0000000000200e00
                 0x0000000000000230 0x0000000000000238  RW     200000
  DYNAMIC        0x0000000000000e18 0x0000000000200e18 0x0000000000200e18
                 0x00000000000001c0 0x00000000000001c0  RW     8
  NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_EH_FRAME   0x0000000000000734 0x0000000000000734 0x0000000000000734
                 0x000000000000001c 0x000000000000001c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e00 0x0000000000200e00 0x0000000000200e00
                 0x0000000000000200 0x0000000000000200  R      1

可以看到,.so文件和.o文件其实是差不多的,但是地址动态链接库的转载地址是从0x0开始的,但是由上cat /proc/xx/maps可以看到,lib.so的最终地址并不是0x0,所以我们可以推断出,共享对象的最终转载地址在编译时不确定的,而是在装载时,转载器根据内存的情况,再分配的。

3 地址无关代码

上面-fPIC是产生地址无关代码,-shared是指明装载时重定位。
加入有如下模块
这里写图片描述
与地址无关的代码,也就是需要考虑代码中会对地址进行引用的情况,共享对象(GCC中的动态链接文件)中地址引用可以分为以下几种情况:
a) 模块内函数调用、跳转等
b) 模块内数据的访问,如模块内定义的静态变量,全局变量等
c) 模块外部的函数调用、跳转等
d) 模块外部的数据的访问,比如别的模块定义的全局变量
其中a,b情况下,运行时,在将模块加载入程序后,模块其在内存中的位置就固定了,根据偏移,模块内部的函数,变量就可以很容易找到了,但是,由于c,d情况下,模块X还需要访问其他模块Y,所以链接器也会将Y也加载入内存,但是,不同程序,将Y加载到的内存位置是不一样的,这就导致,模块X该处的跳转代码因为程序的不同而不同,所以,每个程序都得各自加载X模块,Y模块,并在动态链接时,将X模块相应处的跳转地址更改。
这里写图片描述

由上图可看,每个程序的模块X的”call ext地址”这行代码中的call后面应该接具体的ext位置,所以,进程A,进程B的”call ext地址”处代码地址不一样,即A中模块X的call处代码应该为”call 0x400”,而B程序中的call处代码应该为”call 0x200”。
因为进程A,进程B ,将模块 Y加载到内存中的位置不一样。
这样就导致模块X的text段无法共用。
所以,我们可以在模块X中将”call ext地址”代码中的”ext地址”提出来,放到GOT表中,即”ext地址”是GOT表中的一项,然后链接时再去根据实际情况去填充GOT表,这样,模块X中的text段就一样了,这样,多个进程就可以共用模块X的代码段,而因为GOT(global offset table)是位于data段的,所以,每个进程都有模块X数据段的副本。
为了让模块形成地址无关代码,必须在编译的时候加上-fPIC
这里写图片描述
总结:地址无关代码就是将代码中可能变的地址如call xx的xx放在GOT中,而这个GOT是位于data段的,所以每个调用该模块的进程只需要包含模块的data段就可以了,而共享数据段。
如何共享模块的代码段呢?
我猜想是用共享内存实现的,即在物理内存上只有一份模块的代码段,然后映射到不同进程的地址空间上。每个进程会将模块代码地址映射到Memory Mapping Segment,但是具体位置,每个进程都不一样。而模块的数据段,已经被整合到进程的数据段中了。
这里写图片描述

4 延迟绑定

5 实现动态链接的关键数据结构

5.1 “interp”段

记录动态链接器的位置。

tl@tl-vm:~/program/c/testlll/test_dynamic_link$ objdump -s program1

program1:     文件格式 elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.    
Contents of section .note.ABI-tag:

5.2 “.dynamic”段

记录所需要的动态链接库

l@tl-vm:~/program/c/testlll/test_dynamic_link$ readelf -d program1

Dynamic section at offset 0xe18 contains 25 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[lib.so]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x000000000000000c (INIT)               0x400528
 0x000000000000000d (FINI)               0x400714
 0x0000000000000019 (INIT_ARRAY)         0x600e00
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e08
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x4003f0
 0x0000000000000006 (SYMTAB)             0x4002d0
 0x000000000000000a (STRSZ)              180 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           48 (bytes)

5.3 动态链接重定位表

动态链接中的”rel.dyn”和“rel.plt“分别类似静态链”.rel.data”和”.rel.text”,“rel.dyn”修正数据段和GOT,”rel.plt”修正GOT.PLT也就是函数。

tl@tl-vm:~/program/c/testlll/test_dynamic_link$ readelf -r program1

重定位节 '.rela.dyn' 位于偏移量 0x4e0 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000600ff8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

重定位节 '.rela.plt' 位于偏移量 0x4f8 含有 2 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000601018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 foobar + 0
000000601020  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
tl@tl-vm:~/program/c/testlll/test_dynamic_link$ readelf -S program1
共有 31 个节头,从偏移量 0x19d0 开始:

[23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000

可以容易看到,重定位表中的每一项的offset其实就是其在相应的got或者got.plt中位置,所以,动态链接时,我们只要找foobar函数的位置,然后找到其动态重定位表,然后往000000601020地址填入foobar函数的地址即可。

6 动态链接的步骤和实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值