对于使用PIC技术的可执行文件或共享对象来说,虽然它们的代码段不需要重定位(因为地址无关),但是数据段还包含了绝对地址的引用,因为代码段中绝对地址相关的部分被分离了出来,变成了GOT,而GOT实际上是数据段的一部分。除了GOT以外,数据段还可能包含绝对地址引用,我们在前面的章节中已经举例过了。
动态链接重定位的相关结构共享对象的重定位与我们在前面“静态链接”中分析过的目标文件的重定位十分类似,唯一有区别的是目标文件的重定位是在静态链接时完成的,而共享对象的重定位是在装载时完成的。在静态链接中,目标文件里面包含有专门用于表示重定位信息的重定位表,比如“rel.text”表示是代码段的重定位表,“rel.data”是数据段的重定位表。
动态链接的文件中,也有类似的重定位表分别叫做“ .rel.dyn”和“.rel. plt”,它们分别相当于“.rel.text”和“.rel.data”“.rel .dyn”实际上是对数据引用的修正,它所修正的位置位于“.got”以及数据段;而“ .rel.plt”是对函数引用的修正,它所修正的位置位于“.got.plt”。我们可以使用 readelf来查看一个动态链接的文件的重定位表
可以看到Lib.c中两个导入函数“printf” 和“sleep” 从“.rel.plt”到了“.rel.dyn”,并且类型也从R_386_JUMP_SLOT变成了R_386_PC32
而R_386_RELATIVE类型多出了一个偏移为0x0000042c的入口,这个入口是什么呢?通过对Lib.so 的反汇编可以知道,这个入口用来修正给printf的第一个参数,即我们的字符串常量“Printing from Lib.so %d\n”的地址。为什么这个字符串常量的地址在PIC时不需要重定位而在非PIC时需要重定位呢? 很明显,PIC时,这个字符串可以看做是普通的全局变量,它的地址是可以通过PIC中的相对当前指令的位置加上一个固定偏移计算出来的:而在非PIC中,代码段不再使用这种相对于当前指令的PIC方法,而是采用绝对地址寻址,所以它需要重定位;
动态链接时进程堆栈初始化信息站在动态链接器的角度看,当操作系统把控制权交给它的时候,它将开始做链接丁作,那么至少它需要知道关于可执行文件和本进程的一些信息,比如可执行文件有几个段(“ Segment”)、每个段的属性、程序的入口地址(因为动态链接器到时候需要把控制权交给可执行文件)等。这些信息往往由操作系统传递给动态链接器,保存在进程的堆栈里面。我们在前面提到过,进程初始化的时候,堆栈里面保存了关于进程执行环境和命令行参数等信息。事实上,堆栈里面还保存了动态链接器所需要的一些辅助信息数组( Auxiliary Vector)。辅助信息的格式也是一个结构数组,它的结构被定义在“elf.h”
typedf struct { uint32_t a_type; union { uint32_t a_val; } a_un; } Elf32_auxv_t;是不是已经对这种结构很熟悉了?没错,跟前面的“.dynamic”段里面的结构如出一辙。先是一个32位类型的值,后面是一个32位的数值部分,你很可能很奇怪为什么要用一个union把后面的32位数值包装起来,事实上,这个union没啥用,只是历史遗留而已,可以当做不存在。我们摘录几个比较重要的类型值,这几个类型值都是比较常见的,而且是动态链接器在启动时所需要的,如表7-3所示:
介绍了这么多关于辅助信息数组的结构,我们还没看到它位于进程堆栈的哪一个位置呢。事实上,它位于环境变量指针后面,比如我们假设操作系统传给动态链接器的辅助信息有4个,分别是:
AT_PHDR,值为0x08048034,程序表头位于0x08048034
AT_PHENT,值为20,程序表头中每一个项大小为20个字节
AT_PHNUM,值为7,程序表头共有7个项
AT_ENTRY,0x08048320,程序入口地址为0x08048320
那么进程的初始化堆栈就如图7-11所示。
我们可以写一个小程序来把堆栈中初始化信息全部打印出来,源代码如下
Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx