【静态连接和动态链接库】
[静态链接]--->静态库:和源程序链接和并装载到虚拟内存
[动态链接]--->共享库:
<1>静态共享库:针对每个库,操作系统分配特定虚拟内存地址,模块装载到该特定的虚拟地址,若当前共享库未被装载,那么这个地址空间闲置。即最终装载地址在编译时未确定,在链接时由于知道了库所以确定了;
<2>动态共享库:动态共享库装载时,由装载器根据当前虚拟内存中地址空间情况,动态分配虚拟地址,即最终装载地址在链接时依然未确定直到装载。
[名词分析]
链接:源文件和库文件建立链接;
装载:链接后的文件建立和虚拟内存间的映射;
链接重定位:
是指在静态链接时,链接之前在源文件中引用的库文件函数名(如add)地址都记作0x0000,链接之后分类合并得到了该函数的相对于链接后文件起始地址0的相对地址(如0x1000),从0x0000-->0x1000地址的转变就是链接重定位;链接后装载时仅需同时偏移相应的量即可。
装载重定位:
动态链接由于链接时是动态加载库文件到虚拟内存中的,链接后的文件不是一个整体而且无法计算相对地址,所以只能在装载到虚拟内存时得到该库当前的内存地址。
【共享对象结构分析】
[.data]数据段:自动存入ELF文件数据段
共享库中的全局变量数据段(.data段)在装载时都并入了可执行文件的数据段(.got段),即在每一个进程的可执行文件中都存在其子共享库全局变量的副本。
共享库中的局部变量都存放在代码段(.text段)中,当开始执行后加载到内存中的堆栈里
[.interp]动态链接解释段:指定动态链接器路径。
[.dynamic]动态链接段:存放动态链接基本信息
DT_SYMTAB:[.dynsym]动态链接符号表地址;
DT_STRTAB:动态连接字符串表地址;
DT_REL:动态链接重定位表地址,包括代码段重定位表(.rel.text)和数据段重定位(.rel.data)。
DT_NEED:当前ETF文件所的共享对象文件
... ...
[.symtab]静态链接符号表:存放静态链接符号表
--------------------------------------------------------------------------------------------------
Q:动态链接段中的符号表和静态链接符号表关系?
A:动态链接表(.dynsym)只存放动态链接符号,而静态链接符号表(.symtab)存放所有符号(包括动态的)。
--------------------------------------------------------------------------------------------------
[.text]代码段:需要进行地址无关码转换!
代码段与绝对地址有关:
即代码段对应这某个确定的虚拟地址,那么它就不能被多个进程共享,因为它不是被操作系统随机分配的,就是说我们编译之后就已经知道了它的地址,这就是静态共享库了(已淘汰了);
当其他进程想要使用这个绝对地址,可以!因为这个地址早就知道了。
代码段与绝对地址无关:
即代码段的绝对地址当装载时候才能得到绝对地址,装载之前我们只知道相对地址,装载过程中操作系统按照内存空间随机的分配了一个绝对地址(已知了)!
当其他进程想使用这个绝对地址时,把当前虚拟内存中的代码和数据映射到其他进程的地址空间中。
---------------------------------------------------------------------------------------------------------------
静态链接:链接时重定位;
静态共享库:不需要重定位;
动态共享库:装载时重定位;
动态共享库:在装载时分配虚拟内存,各个动态库首地址一定要进行重定位-shared,但是动态共享库的代码段可能会产生和地址有关的代码:这个时候我们就需要-fPIC来帮助我们把代码段中的地址有关代码转换成地址无关代码,就多个可以很easy的使用共享对象中的代码段(只读)和数据段(读写);如果我们没有做出地址无关码这一步,那么共享库复用的时候从某个进程的虚拟内存映射到另外一个进程中,可以算得共享对象内部的代码,但这样就必须预留出空间,和自动分配空间就违背了。---------------------------------------------------------------------------------------------------------------
[代码段无关码实现方式]:
<1>内部:保留偏移量来计算当前地址,即与绝对地址无关。
模块内部函数调用和跳转;
模块内部数据访问。
<2>外部:依靠地址依赖/符号表查找绝对地址,
模块间数据访问; ---> 存放到本文件的全局变量地址段(.got段),ATT:不是可执行文件
模块间函数调用/跳转;--->保存到本文件的的函数引用地址段 (.got.plt段),ATT:不是可执行文件
Att:.got段中存放本文件全局变量的符号和地址,使用该某个变量只需在段中索引。
Att:.got.plt段中存放三项
<1>.dynamic段地址,使用某个函数名只需在动态链接符号表中索引即可找到引用的地址;
<2>存放本模块链接库的ID,如test.so中存放的就是test。
<3>存放_dll_runtime_resolve()函数地址,用来根据地址和符号完成地址绑定工作。
[简而言之]
装载共享对象:保证多个进程所需共享对象的内部代码段与绝对地址无关,把和绝对地址有关的数据/函数在动态链接后存放到各个共享对象的.dynamic段中。
void main(){
CPU控制权转交动态链接器;
操作系统告诉了动态链接器有关可执行文件的基本信息(包括入口地址,段的基本信息等);
动态链接器通过本身的静态链接自举,自举结束后把可执行文件静态链接到内存的某个特定地址;
然后通过查看可执行文件的.dynamic段中的DT_NEED把当前文件所依赖的共享对象文件找出来,
while(每映射一个依赖的动态对象){
查看该动态对象的.dynamic段观察该对象是否依赖别的对象,如果依赖,返回上一步循环;
把当前动态对象文件中的符号表合并到虚拟空间中的全局符号表中;
把当前依赖文件中的全局变量和静态局部变量装载映射到虚拟内存中的静态区;
把可执行文件和依赖文件的局部变量存放到栈中,栈中作用量会在离开栈时自动销毁;
把程序代码映射到虚拟内存程序代码区;
if(所有依赖对象装载完毕)
break;
} /*ELF和LInux进程虚拟空间映射完毕*/
CPU控制权转交可执行文件入口地址;
可执行文件开始执行;
Process Virtual Space --> Physical Space
/*虚拟空间和物理空间映射完毕*/
}