GOT & PLT 易于理解的个人笔记

为什么我们用动态链接和GOT表

  1. 我们知道静态链接就没那么多事,直接把全部要用的函数都绑定在一起,各个变量和函数之间的偏移量当然能算出来。
  2. 但是这也恰恰是静态链接的缺点,相同的代码段反复调用真是太臃肿了!
  3. 于是我们决定把常用的库单独拿出来给大家用,我们还知道,.text是不可修改的,也就是运行的时候可不能再把引用的地址像链接器一样"填"在预留的"坑"里,所以我们还是得装模作样的告诉程序应该跳转到哪里,比如我要call puts,但是我确实不知道puts在哪里(因为动态链接发生在程序运行时),我也留个坑,叫做puts@got。
  4. got表记录全局偏移,也就是真的存放的位置,放在.data节中所以可以编辑。你只需要知道前三个表项分别是:got[0]保存了.dynamic段的地址,这其中描述了本模块动态链接的相关信息;got[1]保存了该模块的id; got【2】就是真正帮你找你要的函数在哪个地址的,比如他找到了puts在地址0x12345678处,它就填在got[3]。那么以后看到puts@got就直接跳到got[3]的地址(这个id的分配是链接器规划好的)。

为什么我们要延迟绑定和plt表

看起来已经完全解决问题了呀!通过got解决了.text不能编辑的问题,还可以"位置无关",多棒!

但是请你想象一下,当你执行一个程序,他引用了100000个函数,现在他正在靠got[2]的查找函数一个一个查找......

简直是灾难!很明显正常人都能想到,为什么不"用到的时候"再找呢?你一开头找那么久干什么?万一有个if-else,其中一个分支引用了99%的外部函数,结果我根本就没进入这个分支......

好!现在让我们看下plt的逻辑:

  1. 当我call puts@plt的时候,我跑到plt代码段执行这些动作:跳到got表,如果第一次调用没人帮我找,那么got表此时的地址就是plt刚刚跳的位置,又回到了plt!
  2. 那么就继续吧!先压入我要找的函数id,再jmp到plt的公共代码段。
  3. 公共代码段对于所有plt操作都是一样的,那就是呼唤我们熟悉的got[2]来帮我们找函数。
  4. got[2]找好以后,它会填入got表中,那么下次再执行步骤1的时候,got表此时的位置就是puts的位置!
  5. 现在又要用到puts了!我call puts@plt,跳到puts@plt代码段,然后又跳到got表,然后发现居然有人帮我们找好了!

以上内容我相信能帮你轻松的了解plt got的机制,一些专业名词你可以自己搜索。原理懂了看起来方便很多的!