【程序员的自我修养05】符号修正的功臣——重定位表

2023-12-13 18:56:12

绪论

大家好,欢迎来到【程序员的自我修养】专栏。正如其专栏名,本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度,我会尽自己最大的努力,争取深入浅出。若你希望与一群志同道合的朋友一起学习,也希望加入到我们的学习群中。文末有加入方式。

简介

在上篇文章【程序员的自我修养04】目标文件生成可执行文件过程文章中,我们了解了目标文件如何生成可执行文件的两个步骤:空间与地址分配符号解析和重定位建议读者看一遍上文,方便理解本章内容

在上篇文章的最后,并提出了一个思考题:链接器是如何知道哪些符号需要被修正的呢?

从上文中,我们知道对外部引用的符号或变量,目标文件暂时会设置成0,待符号分析完成后,可执行程序已经确认其真实的虚拟地址,再进行修正。我们是否可以根据变量值是否为0来进行修改正呢?这样的思路,对于函数引用好像是可以的,毕竟没有哪个函数的地址是0吧。但是变量的好像就不行了,因为变量存在初始值为0的情况。这便是本章的核心:重定位表

重定位表

重定位表是ELF文件中专门用来保存需要重定位信息的段,它的作用是指导链接器如何修改相应的段内容。对于每一个要被重定位的ELF段都有一个对应的重定位表。比如代码段.text如果有要被重定义的地方,那么就会有一个相对应的段称为.rel.text。如上文案例中a.o的段表信息。

yihua@ubuntu:~/test/static-linker$ readelf -S a.o
There are 12 section headers, starting at offset 0x2c8:

Section Headers:
  [Nr] Name              Type             Address           Offset      Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000    0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040    0000000000000029  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000220    0000000000000030  0000000000000018   I       9     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000069    0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000069    0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .comment          PROGBITS         0000000000000000  00000069    000000000000002a  0000000000000001  MS       0     0     1
  [ 6] .note.GNU-stack   PROGBITS         0000000000000000  00000093    0000000000000000  0000000000000000           0     0     1
  [ 7] .eh_frame         PROGBITS         0000000000000000  00000098    0000000000000038  0000000000000000   A       0     0     8
  [ 8] .rela.eh_frame    RELA             0000000000000000  00000250    0000000000000018  0000000000000018   I       9     7     8
  [ 9] .symtab           SYMTAB           0000000000000000  000000d0    0000000000000120  0000000000000018          10     8     8
  [10] .strtab           STRTAB           0000000000000000  000001f0    000000000000002c  0000000000000000           0     0     1
  [11] .shstrtab         STRTAB           0000000000000000  00000268    0000000000000059  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
yihua@ubuntu:~/test/static-linker$ readelf -S b.o
There are 11 section headers, starting at offset 0x270:

如上,因为a.o中引用了外部函数swap,故在链接时需要对代码段进行重定位,则新增描述段.rel.text。同理,还可能存在.rel.data重定位段。我们可以通过如下命令查看文件中重定位表的信息。

yihua@ubuntu:~/test/static-linker$ objdump -r a.o

a.o:     file format elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000016 R_X86_64_PC32     shared-0x0000000000000004
000000000000001e R_X86_64_PLT32    swap-0x0000000000000004


RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text

可知,a.o 中有两段重定位符号表,分别用于修改.text段和.eh_frame段。.eh_frame段是一个特殊的段,它与异常处理和调试信息相关,我们暂时不关注仅关注.text

分析:

  • 其中 RELOCATION RECORDS FOR [.text]中的[.text]代表是代码段的重定位表。
  • OFFSET列:表示该段中需要被调整的位置。结合a.o的反汇编可知,0x00000000000000160x000000000000001e分别是代码段中,对外部变量shared和外部函数swap的引用。
yihua@ubuntu:~/test/static-linker$ objdump -d a.o

a.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   c7 45 fc 64 00 00 00    movl   $0x64,-0x4(%rbp)
   f:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  13:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 1a <main+0x1a>
  1a:   48 89 c7                mov    %rax,%rdi
  1d:   e8 00 00 00 00          callq  22 <main+0x22>
  22:   b8 00 00 00 00          mov    $0x0,%eax
  27:   c9                      leaveq
  28:   c3                      retq
  • TYPE列:表示重定位符号的类型。不同的平台,其类型不同,而类型决定了修复符号的方式,如a.o重定位符号表中。

R_X86_64_PC32:重定位公式为S+A-P

  • S:符号链接后的最终的虚拟地址。
  • A:加数,也称修正值。
  • P:需要被重定位处在链接后最终的虚拟地址。

R_X86_64_PLT32:重定位公式为L+A-P

  • L:符号的实际虚拟地址或在PLT表中的地址。当函数符号定义在目标文件中时,L则为符号的实际虚拟地址;当函数符号定义动态库中时,L则为PLT表中的地址。很明显,swap属于前者。在后续章节中,我们再深入讨论第二种情况

  • VALUE列:表示符号与修正值。比如shared-0x0000000000000004,表示对应符号表中shared符号,修正值为-0x0000000000000004

符号重定位流程

通过对重定位表的了解,我们可以通过案例,尝试推论符号重定位流程。代码示例如下:

a.o的需要重定位的代码段信息:

yihua@ubuntu:~/test/static-linker$ objdump -d a.o

a.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   c7 45 fc 64 00 00 00    movl   $0x64,-0x4(%rbp)
   f:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  13:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 1a <main+0x1a>
  1a:   48 89 c7                mov    %rax,%rdi
  1d:   e8 00 00 00 00          callq  22 <main+0x22>
  22:   b8 00 00 00 00          mov    $0x0,%eax
  27:   c9                      leaveq
  28:   c3                      retq

a.o的重定位表信息:

yihua@ubuntu:~/test/static-linker$ readelf -r a.o

Relocation section '.rela.text' at offset 0x220 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000016  000900000002 R_X86_64_PC32     0000000000000000 shared - 4
00000000001e  000b00000004 R_X86_64_PLT32    0000000000000000 swap - 4

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

ab的符号表信息:

yihua@ubuntu:~/test/static-linker$ readelf -s ab

Symbol table '.symtab' contains 16 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000004000e8     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000400160     0 SECTION LOCAL  DEFAULT    2
     3: 0000000000601000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000601018     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.c
     7: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS b.c
     8: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
     9: 0000000000601000     0 OBJECT  LOCAL  DEFAULT    3 _GLOBAL_OFFSET_TABLE_
    10: 0000000000400111    75 FUNC    GLOBAL DEFAULT    1 swap
    11: 0000000000601018     4 OBJECT  GLOBAL DEFAULT    4 shared
    12: 000000000060101c     0 NOTYPE  GLOBAL DEFAULT    4 __bss_start
    13: 00000000004000e8    41 FUNC    GLOBAL DEFAULT    1 main
    14: 000000000060101c     0 NOTYPE  GLOBAL DEFAULT    4 _edata
    15: 0000000000601020     0 NOTYPE  GLOBAL DEFAULT    4 _end

a.o代码段在ab中最终加载的虚拟地址。

yihua@ubuntu:~/test/static-linker$ objdump -d ab

ab:     file format elf64-x86-64


Disassembly of section .text:

00000000004000e8 <main>:
  4000e8:       55                      push   %rbp
  4000e9:       48 89 e5                mov    %rsp,%rbp
  4000ec:       48 83 ec 10             sub    $0x10,%rsp
  4000f0:       c7 45 fc 64 00 00 00    movl   $0x64,-0x4(%rbp)
  4000f7:       48 8d 45 fc             lea    -0x4(%rbp),%rax
  4000fb:       48 8d 35 16 0f 20 00    lea    0x200f16(%rip),%rsi        # 601018 <shared>
  400102:       48 89 c7                mov    %rax,%rdi
  400105:       e8 07 00 00 00          callq  400111 <swap>
  40010a:       b8 00 00 00 00          mov    $0x0,%eax
  40010f:       c9                      leaveq
  400110:       c3                      retq

分析:

  1. 通过a.o的重定位表可知,有两个需要重定位的项,分别在.text段offset 0x16 、0x1e。并且代码段对应位置为0。
  2. 通过a.o的重定位表可知,shared变量的修正值为-4。在ab符号表中,shared的虚拟地址为0x0000000000601018 。重定位处shared在链接后最终的虚拟地址为0x00000000004000e8+0x16 。根据重定位公式为 S+A-P,得出修改后的地址为0x0000000000601018+(-4)-(0x00000000004000e8+0x16) = 0x200f16。与实际相符。
  3. 同理,因为外部函数swap定义在目标文件中。因此定位表达式L+A-P=S+A-P=0x0000000000400111+(-4)-(4000e8+1e)= 7。与实际相符。
  4. 至此,是不是觉得很神奇,内心很欢喜~~~

结合上一章内容,我们大致可以总结可重定位文件链接成可执行文件的流程如下:

  1. 空间分配。将各个.o文件的相似段进行整合,输出目标文件ab
  2. 地址分配。对ab需要加载到内存中的段分配虚拟内存。比如.textdata段。
  3. 符号解析。将ab文件中全局符号符号表进行完善。比如根据步骤二,计算出符号的对应虚拟地址。
  4. 符号重定位。链接器根据各.o文件中的重定位表结合ab文件中的全局符号表信息,计算出最终的虚拟地址,并修正。
    在这里插入图片描述

总结

通过本章,再结合上篇【程序员的自我修养04】目标文件生成可执行文件过程 基本已经掌握了 目标文件静态链接成可执行程序的过程。后续我会从动态链接可执行程序角度,进行剖析。有兴趣的朋友可以持续关注哦。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途。
在这里插入图片描述

文章来源:https://blog.csdn.net/xieyihua1994/article/details/134978043
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。