Assembly @plt在这里是什么意思?
这是一种获得代码修复的方法(根据代码在虚拟内存中的位置调整地址,不同进程之间可能不同),而不必为每个进程维护单独的代码副本。PLT是程序链接表,是使动态加载和链接更易于使用的结构之一Assembly @plt在这里是什么意思?,assembly,disassembly,Assembly,Disassembly,这是一种获得代码修复的方法(根据代码在虚拟内存中的位置调整地址,不同进程之间可能不同),而不必为每个进程维护单独的代码副本。PLT是程序链接表,是使动态加载和链接更易于使用的结构之一 printf@plt实际上是一个小存根,它(最终)调用realprintf函数,在过程中进行修改以加快后续调用 realprintf函数可以映射到给定进程(虚拟地址空间)中的任何位置,以及试图调用它的代码 因此,为了允许调用代码(下面左侧)和被调用代码(下面右侧)的正确代码共享,您不希望直接对调用代码应用任何修正,
printf@plt
实际上是一个小存根,它(最终)调用realprintf
函数,在过程中进行修改以加快后续调用
realprintf
函数可以映射到给定进程(虚拟地址空间)中的任何位置,以及试图调用它的代码
因此,为了允许调用代码(下面左侧)和被调用代码(下面右侧)的正确代码共享,您不希望直接对调用代码应用任何修正,因为这将限制它在其他进程中的位置
因此,PLT
是一个较小的进程特定区域,位于运行时可靠计算的地址,进程之间不共享,因此任何给定进程都可以随意更改,而不会产生不利影响
检查下图,其中显示了在两个不同进程中映射到不同虚拟地址的代码和库代码,
ProcA
和ProcB
:
(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>
此特定示例显示了PLT映射到固定位置的简单情况。在您的场景中,它位于相对于当前程序计数器的位置,如程序计数器相对查找所示:
Address: 0x1234 0x9000 0x8888
+-------------+ +---------+ +---------+
| | | Private | | |
ProcA | | | PLT/GOT | | |
| Shared | +---------+ | Shared |
========| application |=============| library |==
| code | +---------+ | code |
| | | Private | | |
ProcB | | | PLT/GOT | | |
+-------------+ +---------+ +---------+
Address: 0x2020 0x9000 0x6666
:jmpq*0x2004c2(%rip);0x600860
我刚刚使用了固定地址来简化示例
最初的代码共享方式意味着它们必须加载到使用它的每个进程的每个虚拟地址空间中的相同内存位置。要么是这样,要么就是无法共享,因为为一个进程修复单个共享副本的行为将完全阻塞映射到不同位置的其他进程
通过使用位置无关代码以及PLT和全局偏移表(GOT),第一次调用函数printf@plt
(在PLT中)是一种多阶段操作,其中会发生以下操作:
- 你呼叫
在PLT中printf@plt
- 它调用get版本(通过指针),该版本最初指向PLT中的一些设置代码
- 此设置代码加载相关的共享库(如果尚未完成),然后修改get指针,以便后续直接调用real
,而不是PLT设置代码printf
- 然后,它在该进程的正确地址调用加载的
代码printf
- 你呼叫
在PLT中printf@plt
- 它调用get版本(通过指针),该版本现在指向真正的
printf
可以找到一篇很好的文章,详细介绍了如何在运行时加载
glibc
。不确定,但您所看到的可能是有意义的。第一次运行disas命令时,尚未调用printf,因此无法解析它。一旦程序在第一次更新get时调用printf方法,现在printf被解析,get指向实函数。因此,对disas命令的下一次调用显示了真实的printf程序集。为什么两个disas printf
会给出不同的结果?存根在我键入r
之前,另一个在break
之后,它可以用gdb的任何可执行文件进行复制。这是过程链接表。我会更新更多信息。@Luis,我不确定是否有。我认为安装代码实际上加载了共享库作为其过程的一部分(ASLR意味着它将在某个任意地址加载),因此在该点之后才知道该地址。第一个输出行来自哪里objdump
我想是吧?
Address: 0x1234 0x9000 0x8888
+-------------+ +---------+ +---------+
| | | Private | | |
ProcA | | | PLT/GOT | | |
| Shared | +---------+ | Shared |
========| application |=============| library |==
| code | +---------+ | code |
| | | Private | | |
ProcB | | | PLT/GOT | | |
+-------------+ +---------+ +---------+
Address: 0x2020 0x9000 0x6666
<printf@plt+0>: jmpq *0x2004c2(%rip) ; 0x600860 <_GOT_+24>