PLT&GOT

Linux动态链接

一个比喻:发布的文章里复制别人的文字,属于静态链接。给出别人文章的链接,属于动态链接。
Linux下的动态链接是通过PLT&GOT 来实现的
动态链接每个函数需要两个东西:
1.用来存放外部函数地址的数据段
2.用来获取数据段记录的外部函数地址的代码
对应两个表。存放外部的函数地址的数据表为GOT表;存放额外代码的表为PLT表

动态链接的好处

  1. 节省内存和磁盘空间
    假设磁盘中两个程序都包含lib.o模块,静态链接情况下,两个可执行文件都会包含该模块,造成了磁盘空间的浪费。而两个程序运行时,内存空间也会包含这两个相同的模块,造成内存的浪费。动态链接情况下,运行程序时,如果系统发现lib.o模块已经存在,就会将模块和程序链接起来,节省内存。
  2. 程序更新更简单
    静态链接时需要更新lib.o文件,第三方要把新版本的lib.o和程序重新链接好后发送给用户。
    动态链接时需要更新lib.o文件时,只需要覆盖原有的文件,在程序下一次运行时,会把新版本的目标文件装载到内存中并链接起来,就完成了更新。
  3. 程序扩展性和兼容性更强
    动态链接的程序在运行时可以动态地选择加载各种模块,也就是我们常常使用的插件。软件的开发商开发某个产品时会按照一定的规则制定好程序的接口,其他开发者就可以通过这种接口来编写符合要求的动态链接文件,实现程序功能的扩展。增强兼容性是表现在动态链接的程序对不同平台的依赖差异性降低,比如对某个函数的实现机制不同,如果是静态链接的程序会为不同平台发布不同的版本,而在动态链接的情况下,只要不同的平台都能提供一个动态链接库包含该函数且接口相同,就只需用一个版本了。

概述

动态链接的程序在运行时会根据自己所依赖的动态链接库,通过动态链接器将他们加载至内存中,并在此时将他们链接成一个完整的程序。

  • Linux 系统中,ELF 动态链接文件被称为动态共享对象(Dynamic Shared Objects),简称共享对象 ,一般都是以 “.so” 为扩展名的文件
  • 在 windows 系统中就是常常软件报错缺少xxx.dll 文件。

重定位

链接时重定位

链接阶段是将一个或多个中间文件(.o文件)通过链接器将它们链接成一个可执行文件,主要做的事情有: 1.对各个中间文件的同名section进行合并 2.对代码段,数据段等进行地址分配 3.进行链接时重定位
两种情况: 如果是在其他中间文件中已经定义了的函数,链接阶段可以直接重定位到函数地址 如果是在动态库中定义了的函数,链接阶段无法直接重定位到函数地址,只能生成额外的小片段代码,也就是PLT表,然后重定位到该代码片段

运行时重定位

运行后加载动态库,把动态库中的相应函数地址填入GOT表,由于PLT表是跳转到GOT表的,这就构成了运行时重定位

延迟重定位

只有动态库函数在被调用时,才会进行地址解析和重定位工作,这时候动态库函数的地址才会被写入到GOT表项中

.plt

Procedure Linkage Table——程序链接表
Linux ELF文件中用于延迟绑定的表,它有两个功能:
1.直接在.got.plt 节中拿到地址,并跳转
2.当 .got.plt 没有所需地址的时,触发「链接器」去找到所需地址,并填充到.got.plt中,然后跳转

.got

Global Offset Table——全局偏移表
Linux ELF文件中用于定位全局变量和函数的一个表,「链接器」为「外部符号」填充的实际偏移表,连接器在执行链接时实际上要填充的部分

.got.plt

GOT 专门为 PLT 专门准备的节。.got.plt 中的值是 GOT 的一部分。它包含上述 PLT 表所需地址(已经找到的和需要去触发的)。相当于.plt的GOT全局偏移表。其内容有两种情况:
1.若在之前查找过该符号,内容为外部函数的具体地址
2.若为查找过,内容为跳转回.plt的代码,并执行查找

.plt.got

网上都说不知道,我也还不知道……

延迟绑定 lazy Binding

可执行文件中保存的是PLT表的地址,对应PLT表指向的是GOT的地址,GOT指向的是glibc的地址。想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制

因为动态链接的程序是在运行时需要对全局和静态数据访问进行GOT定位,然后间接寻址。同样,对于模块间的调用也需要GOT定位,再才间接跳转,这么做势必会影响到程序的运行速度。而且程序在运行时很大一部分函数都可能用不到,于是ELF采用了当函数第一次使用时才进行绑定的思想,也就是我们所说的延迟绑定。ELF实现延迟绑定是通过 PLT ,原先 GOT 中存放着全局变量和函数调用,现在把他拆成另个部分.got 和 .got.plt,用.got 存放着全局变量引用,用.got.plt 存放着函数引用。

GDB调试理解PLT&GOT

未执行前,
objdump -d xxxx
查看表项内容


看到,puts@plt,jmp *0x804a018

未执行前,查看该地址存放的是:

即执行jmp *0x8048446,即为jmp的下一条指令:
压入一个参数
然后jmp *0x8048420,即跳转到第一个函数(第一个函数自动生成和第二个相同的名字),
压入参数,jmp *0x804a008。查看该地址,未执行时值为0

开始执行,在puts后下一个断点
再次查看上述两个地址的值
这时,执行puts便会
直接跳转到0xf7e62cb0,正是puts函数的真实地址
而0xf7fee000是函数_dl_runtime_resolve的地址



总结

当调用一个没有调用过的函数时,
流程:xxx@plt -> xxx@got -> xxx@plt -> 公共@plt ->_dl_runtime_resolve
公共表项调用_d\l_runtime_resolve后,根据原xxx@plt每次jmp前push的参数(即为函数的ID),_dl_runtime_resolve去寻找对应函数的地址
然后,_dl_runtime_resolve对动态函数进行地址解析和重定位后,把动态函数的真实地址写入GOT表项中,执行函数并返回

第二次调用时,到xxx@plt表后jmp到对应got表,由于函数真实地址已经被写入GOT表,所以直接jmp到函数真实地址执行调用

在 i386 架构下,除了每个函数占用一个 GOT 表项外,GOT 表项还保留了3个公共表项,也即 got 的前3项,分别保存:
got [0]: 本 ELF 动态段 (.dynamic 段)的装载地址
got [1]:本 ELF 的 link_map 数据结构描述符地址
got [2]:_dl_runtime_resolve 函数的地址

动态链接器在加载完 ELF之后,都会将这3地址写到 GOT 表的前3项

最后附上流程图(源自网络)


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇