操作系统-Linux-浅析GOT与PLT

// 如有谬误,还请麻烦各位读者斧正。

 

--加载位置无关代码--

 

位置无关代码(PIC):

  可以加载,无需重定位的代码。

 

在内存中加载目标模块时,数据段与代码段的距离总是保持不变。

(目标模块:你要加载进内存的模块)

与绝对内存地址无关 (不然程序就难以在代码段中引用数据段的数据)

利用这一点,

编译器会在目标模块的数据段的开始创建一个表,用于存放(被加载的)目标模块需要的全局数据目标(函数或变量)。

这个表叫全局偏移量表(GOT)

 

GOT中每个被目标模块引用的全局数据目标都有一个8个字节的条目(全局数据目标的绝对地址)。

编译器会为每个条目生成重定位记录,使得它(指条目)为正确的绝对地址。

 

我们写的代码会通过GOT间接访问全局数据目标。

这样的话全局数据目标即使在其他模块也能被正确引用。

 

示例:尝试加载某个共享模块中的某个函数

链接完成后的情况:

其他模块也可以通过访问GOT[4] 使用 全局变量 addr

 

 

 

--调用共享库的函数--

 

程序调用共享库定义的函数时,编译器无法预测函数的运行地址

(共享库加载地址无法被预测)

 

解决问题的方法:

  1.为这个引用生成一条重定位记录,动态链接器在程序加载时解析。

  2.延迟绑定:第一次调用函数时再绑定。

 

GNU编译系统:"我们使用延迟绑定"

 

为什么延迟绑定:

  一个库装了一堆函数,程序可能就用两个……

  延迟绑定可以避免动态链接器加载成千上万用不到的重定位。

  第一次调用开销不小,但是之后的每次调用都只会花费一条指令和一个间接的内存引用。

 

延迟绑定通过两个数据结构完成:

  GOT 与 过程链接表(PLT) //听名字就知道这个表是干嘛的

 

PLT:代码段的一部分

PLT是一个数组,其中每个条目占16字节。

 

每个目标模块都有一个GOT与PLT

每个被调用的函数都有一个PLT 条目

 

实例: 看看第一次 调用 qwq_function 会发生什么

 

代码调用函数其实是在

call 函数对应的PLT表的位置。

# 对应图中的第一步:程序调用进入PLT[3] (qwq_function 对应的PLT条目)

 

开始执行PLT的代码:

  (GOT[5] 是 qwq_function 对应的GOT条目)

  跳转到 *GOT[5] (GOT[5] 中存储的地址)

  第一次调用时 *GOT[5] 的值为 qwq_function 对应的PLT条目中的第二条指令。

  # 这个间接跳转在 第一次调用之后的每次调用 中都有着巨大作用

  函数id入栈

  调用动态链接器从共享库中寻找函数 qwq_function

  动态链接器用函数id与GOT[1] 确认 qwq_function 的运行时位置

  然后把这个位置(qwq_function 的运行时位置)写回 GOT[5]

  动态链接器把控制权移交给 qwq_function

  # 执行 qwq_function

 

第二次call 的时候就是:

  

 

 

 两步搞定!

 

IDA中查看PLT与GOT:

使用Ctrl+S

 

 

 

 

 

 

posted @ 2019-11-25 18:17  树行云  阅读(1244)  评论(0编辑  收藏  举报