GOT 表和 PLT 表

“GOT 表”即全局偏移表(Global Offset Table),“PLT 表”即过程链接表(Procedure Linkage Table),它们在动态链接过程中起着重要的作用。

GOT(Global Offset Table,全局偏移量表)表中存储的是函数的真正地址以及本模块要引用的全局变量的地址。在程序加载时,动态链接器会重定位 GOT 表中的函数地址。具体来说,GOT 表中的一些特殊位置具有特定的作用:

  • GOT(0):包含.dynamic段的地址,.dynamic段包含了动态链接器用来绑定过程地址的信息,如符号的位置和重定位信息。
  • GOT(1):包含动态链接器的标识(link_map 的地址)。
  • GOT(2):包含动态链接器的延迟绑定代码的入口点。
  • GOT 的其他表目:存储本模块要引用的一个全局变量或函数的地址。

PLT(Procedure Linkage Table,过程链接表)表中存储的是 GOT 表中的相对位置。PLT 表中的每一项对应一个函数,其数据是一段由几条指令组成的代码。例如,对于某个函数的 PLT 表项,其中的指令第一条是跳转到相应的 GOT 存储的地址值,第二条指令把函数相应的标识(id)压入栈中,第三条指令跳转到 PLT(0)中调用动态链接器解析函数地址,并把函数真正地址存入相应的 GOT 表目中。

PLT 表与 GOT 表通过延时绑定机制协同工作,即将过程地址的绑定推迟到第一次调用该函数的时候。在首次调用函数时,会通过 PLT 表中的指令跳转到 GOT 表,触发一系列操作来查找函数的真实地址并填充到 GOT 表中。之后再调用该函数时,就可以直接从 GOT 表中获取到真实地址并进行跳转,从而提高程序的执行效率。

链接是最后一个环节

高级编程语言(如 C、C++)预处理 编译 汇编 链接-CSDN博客

GOT 表用于存储全局数据和外部函数的引用地址

当程序引用外部的数据或函数时,通过 GOT 表来获取相关数据符号的实际地址。在动态链接的情

况下,程序加载时并不将链接库中所有函数都一起加载进来,而是在执行时按需加载。这样可以提

高程序运行的效率并减少内存占用

       由于现代操作系统通常不允许修改代码段,只能修改数据段,GOT 表便应运而生。

PLT 表是一个过程链接表,它包含了一系列的代码片段当程序调用一个外部函数时,实际上会跳

转到 PLT 表中相应的条目,然后再通过 GOT 表找到函数的实际地址并进行跳转。 、

例如,在一个程序中调用某个外部函数时,会先通过 PLT 表跳转到对应的 GOT 表条目,此时

GOT 表中的地址可能尚未被填充(初始可能为指向 PLT 表中特定位置的指针或其他预设值)。然

后通过特定的机制(如运行时重定位),将函数的真实地址填充到 GOT 表中相应的位置。当再次

调用该函数时,就可以直接从 GOT 表中获取已填充的真实地址,从而快速跳转到函数的实际位置

执行。

这种通过 GOT 表和 PLT 表的机制实现了动态链接的延迟绑定(Lazy Binding),即在函数第一次

被调用时才进行地址的解析和绑定,提高了程序的启动速度,并减少了不必要的开销。

.got.plt 通常被认为是 GOT 表的一个部分,它专门用于存储导入函数的地址。

问题:plt表是不是相当于got表的缓存?

PLT 表可以看作是对 GOT 表的一种间接访问方式,而不能简单地说它是 GOT 表的缓存。

GOT(Global Offset Table,全局偏移表)用于存储全局变量和函数的实际地址。在动态链接的情况下,程序在运行时需要确定这些地址。

PLT(Procedure Linkage Table,过程链接表)的每一项都是一小段代码。当程序调用一个外部函数时,实际上是跳转到相应的 PLT 项,然后通过 GOT 表来获取函数的真实地址。 在首次调用函数时,GOT 表中对应的项可能还没有被填充函数的真实地址。

此时,PLT 表中的代码会执行一些额外的操作,例如通过动态链接器来查找函数的真实地址并填充到 GOT 表中。后续再调用该函数时,就可以直接从 GOT 表中获取已经填充好的真实地址,从而提高调用效率。

例如,当调用一个外部函数 `func` 时,流程大致如下:

1. 跳转到 `func` 对应的 PLT 项。

2. PLT 项中的代码首先会跳转到 GOT 表中对应的项。

3. 如果这是首次调用,GOT 表中该项可能存储的是 PLT 项中的下一条指令地址(一个间接跳转),然后通过这个间接跳转执行一些额外的操作,如调用动态链接器来查找 `func` 的真实地址并填充到 GOT 表中。

4. 下次再调用 `func` 时,从 GOT 表中获取的就是 `func` 的真实地址,直接跳转到该地址执行函数。

问题:为什么3中是下一条指令?

在首次调用外部函数时,GOT 表中存储的是 PLT 项中的下一条指令地址,这样设计主要是为了实现延迟绑定(lazy binding)。

当程序首次调用外部函数时,它并不知道该函数在共享库中的真实地址。通过将 GOT 表中该项初始化为 PLT 项中的下一条指令地址,可以触发一系列额外的操作来查找函数的真实地址。 具体来说,程序跳转到 PLT 表中对应条目后,会按照以下步骤进行:

1. 首先检查 GOT 表中对应项,如果发现存储的是 PLT 项中的下一条指令地址,就会执行这个间接跳转,回到 PLT 中继续执行。

2. 接下来会执行一些特定的指令,例如压栈操作,将一些必要的参数准备好。

3. 然后通过调用动态链接器来查找该函数的真实地址。

4. 找到真实地址后,将其填充到 GOT 表中对应项。 这样,当下次再调用该函数时,从 GOT 表中获取的就是函数的真实地址,直接跳转到该地址执行函数,而无需再次进行查找和重定位的过程,提高了程序的执行效率。

这种设计的好处是实现了延迟绑定,即在首次调用时才进行地址查找和绑定的操作避免了在程序启动时就对所有可能用到的外部函数进行地址解析,节省了时间和资源。因为在一个程序中,并非所有的外部函数都会在每次运行时都被调用到。 同时,由于代码段通常是只读的,不能直接修改,而 GOT 表位于数据段是可写的,这样就可以在数据段中进行地址的存储和修改,而不会违反代码段的访问权限限制。此外,通过使用 PLT 和 GOT 表的机制,还可以实现多个进程共享同一个共享对象的代码段,同时保持数据段的独立副本,节省了内存空间。

.section.plt
func@plt:
    jmp *func@got
    pushl $0
    jmp.plt0

.section.got
func@got:
   .long 0

.section.text
.plt0:
    # 调用动态链接器来查找 func 的真实地址
    # 假设这里有获取地址并填充到 GOT 表的代码
    movl $func_real_address, func@got
    jmp *func@got

func_real_address:
    # 这里是 func 函数的实际实现
    movl $10, %eax
    ret

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值