@plt在这里意味着什么?

时间:2011-03-29 07:34:37

标签: assembly

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>

2 个答案:

答案 0 :(得分:104)

这是一种获取代码修正的方法(根据代码在虚拟内存中的位置调整地址,不同进程可能会有所不同),而无需为每个进程维护代码的单独副本。 PLT是过程链接表,其中一种结构使动态加载和链接更容易使用。

printf@plt实际上是一个小存根,它(最终)调用真正的printf函数,修改事情以便后续调用更快。

这个真正的printf函数可以映射到给定进程(虚拟地址空间)中的任何位置,就像试图调用它的代码一样。

因此,为了允许正确的代码共享调用代码(左下方)和被调用的代码(右下方),所以可能最终也在左侧),您不希望应用任何修正直接调用代码,因为这将限制它在其他进程中的位置。

因此PLT是一个较小的特定于流程的区域,位于运行时可靠计算的地址,不是在进程之间共享,所以任何给定的过程都可以自由地改变它,但没有副作用。


检查下图,其中显示了代码和库代码映射到两个不同进程ProcAProcB中的不同虚拟地址:

Address: 0x1234          0x9000      0x8888
        +-------------+ +---------+ +---------+
        |             | | Private | |         |
ProcA   |             | | PLT/GOT | |         |
        | Shared      | +---------+ | Shared  |
========| application |=============| library |==
        | code        | +---------+ | code    |
        |             | | Private | |         |
ProcB   |             | | PLT/GOT | |         |
        +-------------+ +---------+ +---------+
Address: 0x2020          0x9000      0x6666

这个特殊的例子显示了一个简单的情况,PLT映射到一个固定的位置 - 在你的场景中,它位于相对于当前程序计数器的位置,如程序计数器相对查找所示:

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

我使用固定寻址来保持示例更简单。

共享代码的原始方式意味着必须将它们加载到使用它的每个进程的每个虚拟地址空间中的相同内存位置。无论是那个还是无法共享,因为为一个进程修复单个共享副本的行为将完全填满其映射到不同位置的其他进程。

通过使用与位置无关的代码,以及PLT和全局偏移表(GOT),对函数printf@plt第一次调用(在PLT中)是一个多阶段操作,其中:

  • 您在PLT中致电printf@plt
  • 它调用GOT版本(通过指针)最初指向PLT中的某些设置代码。
  • 此设置代码加载相关的共享库(如果尚未完成),然后修改 GOT指针,以便后续调用直接到真实printf而不是PLT设置代码。

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

  • 您在PLT中致电printf@plt
  • 它调用指向真实 printf的GOT版本(通过指针)。

可以找到一篇好文章here,详细说明在运行时如何加载glibc

答案 1 :(得分:4)

不确定,但可能你所看到的是有道理的。第一次运行disas命令时,尚未调用printf,因此无法解析。一旦程序在第一次更新GOT时调用printf方法,现在解析printf并且GOT指向实际函数。因此,下一次调用disas命令会显示真正的printf程序集。