- 启动链接器
- 装载所有需要装载的对象
- 重定位和初始化
动态链接器的自举:
动态链接器本身也是一个共享对象。动态链接器的特殊性在于它不可以依赖其他共享对象,它所依赖的全局和静态变量的重定位的工作由自己完成。当操作系统将控制权交给动态链接器,它就开始了自举的过程。
装载共享对象:
完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,我们可以称它为全局符号表( Global Symbol Table)。然后链接器开始寻找可执行文件所依赖的共享对象,我们前面提到过“ .dynamic"段中,有一种类型的入口是DT_ NEEDED,它所指出的是该可执行文件(或共享对象)所依赖的共享对象。由此,链接器可以列出可执行文件所需要的所有共享对象,并将这些共享对象的名字放入到一个装载集合中。然后链接器开始从集合里取-个所需要的共享对象的名字,找到相应的文件后打开该文件,读取相应的ELF文件头和“.dynamic"段,然后将它相应的代码段和数据段映射到进程空间中。如果这个ELF共享对象还依赖于其他共享对象,那么将所依赖的共享对象的名字放到装载集合中。如此循环直到所有依赖的共享对象都被装载进来为止,当然链接器可以有不同的装载顺序,如果我们把依赖关系看作一个图的话,那么这个装载过程就是一个图的遍历过程,链接器可能会使用深度优先或者广度优先或者其他的顺序来遍历整个图,这取决于链接器,比较常见的算法一般都是广度优先的。当一个新的共享对象被装载进来的时候,它的符号表会被合并到全局符号表中,所以当所有的共享对象都被装载进来的时候,全局符号表里面将包含进程中所有的动态链接所需要的符号。
符号的优先级:两个模块定义了同一个符号,装载进来的时候,全局符号表中,后来的直接被忽略。这种现象被称为全局符号介入。全局符号介入与地址无关代码
前面介绍地址无关代码时,对于第-类模块内部调用或跳转的处理时,我们简单地将其当作是相对地址调用/跳转。但实际上这个问题比想象中要复杂,结合全局符号介入,关于调用方式的分类的解释会更加清楚。还是拿前面“pic.c"的例子来看,由于可能存在全局符号介入的问题,foo 函数对于bar的调用不能够采用第一类模块内部调用的方法,因为一旦bar函数由于全局符号介入被其他模块中的同名函数覆盖,那么foo如果采用相对地址调用的话,那个相对地址部分就需要重定位,这又与共享对象的地址无关性矛盾。所以对于bar()函数的调用,编译器只能采用第三种,即当作模块外部符号处理,bar()函数被覆盖,动态链接器只需要重定位“.got.plt", 不影响共享对象的代码段。
重定位和初始化
当上面的步骤完成之后,链接器开始重新遍历可执行文件和每个共享对象的重定位表,将它们的GOT/PLT中的每个需要重定位的位置进行修正。因为此时动态链接器已经拥有了进程的全局符号表,这个修正过程跟地址重定位的原理基本相同。重定位完成之后,如果某个共享对象有“.init"段,那么动态链接器会执行“.init"段中的代码,用以实现共享对象特有的初始化过程,比如最常见的,共享对象中的C++的全局/静态对象的构造就需要通过“.init"来初始化。相应地,共享对象中还可能有“.finit" 段,当进程退出时会执行“.finit"段中的代码,可以用来实现类似C++全局对象析构之类的操作。
标签:符号表,全局,实现,装载,对象,共享,动态,链接 From: https://www.cnblogs.com/wuyun--wy/p/17007458.html