重定位
重定位(relocation)是把符号引用与符号定义连接在一起的过程。当程序调用一个函数时,将从当前运行的指令跳转到一个新的指令地址去执行。在编写程序的时候,只需指明所要调用的函数名(即符号引用),在重定位的过程中,函数名会与实际的函数所在地址(即符号定义)联系起来,使程序知道应该跳转到哪里去。
重定位文件必须知道如何修改其所包含的“节”的内容,在构建可执行文件或共享目标文件的时候,把节中的符号引用换成这些符号在进程空间中的虚拟地址。包含这些转换信息的数据就是“重定位项(relocation entries)”。重定位项结构:
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
r_offset
本数据成员给出重定位所作用的位置。对于重定位文件来说,此值是受重定位作用的存储单元在节中的字节偏移量;对于可执行文件或共享ELF文件来说,此值是受重定位作用的存储单元的虚拟地址。
r_info:本数据成员既给出了重定位所作用的符号表索引,也给出了重定位的类型。以下是应用于 r_info 的宏定义。
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t))
r_addend:本成员指定了一个加数,这个加数用于计算需要重定位的域的值。
Elf32_Rela 与 Elf32_Rel 在结构上只有一处不同,就是前者有 r_addend。Elf32_Rela 中是用r_addend 显式地指出加数;而对 Elf32_Rel来说,加数是隐含在被修改的位置里的。Elf32_Rel中加数的形式这里并不定义,它可以依处理器架构的不同而自行决定。
一个“重定位节(relocation section)”需要引用另外两个节:一个是符号表节,一个是被修改节。在重定位节中,节头的 sh_info 和 sh_link 成员分别指明了引用关系。不同的ELF文件中,重定位项的 r_offset 成员的含义略有不同,但其重定位的作用是不变的。
- 在重定位文件中,r_offset 成员含有一个节偏移量。也就是说,重定位节本身描述的是如何修改文件中的另一个节的内容,重定位偏移量(r_offset)指向了另一个节中的一个存储单元地址。
- 在可执行文件或共享目标文件中,r_offset 含有的是符号定义在进程空间中的虚拟地址。可执行文件和共享ELF文件是用于运行程序而不是构建程序的,所以对它们来说更有用的信息是运行期的内存虚拟地址,而不是某个符号定义在文件中的位置。
重定位类型(Relocation Types)
被重定位域(relocatable field)是一个 32 位的域,占 4 字节并且地址向 4 字节对齐,其字节序与所在体系结构下其他双字长数据的字节序相同。重定位项用于描述如何修改如下的指令和数据域:
假定以下的计算是发生在把可重定位文件转为可执行或共享目标文件的过程中。原则上,连接编辑器(link editor)要把一个或多个可重定位文件合并成一个可执行文件或共享ELF文件作为输出。它首先需要决定如何把输入文件组合起来,并定位其中的符号,然后更新符号值,最后实现重定位。对于可执行文件或共享目标文件的重定位过程是相似的,结果是相同的。
重定位类型有哪些,取决与特定的处理器架构,使用 readelf -r .o 可以查看重定位表。
为了下面的描述方便,这里定义以下几种运算符号:
- A 表示用于计算重定位域值的加数。
- B 表示在程序运行期,共享ELF被装入内存时的基地址。一般来说,共享ELF文件在构建时基地址为 0,但在运行时则不是。
- G 表示可重定位项在全局偏移量表中的位置,这里存储了此重定位项在运行期间的地址。更多信息参见下文“全局偏移量表”。
- GOT 表示全局偏移量表的地址。
- L 表示一个符号的函数连接表项的所在之处,可能是节内偏移量,或者是内存地址。函数连接表项把函数调用定位到合适的位置。在构建期间,连接编辑器创建初始的函数连接表;在运行期间,动态连接器会修改表项。更多信息参见“函数连接表”部分。
- P 表示被重定位的存储单元在节内的偏移量或者内存地址,由 r_offset 计算得到。
- S 表示重定位项中某个索引值所代表的符号的值。
重定位类型指定了哪些位需要被修改以及如何算计它们的值,下面使用x86系统处理器的重定位类型的计算方法说明。
名字 | 值 | 数据类型 | 计算 |
R_386_GOT32 | 3 | word32 | G+A |
R_386_PLT32 | 4 | word32 | L+A-P |
R_386_COPY | 5 | none | none |
R_386_GLOB_DAT | 6 | word32 | S |
R_386_JMP_SLOT | 7 | word32 | S |
R_386_RELATIVE | 8 | word32 | B+A |
R_386_GOTOFF | 9 | word32 | S+A-GOT |
R_386_GOTPC | 10 | word32 | GOT+A-P |
R_386_GLOB_DAT:这种重定位类型用于把指定的符号地址设置为一个全局偏移量表项。这种重定位类型在符号与全局偏移量表项之间建立起了联系。
R_386_JMP_SLOT:连接编辑器创建这种重定位类型,用于动态连接。此类型相应的 offset 成员给出了函数连接表项的位置。动态连接器修改函数连接表项来跳转到指定的符号地址。
R_386_RELATIVE:连接编辑器创建这种重定位类型,主要是用于动态连接。此类型相应的 offset成员给出了共享目标内的一个位置,这个位置含有一个代表相对地址的值。把共享目标被加载的地址加上这个相对地址,动态连接器就可以计算得到真正需要的虚拟
地址。这种类型的重定位项必须为符号表指定 0 值。
R_386_GOTOFF:这种重定位类型计算符号值与全局偏移量表地址之间的差值。它还指示连接编辑器去构建全局偏移量表。
R_386_GOTPC:这种重定位类型与 R_386_PC32 很相似,只不过在计算中它使用的是全局偏移量表的地址。一般来说,在这种类型的重定位中所引用的符号是_GLOBAL_OFFSET_TABLE_,它还指示连接编辑器去构建全局偏移量表。
下面是关于ARM的重定位:
关于ARM支持的重定位类型的定位的文档没有找到,然后在交叉编译工具链的./arm-linux-gnueabi/libc/usr/include/elf.h 文件中看了一下支持的比较多。
写到这虽然对ELF文件的整体有了大概的认识,但是感觉对很多地方的作用还是不明白的地方,特别是关于重定位,云里雾里,将会对ELF文件继续写下去。
Oracle® Solaris 11.2 链接程序和库指南 https://docs.oracle.com/cd/E56344_01/html/E54069/chapter6-74186.html#scrolltoc