链接器——重定位

重定位:编译器和汇编器生成从地址0开始的代码和数据结。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个这个内存位置。

重定位条目:当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置,它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置,所以,无论间时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

ELF重定位条目的格式:

typedef struct  {
     
      long offset;          /*需要被修改的引用的节偏移*/
      long type:32,         /*type告知链接器如何修改新的引用*/
           symbol:32;       /*Symbol标识被修改引用应该指向的符号*/
      long addend;          /*一个有符号常数*/
}Elf64_Rela;

ELF定义了32种不同的重定位类型,有些相当隐秘。我们只关心其中两种最基本的重定位类型:
R_X86_64_PC32 。重定位一个使用32位PC相对地址的引用。
R_X86_64_32 。重定位一个使用32位绝对地址的引用。

重定位符号引用
重定位算法:

1 foreach section s{
     
2     foreach relocation entry r{
     
3          refptr = s + r.offset; /*ptr to reference to be relocated*/
4
5          /* Relocate a PC-relative reference */
6          if (r.type == R_X86_64_PC32){
     
7                refaddr = ADDR(s) + r.offset;/* ref's run-time address*/
8                refptr=(unsigned) (ADDR(r.symbol)+ r.addend - refaddr);
9           }
10
11          /* Relocate an absolute reference */
12          if (r.type == R_X86_64_32)
13               *refptr=(unsigned) (ADDR(r.symbol) + r.addend);
14    }
15 }

第1行和第2行在每个节s以及与每个节相关联的重定位条目r上迭代执行。为了使描述具体化,假设每个节s是一个字节数组,每个重定位条目是一个类型为Elf64_Rela的结构,如 ELF重定位条目的格式 中的定义,另外假设当算法运行时,链接器已经为每个节(用ADDR(s)表示)和每个符号都选择了运行时地址(用ADDR(r.symbol)表示)。
第3行计算的是需要被重定位的4字节引用的数组s中的地址。
如果这个引用使用的是PC相对寻址,那么它就用第****5~9行来重定位。
如果该引用使用的是绝对寻址,它就通过第11~13行来重定位。

main.o反汇编代码:

1    0000000000000000 <main>2       048 83 ec 08           sub $0x8,%rsp
3       4: be 02 00 00 00        mov $0x2,%esi     
4       9: bf 00 00 00 00        mov $0x0,%edi      /*%edi = &array*/
5                           a:R_X86_64_32 array     /*Relocation entry*/

6       e: e8 00 00 00 00        callq 13 <main+0x13>   /*sum()*/
7         f:R_X86_64_PC32 sum-0x4  /*Relocation entry*/
8       13: 48 83 c4 08           add $0x8,%rsp
9       17:c3                    retq

1.重定位PC相对引用

main.o反汇编代码中函数main调用sum函数,sum函数是在模块sum.o中定义的,call指令开始于节偏移Oxe的地方,包括1字节的操作码0xe8,后面跟着的是对目标的32位PC相对引用的占位符。
相应的重定位条目r由4个字段组成:
r.offset = Oxf
r.symbol = sum
r.addend = -4
r.type=R_X86_64_PC32
这些字段告诉链接器修改开始于偏移量Oxf处的32位PC相对引用,这样在运行时它会指向sum例程。现在,假设链接器已经确定
ADDR(s) = ADDR(.text) = 0x4004d0 和
ADDR(r.symbol) = ADDR(sum) = 0x4004e8
链接器先计算出引用的运行时地址(第7行):
refaddr = ADDR(s) + r.offset
= 0x4004d0 + Oxf
= 0x4004df
然后,更新该引用,使得它在运行时指向sum程序(第8行):
refptr = (unsigned) (ADDR(r.symbol) + r.addend - refaddr)
(unsigned) (0x4004e8 +(-4) - Ox4004df)
=(unsigned) (0x5)
在得到的可执行目标文件中,call指令有如下的重定位的形式:
4004de: e8 05 00 00 00
callq 4004e8 < sum>

2.重定位绝对引用

重定位绝对引用相对简单。main.o反汇编代码第4行中,mov指令将array的地址(一个32位立即数值)复制到寄存器%edi中。mov指令开始于节偏移量0x9的位置,包括1字节操作码0xbf,后面跟着对array的32位绝对引用的占位符。
对应的占位符条目r包括4个字段:
r.offset = Oxa
r.symbol = array
r.type =R_X86_64_32
r.addend = 0
这些字段告诉链接器要修改从偏移量Oxa开始的绝对引用,这样在运行时它将会指向array的第一个字节。现在,假设链接器已经确定
ADDR(r.symbol) = ADDR(array) =0x601018
链接器使用第13行修改了引用:
*refptr=(unsigned) (ADDR(r.symbol) + r.addend)
= (unsigned) (0x601018 + 0)
=(unsigned)(0x601018)
在得到的可执行目标文件中,该引用有下面的重定位形式:
==4004d9: bf 18 10 60 00
mov $0x601018,%edi ==

主要参考读物:深入理解计算机系统第3版

你可能感兴趣的:(链接器——重定位)