ELF中的GOT PLT REL

ELF中的GOT PLT REL

如果在编译时开启PIC(位置无关代码)和PIE(位置无关可执行程序),那么在链接器加载程序的时候,可以将ELF加载到任意地址空间.动态加载的实现就与GOT 和PLT表有关

  • gcc 编译:
    • got表在.got节区
    • plt表在.plt节区
  • clang编译:
    • got表在.got节区和.got.plt节区 :会加载到内存中,且可写
    • plt表在.plt节区: 会加载到内存中,且包含要执行的机器指令

RLE表 - 程序加载进内存时,通过自己的rel表,告诉链接器,哪些位置需要重定位

GOT表 (Global Offset Table,全局偏移表) - 存放链接器找到的函数/变量的地址

PLT表(Procedure Linkage Table,过程链接表 ) - 由代码片段组成的,每个代码片段都跳转到GOT表中的一个具体的函数调用,用于将地址无关函数转移到绝对地址

在android arm中,是装载时重定位,链接器将模块加载进内存的时候,遍历rel表,得知哪些位置需要重定向,将重定向之后的地址填入got表

当程序执行时,需要某一个引用变量,或者需要调用一个函数的时候,就会访问got表,查找到对应的地址

PLT表是Linux ELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定。

延迟绑定:

所谓延迟绑定,就是当函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别有利于一些引用了大量函数的程序。

1586185898823

存在一个puts函数,在PLT表项为puts@plt,在GOT 表项为puts@got,第一次执行的时候:

  1. 进入puts@plt
  2. 进入GOT表重对应的位置,此时的GOT保存到是 pushl n的地址,实际上是顺序执行的下一步,如图中标号3的线
  3. 执行pushl n n 为puts函数在GOT表中的位置,向堆栈中压入这个偏移量的主要作用就是为了找到puts函数的符号名以及puts函数地址在GOT表项中所占的位置,以便在函数定位完成后将函数的实际地址写到这个位置。
  4. 跳到PLT[0]的位置,这步将push GOT[1],即共享库链表地址入栈
  5. 顺序执行jmp GOT[2],保存的是_dl_runtime_resolve函数的入口
  6. 执行_dl_runtime_resolve 该函数会找到puts函数的实际加载地址,并把它写到GOT表中,返回时就会进入puts函数内执行。

函数后续执行就会直接跳转的GOT表puts函数对应位置,该位置保存着puts函数的地址,直接进入puts函数。

  • GOT[1]保存的是一个地址,指向已经加载的共享库的链表地址(加载的共享库会形成一个链表);
  • GOT[2]保存的是一个函数的地址,定义如下:GOT[2] = &_dl_runtime_resolve,这个函数的主要作用就是找到某个符号的地址,并把它写到与此符号相关的GOT项中

坑:

阅读相关源码,为什么2 3 4 是这种操作

参考

ELF文件符号动态解析的过程