Linux下动态链接的步骤与实现详解(3)

为了提高模块内部函数调用的效率,有一个办法是把bar()函数变成编译单元私有函数,即使用“ statIc”关键字定义bar()函数,这种情况下,编译器要确定bar()函数不被其他模块覆盖,就可以使用第一类的方法,即模块内部调用指令,可以加快函数的调用速度。

3. 重定位与初始化

当上面的步骤完成之后,链接器开始重新遍历可执行的文件和每个共享对象的重定位表,将它们的GOT/PLT的每个需要重定位的位置进行修正。因为此时动态链接器已经拥有了进程的全局符号表,所以这个修正过程也显得比较容易,跟我们前面提到的地址重定位的原理基本相同。在前面介绍动态链接的重定位表时,我们已经碰到了几种重定位类型,每种重定位入口地址的计算方法我们在这里就不再重复介绍了。

重定位完成之后,如果某个共享对象有“.init”段,那么动态链接器会执行“.init”段中的代码,用以实现共享对象特有的初始化过程,比如最常见的,共享对象中的C++ 的全局静态对象的构造就需要通过“init”来初始化。相应地,共享对象中还可能有“ finit”段,当进程退出时会执行“.finit"段中的代码,可以用来实现类似C++全局对象析构之类的操作。

如果进程的可执行文件也有“init”段,那么动态链接器不会执行它,因为可执行文件中的“init”段和“ finit”段由程序初始化部分代码负责执行,我们将在后面的“库”这部分详细介绍程序初始化部分。

当完成了重定位和初始化之后,所有的准备工作就宣告完成了,所需要的共享对象都已经装载并且链接完成了,这时候动态链接器就如释重负,将进程的控制权转交给程序的入口并且开始执行。

4. Linux动态链接器的实现

在前面分析 Linux下程序的装载时,己经介绍了一个通过 execve()系统调用被装载到进程的地址空间的程序,以及内核如何处理可执行文件。内核在装载完ELF可执行文件以后就返回到用户空间,将控制权交给程序的入口。对于不同链接形式的ELF可执行文件,这个程序的入口是有区别的。对于静态链接的可执行文件来说,程序的入口就是ELF文件头里面的 e_entry指定的入口;对于动态链接的可执行文件来说,如果这时候把控制权交给e_entry指定的入口地址,那么肯定是不行的,因为可执行文件所依赖的共享库还没有被装载,也没有进行动态链接。所以对于动态链接的可执行文件,内核会分析它的动态链接器地址(在“.interp”段),将动态链接器映射至进程地址空间,然后把控制权交给动态链接器。

Linux动态链接器是个很有意思的东西,它本身是一个共享对象,它的路径是lib/ld-linux.so.2,这实际上是个软链接,它指向lib/ld-x.y.z.so,这个才是真正的动态连接器文件。共享对象其实也是ELF文件,它也有跟可执行文件一样的EF文件头(包括 e_entry、段表等)。动态链接器是个非常特殊的共享对象,它不仅是个共享对象,还是个可执行的程序,可以直接在命令行下面运行:

其实 Linux的内核在执行 execve()时不关心目标ELF文件是否可执行(文件头 e_type是 ET_EXEC还是 ET_DYN),它只是简单按照程序头表里面的描述对文件进行装载然后把控制权转交给ELF入口地址(没有“.interp”就是ELF文件的 e_entry;如果有“.interp”的话就是动态链接器的 e_entry)。这样我们就很好理解为什么动态链接器本身可以作为可执行程序运行,这也从一个侧面证明了共享库和可执行文件实际上没什么区别,除了文件头的标志位和扩展名有所不同之外,其他都是一样的。 Windows系统中的EXE和DLL也是类似的区别,DLL也可以被当作程序来运行, Windows提供了一个叫做rund32exe的工具可以把一个DLL当作可执行文件运行。

Linux的ELF动态链接器是Glbc的一部分,它的源代码位于Glibc的源代码的elf目录下面,它的实际入口地址位于 sysdeps/i386/d1-manchine.h中的__start(普通程序的入口地址start()在 sysdeps/i386/elf/start.S,本书的第4部分还会详细分析)

start调用位于 elf/rtld.c的_dl_start函数。dl start函数首先对ldso(以下简称ld x.y.z.so为ld.so)进行重定位,因为ld.so自己就是动态链接器,没有人帮它做重位工作,所以它只好自己来,美其名曰“自举”。自举的过程需要十分的小心谨慎,因为有很多限制.这个我们在前面已经介绍过了。完成自举之后就可以调用其他函数并访问全局变量了。调用_dl_start_final,收集一些基本的运行数值,进入_ dl_sysdep_start,这个函数进行一些平台相关的处理之后就进入了 _dl_main,这就是真正意义上的动态链接器的主函数了。 _dl_main在一开始会进行一个判断:

Linux下动态链接的步骤与实现详解

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/d1529f1940a41c9a7205cdc6c4a42f1c.html