linux @plt在这里是什么意思?

yhuiod9q  于 2022-11-02  发布在  Linux
关注(0)|答案(2)|浏览(171)
0x00000000004004b6 <main+30>:   callq  0x400398 <printf@plt>

有人知道吗?

更新

为什么两个disas printf给予不同的结果?

(gdb) disas printf
Dump of assembler code for function printf@plt:
0x0000000000400398 <printf@plt+0>:  jmpq   *0x2004c2(%rip)        # 0x600860 <_GLOBAL_OFFSET_TABLE_+24>
0x000000000040039e <printf@plt+6>:  pushq  $0x0
0x00000000004003a3 <printf@plt+11>: jmpq   0x400388

(gdb) disas printf
Dump of assembler code for function printf:
0x00000037aa44d360 <printf+0>:  sub    $0xd8,%rsp
0x00000037aa44d367 <printf+7>:  mov    %rdx,0x30(%rsp)
0x00000037aa44d36c <printf+12>: movzbl %al,%edx
0x00000037aa44d36f <printf+15>: mov    %rsi,0x28(%rsp)
0x00000037aa44d374 <printf+20>: lea    0x0(,%rdx,4),%rax
0x00000037aa44d37c <printf+28>: lea    0x3f(%rip),%rdx        # 0x37aa44d3c2 <printf+98>
xtfmy6hx

xtfmy6hx1#

这是一种无需为每个进程维护单独代码副本就可以获得代码修复(根据代码在虚拟内存中的位置调整地址,不同进程之间的地址可能不同)的方法。PLT,或***过程链接表***,是使动态加载和链接更易于使用的结构之一(另一个是GOT,或***全局偏移表***)。
请参考下图,其中显示了您的调用代码和库代码(您调用的代码)Map到两个不同进程(AB)中的不同虚拟地址。真实的内存中的每段代码只有 * 一个 * 副本,每个进程中的不同虚拟地址Map到该实际地址):

Process A
    Addresses (virtual):
        0x1234                      0x8888
        +-------------+ +---------+ +---------+
        |             | | Private | |         |
        |             | | PLT/GOT | |         |
        | Shared      | +---------+ | Shared  |
    ===== application =============== library =====
        | code        | +---------+ | code    |
        |             | | Private | |         |
        |             | | PLT/GOT | |         |
        +-------------+ +---------+ +---------+
        0x2020                      0x6666
Process B

当共享库被引入地址空间时,在进程专用(私有)PLT和/或GOT中构建条目,这些条目将在第一次使用时执行一些修复以使事情更快。随后的使用将绕过修复,因为不再需要它。
过程大致是这样的。
printf@plt实际上是一个小的stub,它(最终)调用真实的的printf函数,并在此过程中进行修改以使后续调用更快。

  • 真实的 * printf函数被Map到给定进程(虚拟地址空间)中的任意位置,尝试调用它的代码也是如此。

因此,为了允许调用代码(左上方)和被调用代码(右上方)的正确代码共享,您不能直接对调用代码应用任何修复,因为这将“破坏”它在其他进程中的工作方式(如果它在每个进程中Map到 * 相同 * 的位置,这并不重要,但这有一点限制,特别是如果其他东西已经被Map到那里)。
因此,PLT是一个较小的 * 特定于进程的 * 区域,位于运行时可靠计算的地址,* 不 * 在进程之间共享,因此任何给定的进程都可以自由地更改它,而不会对其他进程产生负面影响。
让我们更详细地了解整个过程。上图没有显示PLT/GOT的地址,因为它可以通过相对于当前程序计数器的位置来找到。这可以通过相对于PC的查找来证明:

<printf@plt+0>: jmpq  *0x2004c2(%rip)  ; 0x600860 <_GOT_+24>

通过使用被调用库中的位置无关代码沿着PLT/GOT,对函数printf@plt的 * 第一次 * 调用(因此 * 在 * PLT中)是一个多阶段操作,其中发生以下操作:

  • 它调用GOT版本(通过指针),GOT版本 * 最初 * 指向PLT中的一些设置代码。
  • 该设置代码加载相关的共享库(如果尚未完成),然后 * 修改 * GOT指针,以便随后的调用直接转到真实的的printf(在进程特定的虚拟地址),而不是PLT设置代码。
  • 然后,它会在该位址呼叫载入的printf程式码。

在 * 后续 * 调用中,由于GOT指针已被修改,因此简化了多阶段方法:

  • 它调用GOT版本(通过指针),后者现在指向 * 真实的 * printf

可以在here中找到一篇很好的文章,它详细介绍了glibc在运行时是如何加载的。

vojdkbi0

vojdkbi02#

不确定,但可能你看到的是有意义的。第一次运行disas命令时,printf还没有被调用,所以它没有被解析。一旦你的程序第一次调用printf方法,GOT就被更新了,现在printf被解析了,GOT指向了真实的的函数。因此,下一次调用disas命令时,显示的是真正的printf程序集。

相关问题