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文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定。
延迟绑定:
所谓延迟绑定,就是当函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别有利于一些引用了大量函数的程序。
存在一个puts
函数,在PLT表项为puts@plt
,在GOT 表项为puts@got
,第一次执行的时候:
- 进入puts@plt
- 进入GOT表重对应的位置,此时的GOT保存到是
pushl n
的地址,实际上是顺序执行的下一步,如图中标号3的线 - 执行
pushl n
n 为puts函数在GOT表中的位置,向堆栈中压入这个偏移量的主要作用就是为了找到puts函数的符号名以及puts函数地址在GOT表项中所占的位置,以便在函数定位完成后将函数的实际地址写到这个位置。 - 跳到PLT[0]的位置,这步将
push GOT[1]
,即共享库链表地址入栈 - 顺序执行
jmp GOT[2]
,保存的是_dl_runtime_resolve
函数的入口 - 执行
_dl_runtime_resolve
该函数会找到puts函数的实际加载地址,并把它写到GOT表中,返回时就会进入puts函数内执行。
函数后续执行就会直接跳转的GOT表puts函数对应位置,该位置保存着puts函数的地址,直接进入puts函数。
- GOT[1]保存的是一个地址,指向已经加载的共享库的链表地址(加载的共享库会形成一个链表);
- GOT[2]保存的是一个函数的地址,定义如下:GOT[2] =
&_dl_runtime_resolve
,这个函数的主要作用就是找到某个符号的地址,并把它写到与此符号相关的GOT项中
坑:
阅读相关源码,为什么2 3 4 是这种操作