一、为什么要动态链接
相比静态链接,动态链接的优势:
1. 节省磁盘和内存空间:静态链接中,磁盘和内存重要保存多份Lib.o的副本;而动态链接则只有一份。
2. 方便程序开发和发布:静态链接中,如果一个程序的任何模块需要更新,整个程序需要重新链接,发布给用户;而动态链接则只需要发布变化了的模块。
3. 内存中共享一个目标文件(Lib.o),可以增加CPU缓存的命中率
4. 动态链接的程序在运行时可以动态地选择加载各种程序模块,被用来制作程序的插件。
相比静态链接,动态链接的劣势:
1. DLL Hell:当程序依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致原有的程序无法运行;
2. 运行性能损失:动态链接的程序每次被装载时都要重新进行链接,尽管采用了延迟绑定(Lazy Binding),依然会损失5%左右性能。
二、简单的动态链接例子
动态链接编译Program1和Program2两个程序,我用一个图就总结书上这节的内容(我太厉害了!)
Lib.c
#include <stdio.h> void foobar(int i){ printf("printing from Lib.so %d\n",i); }
Lib.h
#ifndef LIB_H #define LIB_H void foobar(int i); #endif
Program1.c
#include "Lib.h" int main(){ foobar(1); return 0; }
Program2.c
#include "Lib.h" int main(){ foobar(2); return 0; }
看图:
三、地址无关代码
1. 装载时重定位:
1.1. 为什么要“装载时重定位”:可执行文件基本可以确定自己在进程虚拟地址空间的实际位置,因为可执行文件往往是第一个被加载的文件,他可以选择一个固定空闲的地址,比如Linux下一般都是0x08040000,Windows下一般都是0x0040000;与此不同,共享对象.so在编译时就不能假设自己在进程虚拟空间中的其实位置。如果操作系统本想把共享对象装载到进程虚拟空间的0x100这个地址,结果发现这个地址已经被别的程序占用了,则将装载地址调整到一个空闲的位置。总之,因为共享对象.so被装载到的位置(VMS中某地址)不能固定,所以要装载时重定位。
gcc -shared 选项指定输出对象使用装载时重定位
2. 地址无关代码PIC(Position-independent Code):
2.1. 为什么要“地址无关代码”:
采用装载时重定位的方式存在一个问题就是相同的共享对象.so代码(不包括数据部分,数据部分每个进程都有自己的副本)不能在多个进程间共享(每个都将代码放到了它自己的进程虚拟地址空间中了),这就失去了动态链接库dll(或共享对象so)节省内存的优势。
要解决这个问题,我们的目的很简单,希望程序模块中共享的指令部分在装载时不需要因为装载地址的改变而改变,所以实现的基本想法就是把指令中那些需要被修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本。ELF中是这样实现的:在数据段中建立一个指向那些需要被使用的地址列表GOT(Global Offset Table全局偏移表),通过GOT相应的位置进行间接引用。
gcc -fPIC 选项指定输出对象使用地址无关方式
2.2.怎样做到地址无关代码
具体来看,如何将.so中代码段做成与绝对地址无关的——有4种情况。
static int a; extern int b; extern void ext(); void bar(){ a=1; //情况二、内部数据访问 b=2; //情况四、外部数据访问 } void foo(){ bar(); //情况一、内部函数调用 ext(); //情况三、外部函数调用 }
情况一、模块内部调用和跳转:
情况二、模块内部数据访问:
这两种情况不需要用到GOT,只需指定“内部数据”相对于下一条指令的偏移,因为模块在编译时就可以确定模块内部变量相对于当前指令的偏移。(Sam: 下面“情况一、情况二图”中.text和.data两个section相对位置应该不会变化,只是在装载时会根据VMS空闲情况整体调整他们两一起被装载的位置)
情况三、模块间数据访问:
情况四、模块间调用和跳转:
在编译时就可以确定GOT相对于当前指令的偏移(这与情况一、二使用的方法一样),然后我们根据变量地址在GOT中的偏移就可以得到变量的地址,当然变量对应于GOT中哪个偏移是由编译器决定的。链接器在装载模块时会查找每个变量所在的地址,然后填充GOT中的每个项。由于GOT本身是放在数据段的,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本,互不影响。
情况一、情况二、PIC(模块内)
情况三、情况四、PIC(模块间)
四、延迟绑定 PLT
1.1. 为什么要“延迟绑定PLT”:如果不用PLT,程序开始执行时候,动态链接器都要进行一次链接工作,动态链接器会寻找并加载所需要的共享对象,然后进行符号查找地址重定位等工作,势必减慢程序的启动速度。延迟绑定则在没用用到函数时不进行绑定,当函数第一次被用到时才进行绑定(符号查找、重定位等)。
区别.got .rel.dyn .got.plt .rel.plt :
acess位置无关的数据redirect到.got中某项 <--> 延迟加载对应的重定位段 .rel.dyn
acess位置无关的函数redirect到.got.plt中某项 <--> 延迟加载对应的重定位段 .rel.plt
1.2. 延迟绑定PLT具体实现
.PLT0: push *(.got.plt+4) #本模块的ID jump *(.got.plt+8) #_dl_runtime_resolve()函数的地址 .PLT1: #==> 凡调用bar(),将跳转到.PLT1。 #(1) 如果已经重定位,则bar()的绝对地址已经被填充到got.plt的表项bar@got.plt中,就可以直接跳转到bar() #(2) 如果尚未重定位,则got.plt的表项bar@got.plt中还是0,直接跳到下一条语句执行,压入bar()在重定位表”.rel.plt”中的下标, # 压入本模块ID, # 跳转到_dl_runtime_resolve()函数,去绑定函数bar() jump *(bar@got.plt) push offset_of_rel #offset_of_rel是bar()在重定位表”.rel.plt”中的下标 jump .PLT0
延迟绑定的符号解析过程大概如下(以上面示例中的bar()为例):
1.代码中调用bar()的地方在编译时已经被替换成一条调用bar@plt的转移指令,即是调用的”.plt”中的bar所在的项,即”.PLT1″。
2.当首次解析的时候,虽然在”.got.plt”中有bar函数的项,但是在没有解析之前,该项存储的内容为0,而非bar函数的绝对地址,因此指令”jump *(bar@got.plt)”实际上没有任何的动作,只是直接跳转到下一条push指令(因为jump的偏移为0)。
3.指令”push offset_of_rel”是将bar()函数的重定位项的索引压栈,而该重定位项的类型为R_386_JMP_SLOT,offset_of_rel是重定位表”.rel.plt”中的下标。
4.接着指令”jump .PLT0″跳转到.PLT0,将”.got.plt”的第二表项压栈,然后再跳转到”.got.plt”第三个表项指定的地址。这里要简述一下”.got.plt”的结构,”.got.plt”和”.got”几乎是一样的,除了”.got.plt”前三项的值有特殊意义:(1)第一项保存”.dynamic”段的地址,这个段描述了本模块动态链接相关的信息(可暂忽略);(2)保存本模块的ID(用于在动态链接时查找本模块的信息);(3)保存的是_dl_runtime_resolve()函数的地址,用于启动动态链接器,加载模块,解析符号并重定位。。
5.此时,可以看到在堆栈上,已经存有bar()函数在重定位项的索引和当前要重定位的模块ID,那么_dl_runtime_resolve()函数就可以启动动态链接器,当动态链接器得到控制后,它恢复堆栈,获取在步骤3中入栈的重定位项索引和步骤4中入栈的本模块信息,通过这2个信息,动态链接器符号解析和计算绝对地址,将bar()的“真实”地址存储于bar()在全局偏移表”.got.plt”的表项(原来的值为0)。
6.动态链接器更新了”.got.plt”中的信息后,将执行的控制权传递给bar()函数。那么当再次调用bar()函数时,PC依然会跳转到”.PLT1″,但是此时”bar@got.plt”的值已经不再是0了,因为会正确地跳转到bar()函数的绝对地址
五、动态链接相关结构
.interp段:保存一个字符串(可执行文件所需动态链接器的路径)
.dynamic段:动态链接相关信息(i.e. 依赖哪些共享对象;动态链接符号表的位置;动态链接重定位表的位置等)
.dynsym段(动态符号表):类似.symtab段(符号表),与它不同的是.dynsym段只保存了动态链接相关的符号,对于那些模块内部的符号,比如模块私有变量则不保存。即,.dynsym是.symtab的一个子集。
动态链接重定位表
.rel.dyn实际上是对数据引用的修正,它所修正的位置位于.got以及数据段;
.rel.plt 是对函数引用的修正,它所修正的位置位于.got.plt
六、动态链接步骤和实现
1. 动态链接器自举
2. 装载共享对象
3. 重定位和初始化
Linux下的动态链接器是: /lib/ld-linux.so.2 ,它是 /lib/ld-x.y.z.so的软链接。
七、显式运行时链接
参考资料:
1. 编译,链接相关的问题。-fPIC ,地址无关代码,等等 http://www.verydemo.com/demo_c116_i3904.html
2. Dynamic Linker http://ytliu.github.com/blog/2012/12/15/dynamic-linker/
相关参考文章和文章用到的图片 见附件
-------------------------------------------------------------------------------------------------------------------------------------
如何理解“地址无关代码”:
Sam:本质就是不要绝对跳转,要跳转也是相对PC的相对跳转
首先,需要理解加载域与运行域的概念。加载域是代码存放的地址,运行域是代码运行时的地址。为什么会产生这2个概念?这2个概念的实质意义又是什么呢?
在一些场合,一些代码并不在储存这部分代码的地址上执行地址,比如说,放在norflash中的代码可能最终是放在RAM中运行,那么中norflash中的地址就是加载域,而在RAM中的地址就是运行域。
在汇编代码中我们常常会看到一些跳转指令,比如说b、bl等,这些指令后面是一个相对地址而不是绝对地址,比如说b main,这个指令应该怎么理解呢?main这里究竟是一个什么东西呢?这时候就需要涉及到链接地址的概念了,链接地址实际上就是链接器对代码中的变量名、函数名等东西进行一个地址的编排,赋予这些抽象的东西一个地址,然后在程序中访问这些变量名、函数名就是在访问一些地址。一般所说的链接地址都是指链接这些代码的起始地址,代码必须放在这个地址开始的地方才可以正常运行,否则的话当代码去访问、执行某个变量名、函数名对应地址上的代码时就会找不到,接着程序无疑就是跑飞。但是上面说的那个b main的情形有点特殊,b、bl等跳转指令并不是一个绝对跳转指令,而是一个相对跳转指令,什么意思呢?就是说,这个main标签最后得到的只并不是main被链接器编排后的绝对地址,而是main的绝对地址减去当前的这个指令的绝对地址所得到的值,也就是说b、bl访问到的是一个相对地址,不是绝对地址,因此,包括这个语句和main在内的代码段无论是否放在它的运行域这段代码都能正常运行。这就是所谓的位置无关代码。
由上面的论述可以得知,如果你的这段代码需要实现位置无关,那么你就不能使用绝对寻址指令,否则的话就是位置有关了。
相关推荐
在IT领域,链接是软件构建过程中的一个重要环节,它将编译后的对象文件或静态库合并成一个可执行文件或动态库。本章节主要探讨的是链接的各个方面,特别是与驱动程序开发相关的链接技术,以及在C++和Java编程中链接...
链接篇 链接是一种在编译期和运行期将...链接是计算机科学与技术学院哈尔滨工业大学的第7章的主要内容。它是将多个目标文件组合成一个可执行文件的过程。链接有很多应用和优点,例如模块化、公共函数库和提高效率等。
在“Java语言程序与数据结构梁勇第十版第七章复习题答案”中,我们主要探讨的是Java编程语言的基础知识,特别是与数据结构相关的概念。Java作为一种面向对象的编程语言,对于初学者来说,理解其基本语法、类和对象、...
在标题"第七章 UDF的编译与链接_fluentudf_UDFdescription_源码"中,我们可以推测这是一个关于如何编写、编译和链接UDF的教程章节,特别关注于UDF的实现过程和实际应用。 Fluent UDF是用C或C++语言编写的,它们...
本人看《深入理解计算机系统》的整理文档,以思维导图的形式讲述链接的背景、原理、过程、方法和优缺点,分享出来,如有错误,欢迎指正交流!
NULL 博文链接:https://chuanwang66.iteye.com/blog/1839210
在"Chapter07"这个压缩包文件中,我们可以预期找到与HTML第7章相关的练习题和解答,这可能包括了HTML的基本标签使用、表格、段落、标题、图像的嵌入、链接的创建,以及CSS的选择器、属性和盒模型等内容。练习题目...
Linux基础课件第七章多模块软件的编译和链接.ppt
第七章通常会涉及树形结构,包括二叉树、平衡树等重要概念,因为这些数据结构在实际应用中极为广泛,如搜索、排序、文件系统和数据库等领域。 在C语言中实现数据结构,需要深入理解指针、内存管理和函数调用等基本...
计算机操作系统第四章习题答案 本资源共计13道题,涵盖计算机操作系统的多个方面,包括存储器管理、链接方式、动态分区分配、内存管理等。此资源可以帮助学习者更好地理解计算机操作系统的基本概念和原理。 1. 为...
本教程的第5章专门聚焦于这一主题,旨在帮助初学者快速掌握链接的基本概念以及高级技巧。 一、链接基础 1. 链接的概念:链接是互联网的灵魂,它允许用户从一个网页跳转到另一个网页。在HTML中,我们使用`<a>`标签来...
第七章可能讨论了消息循环、消息映射以及如何处理用户输入和其他系统事件,如WM_PAINT、WM_COMMAND等。 3. **对话框和控件** 学习如何创建和使用对话框模板,以及在对话框中添加控件(如按钮、编辑框、列表框等)...
编译后的UDF存储在共享库中,Fluent运行时动态装载执行。编译UDF的库必须与当前系统架构、操作系统及Fluent版本兼容。每次升级或更换环境都需要重新编译库。 **关于DEFINE宏和udf.h** 在编译UDF之前,需要包含...
第7章 项目ExMouseCapture,鼠标捕获; 项目ExCursor,改变客户区光标为I形光标; 项目ExMK,鼠标光标位置坐标在状态栏的显示和客户区点击鼠标左键,弹出鼠标光标位置信息对话框; 项目ExChar,只有在当前窗口下输入...
第五章的内容主要涉及程序装入、链接、内存管理、动态重定位、分区分配算法、对换技术、分页和分段存储管理。 1. 程序装入内存的方式主要有三种:绝对装入、可重定位装入和动态运行时装入。绝对装入方式适用于单道...
第7 章 图形与图像 第8 章 多媒体编程技术 第 9 章 OpenGL 开发三维图形 第10 章 多线程应用程序 第11 章 动态链接库 第 12 章 Delphi 数据库的基本概念 第 13 章 简单数据库应用程序的创建 第14 章 数据交换 第 15 ...
第七章的内容可能涵盖了更高级的主题,如表格、图像、链接、表单和多媒体元素的使用,这些都是构建交互式和动态网页的关键部分。 【上机阶段与实践】 上机阶段是理论学习的补充,让学生有机会将所学知识应用到实际...