静态链接浪费内存和磁盘空间并且更新困难。动态链接的基本思想: 把链接过程推迟到运行时进行。
-shared
生成动态链接模块时只使用-shared,由于装载时重定位的方法需要修改指令,没有办法做到同一份指令被多个进程共享,因为指令被重定位之后对于每个进程来讲是不同的。
-fPIC 地址无关代码
实现的基本思想就是把指令中那些需要被修改的部分分离出来,跟数据部分放在一起,这样指令部分可以保持不变,而数据部分在每个进程中拥有一个副本。这种方案就是地址无关技术
GOT全局偏移表
对于动态链接模块中,对于外部符号(数据)的访问的机制,当指令需要访问某个外部变量时,程序会先找到GOT,然后根据GOT中变量所对应的项找到变量的目标地址。每个变量都对应一个4个字节的地址,链接器在装载模块的时候会查找每个变量所在的地址,然后填充GOT中的各个项。由于GOT表本身是放在数据段的,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本。
-fPIE
地址无关代码技术除了可以用在动态链接模块上,它也可以用于可执行文件
共享模块(动态链接模块)的全局变量问题
当一个模块引用了定义在共享对象的全局变量的时候,由于可执行文件在之前链接时就必须确定该全局变量的地址,所以连接器会在创建可执行文件时,在它的.bss段创建一个global变量的副本。导致同一变量同时存在于多个位置
于是解决的办法只有一个,那就是所有的使用这个变量的指令都指向位于可执行文件中的那个副本。ELF共享库在编译时,默认都把定义在模块内部的全局变量当作定义在其他模块的全局变量,也就是说当作前面的类型四,通过GOT来实现变量的访问。当共享模块被装载时,如果某个全局变量在可执行文件中拥有副本,那么动态链接器就会把GOT中的相应地址指向该副本,这样该变量在运行时实际上最终就只有一个实例。如果变量在共享模块中被初始化,那么动态链接器还需要将该初始化值复制到程序主模块中的变量副本;如果该全局变量在程序主模块中没有副本,那么GOT中的相应地址就指向模块内部的该变量副本。
默认情况下,如果可执行文件是动态链接的,那么GCC会使用PIC的方法来产生可执行文件的代码段部分,以便于不同的进程能够共享代码段,节省内存。所以动态链接的可执行文件中存在.got段
延迟绑定
由于动态链接下对于全局数据的访问和跨模块的调用都要进行复杂的GOT定位,然后间接寻址或调用,导致程序的运行速度减慢大概1%~%5。又因为动态链接的链接工作在运行时完成,导致程序的启动速度减慢。
程序运行过程中,会有很多函数没有用到(错误处理函数,没有使用的功能模块等),所以没有必要一开始就把所有函数都链接好,ELF采用延迟绑定的方法,基本思想是当函数第一次被用到时才由动态链接器进行绑定(符号查找,重定位等),没用到的不绑定。这提高了程序的启动速度。
ELF使用PLT(Procedure Linkage Table)来实现延迟绑定,它使用了一些很精巧的指令序列来完成
ELF将GOT拆分成了两个表.got和.got.plt,其中.got用来保存全局变量引用的地址,.got.plt用来保存函数引用的地址
PLT在ELF文件中以独立的段存放,段名通常叫做.plt,因为它本身是一些地址无关代码,所以可以跟代码段合并成同一个可读可写可执行的“Segment”被载入内存
参考链接