计算机系统篇之链接:静态链接(下)——重定位

Posted csstormq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机系统篇之链接:静态链接(下)——重定位相关的知识,希望对你有一定的参考价值。

计算机系统篇之链接(5):重定位

Author:stormQ

Wednesday, 15. April 2020 04:35PM


重定位的整体过程

重定位的目的是确定每个符号定义的运行时内存地址,并修改对这些符号的引用,使之指向符号定义的运行时内存地址。

重定位的整体过程可以分为两个步骤:

  • 重定位节和符号定义。链接器将输入目标文件的相同节合并成一个节,合并的节将作为可执行目标文件中此类型的节。随后,链接器确定每个合并节的运行时内存地址,并确定合并节中符号定义的运行时内存地址。这一步骤完成后,可执行目标文件中的所有指令和符号定义的运行时内存地址就唯一确定了。

  • 重定位节中的符号引用。链接器修改所有的符号引用,使之指向符号定义的运行时内存地址。链接器要执行此步骤依赖于目标文件中的重定位信息。

重定位条目

ELF 中的重定位条目分为两种格式:RelRela。每个重定位条目表示一个必须被重定位的符号引用,并指明如何计算被修改的符号引用。重定位条目由汇编器生成。

查看可重定位目标文件中的重定位信息:

$ readelf -r main.o

Relocation section '.rela.text' at offset 0x208 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000005  000b00000002 R_X86_64_PC32     0000000000000000 _Z4funcv - 4

Relocation section '.rela.eh_frame' at offset 0x220 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
  • 注:

    • Offset是 Relocation Entry 结构体中的第 1 个字段,占用 8 字节,表示需要修改的符号引用的位置。对于可重定位目标文件,该字段表示需要修改的符号引用的起始位置在目标 section (.rela.text中的重定位条目对应的目标 section 为.text.rela.data中的重定位条目对应的目标 section 为.data,以此类推)中的偏移量(字节)。对于可执行目标文件和可共享目标文件,该字段表示需要修改的符号引用的起始位置所对应的虚拟内存地址。

    • Info是 Relocation Entry 结构体中的第 2 个字段,占用 8 字节,表示符号表索引和重定位类型(符号表索引占用高 32 位,重定位类型占用低 32 位)。

      符号表索引表示需要修改的符号引用在.symtabsection中的索引。这里的Sym. ValueSym. Name列只是打印了所对应符号表条目中ValueName列的值。

      重定位类型指示链接器如何修改该符号引用的值。重定位类型因不同的处理器而异。

    • Addend是 Relocation Entry 结构体中的第 3 个字段,占用 8 字节,表示一个有符号常数,一些重定位类型要使用它对被修改符号引用的值做偏移调整。

    • 重定位条目RelRela之间的唯一区别:Rel中没有Addend字段。

源码:

$ cat test.cpp 
extern int g_val_1;
extern int g_val_2;

void func()

  g_val_1 *= 2;
  g_val_2 *= 2;


$ cat main.cpp 
int g_val_1;
int g_val_2 = 3;

void func();

int main()

  func();
  return 0;

1)验证对于可重定位目标文件,Offset 字段表示需要修改的符号引用的起始位置在目标 section 中的偏移量(字节)

可重定位目标文件main.o中代码的重定位条目放在.rela.textsection 中。该 section 只包含一个重定位条目,其中Offset字段的值为 0x5,表示在目标 section (即.text)中起始位置为 0x5 的内容需要被修改。如果该内容的值在可执行目标文件main中被修改了,即可验证此结论。

查看可重定位目标文件main.o.textsection:

$ objdump -d main.o

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	e8 00 00 00 00       	callq  9 <main+0x9>
   9:	b8 00 00 00 00       	mov    $0x0,%eax
   e:	5d                   	pop    %rbp
   f:	c3                   	retq   

可以看出,main.o.textsection 中偏移量(相对于.text的起始位置,即相对于main函数的起始位置)为 0x5 的内容的当前值为 0x0。这里有两点需要注意:1)由于偏移量为 0x5 的位置与下一条指令的开始位置之间有 4 个字节,所以该内容占用 4 字节;2)由于main.o的字节序是小端,所以,00 00 00 00中最右边的00对应的地址为偏移量 0x5。

查看可执行目标文件main.textsection:

$ objdump -d main

main:     file format elf64-x86-64

# 省略...

Disassembly of section .text:

# 省略...

00000000004004d6 <main>:
  4004d6:	55                   	push   %rbp
  4004d7:	48 89 e5             	mov    %rsp,%rbp
  4004da:	e8 07 00 00 00       	callq  4004e6 <_Z4funcv>
  4004df:	b8 00 00 00 00       	mov    $0x0,%eax
  4004e4:	5d                   	pop    %rbp
  4004e5:	c3                   	retq   

# 省略...

可以看出,main.textsection 的main函数中偏移量(相对于main函数的起始位置)为 0x5 的内容的当前值为 0x7。即该内容的值由原来的 0x0 被修改为了 0x7。因此,验证了结论对于可重定位目标文件,Offset 字段表示需要修改的符号引用的起始位置在目标 section 中的偏移量(字节)

2)验证Info 字段的高 32 位的值表示需要修改的符号引用在.symtabsection中的索引

可重定位目标文件main.o.rela.textsection 只包含一个重定位条目,其中Info字段的值为 0x000b00000002(高 32 位的值为 0xb),符号名称(即Sym. Name列的值)为_Z4funcv。如果main.osymtabsection 中索引为 0xb 的符号表条目为_Z4funcv,即可验证此结论。

查看可重定位目标文件main.osymtabsection:

$ readelf -s main.o

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_val_2
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 main
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z4funcv

从上面结果中可以看出,symtabsection 中索引为 0xb 的符号表条目正是_Z4funcv。因此,验证了结论Info 字段的高 32 位的值表示需要修改的符号引用在.symtabsection中的索引

重定位类型

  • X86_64 中的重定位类型(部分)
X86_64 中的重定位类型含义地址在指令中占用的字节数符号引用的值如何计算
R_X86_64_PC3232 位 PC 相对地址4计算方式详见下文
R_X86_64_6464 位绝对地址8计算方式详见下文
R_X86_64_3232 位绝对地址4计算方式详见下文

1)重定位类型 R_X86_64_PC32 如何计算符号引用的值

重定位类型R_X86_64_PC32表示重定位符号引用的结果是一个 32 位的 PC 相对地址。

一个 PC 相对地址就是距程序计数器(即PC寄存器)的当前运行时值的偏移量。当 CPU 执行一条使用 32 位 PC 相对寻址的指令时,它会将指令中编码的 32 位值加上 PC 寄存器的当前运行值,作为目的地址(如call指令的目标地址),PC 寄存器中的值通常是下一条指令在内存中的地址。

因此,可以得出公式1:ADDR(PC) + VALUE(reference) = ADDR(defined_symbol)

注:ADDR(PC)表示符号引用所在指令的下一条指令的运行时地址;VALUE(reference)表示要修改的符号引用的值,重定位符号引用的目的就是计算出该值的大小;ADDR(defined_symbol)表示被引用的符号定义的运行时地址。

另外,公式2:ADDR(PC) + VALUE(r.addend) = ADDR(s) + VALUE(r.offset)。(该公式是根据结果反向推导而来的)

注:VALUE(r.addend)表示重定位条目中addend字段的值;ADDR(s)表示符号引用的目标 section 的运行时地址;VALUE(r.offset)表示重定位条目中offset字段的值;。

根据公式1和公式2,可以得出VALUE(reference)的计算方式。推导过程为:

VALUE(reference) = ADDR(defined_symbol) - ADDR(PC)
			     = ADDR(defined_symbol) - ( ADDR(s) + VALUE(r.offset) - VALUE(r.addend) )
			     = ADDR(defined_symbol) + VALUE(r.addend) - ( ADDR(s) + VALUE(r.offset) )
			     = ADDR(defined_symbol) + VALUE(r.addend) - ADDR(reference) 

注:ADDR(reference)表示需要修改的符号引用的运行时地址。

最终,得出需要修改的符号引用的值等于ADDR(defined_symbol) + VALUE(r.addend) - ADDR(reference)

接下来,通过示例验证上述过程。

查看可重定位目标文件main.o的重定位信息:

$ readelf -r main.o

Relocation section '.rela.text' at offset 0x208 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000005  000b00000002 R_X86_64_PC32     0000000000000000 _Z4funcv - 4

Relocation section '.rela.eh_frame' at offset 0x220 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

从上面结果中可以看出,VALUE(r.offset)的值为 0x5,VALUE(r.addend)的值为 -0x4。

查看可重定位目标文件main.o和可执行目标文件main.textsection:

$ objdump -d main.o

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	e8 00 00 00 00       	callq  9 <main+0x9>
   9:	b8 00 00 00 00       	mov    $0x0,%eax
   e:	5d                   	pop    %rbp
   f:	c3                   	retq   

$ objdump -d main

main:     file format elf64-x86-64

# 省略...

Disassembly of section .text:

# 省略...

00000000004004d6 <main>:
  4004d6:	55                   	push   %rbp
  4004d7:	48 89 e5             	mov    %rsp,%rbp
  4004da:	e8 07 00 00 00       	callq  4004e6 <_Z4funcv>
  4004df:	b8 00 00 00 00       	mov    $0x0,%eax
  4004e4:	5d                   	pop    %rbp
  4004e5:	c3                   	retq   

00000000004004e6 <_Z4funcv>:
  4004e6:	55                   	push   %rbp
  4004e7:	48 89 e5             	mov    %rsp,%rbp
  4004ea:	8b 05 48 0b 20 00    	mov    0x200b48(%rip),%eax        # 601038 <__TMC_END__>
  4004f0:	01 c0                	add    %eax,%eax
  4004f2:	89 05 40 0b 20 00    	mov    %eax,0x200b40(%rip)        # 601038 <__TMC_END__>
  4004f8:	8b 05 32 0b 20 00    	mov    0x200b32(%rip),%eax        # 601030 <g_val_2>
  4004fe:	01 c0                	add    %eax,%eax
  400500:	89 05 2a 0b 20 00    	mov    %eax,0x200b2a(%rip)        # 601030 <g_val_2>
  400506:	90                   	nop
  400507:	5d                   	pop    %rbp
  400508:	c3                   	retq   
  400509:	0f 1f 80 00 00 00 00 	nopl   0x0(%rax)
  
# 省略...

从上面结果中可以看出,ADDR(s)的值为 0x4004d6,ADDR(defined_symbol)的值为 0x4004e6。

因此,VALUE(reference)的值等于 0x4004e6 + (-0x4) - (0x4004d6 + 0x5) = 0x7。即链接器需要将符号引用的值修改为 0x7。查看main中该符号引用的值也确实是 0x7。

2)重定位类型 R_X86_64_64 如何计算符号引用的值

重定位类型R_X86_64_64表示重定位符号引用的结果是一个 64 位的绝对地址。

首先,编译源码时使用大型代码模型,即编译时添加选项-mcmodel=large,从而出现重定位类型为R_X86_64_64的重定位条目。

生成并查看可重定位目标文件main_large.o的重定位信息:

$ g++ -c main.cpp -mcmodel=large -o main_large.o
$ readelf -r main_large.o 

Relocation section '.rela.text' at offset 0x210 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000006  000b00000001 R_X86_64_64       0000000000000000 _Z4funcv + 0

Relocation section '.rela.eh_frame' at offset 0x228 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

生成并查看可执行目标文件main_large.textsection:

$ g++ -o main_large main_large.o test.o
$ objdump -d main_large

main_large:     file format elf64-x86-64

# 省略...

Disassembly of section .text:

# 省略...

00000000004004d6 <main>:
  4004d6:	55                   	push   %rbp
  4004d7:	48 89 e5             	mov    %rsp,%rbp
  4004da:	48 b8 ed 04 40 00 00 	movabs $0x4004ed,%rax
  4004e1:	00 00 00 
  4004e4:	ff d0                	callq  *%rax
  4004e6:	b8 00 00 00 00       	mov    $0x0,%eax
  4004eb:	5d                   	pop    %rbp
  4004ec:	c3                   	retq   

00000000004004ed <_Z4funcv>:
  4004ed:	55                   	push   %rbp
  4004ee:	48 89 e5             	mov    %rsp,%rbp
  4004f1:	8b 05 41 0b 20 00    	mov    0x200b41(%rip),%eax        # 601038 <__TMC_END__>
  4004f7:	01 c0                	add    %eax,%eax
  4004f9:	89 05 39 0b 20 00    	mov    %eax,0x200b39(%rip)        # 601038 <__TMC_END__>
  4004ff:	8b 05 2b 0b 20 00    	mov    0x200b2b(%rip),%eax        # 601030 <g_val_2>
  400505:	01 c0                	add    %eax,%eax
  400507:	89 05 23 0b 20 00    	mov    %eax,0x200b23(%rip)        # 601030 <g_val_2>
  40050d:	90                   	nop
  40050e:	5d                   	pop    %rbp
  40050f:	c3                   	retq
  
# 省略...

从上面可以看出,链接器需要将符号引用的值修改为 0x4004ed,正是_Z4funcv符号定义的运行时地址。

3)重定位类型 R_X86_64_32 如何计算符号引用的值

重定位类型R_X86_64_32表示重定位符号引用的结果是一个 32 位的绝对地址。与重定位类型R_X86_64_64的验证过程类似,此处不再赘述。

重定位符号引用

重定位符号引用的过程为遍历所有的重定位条目,每个重定位条目根据其重定位类型修改其目标 section 中此符号所有的引用。

如果你觉得本文对你有所帮助,欢迎关注公众号,支持一下!

以上是关于计算机系统篇之链接:静态链接(下)——重定位的主要内容,如果未能解决你的问题,请参考以下文章

计算机系统篇之链接:目标文件

计算机系统篇之链接:目标文件

计算机系统篇之链接:静态链接(上)

计算机系统篇之链接:静态链接(上)

计算机系统篇之链接:静态链接(中)——符号解析

学习笔记