【发布时间】:2017-08-20 07:26:01
【问题描述】:
我知道在典型的 ELF 二进制文件中,函数是通过过程链接表 (PLT) 调用的。函数的 PLT 条目通常包含到全局偏移表 (GOT) 条目的跳转。这个入口会先引用一些代码把实际函数地址加载到GOT中,并包含第一次调用后的实际函数地址(惰性绑定)。
准确地说,在将 GOT 入口点延迟绑定回 PLT 之前,跳转到 GOT 之后的指令。这些指令通常会跳转到 PLT 的头部,从那里调用一些绑定例程,然后更新 GOT 条目。
现在我想知道为什么有两种间接方式(调用 PLT,然后从 GOT 跳转到地址),而不是仅仅保留 PLT 并直接从 GOT 调用地址。看起来这可以节省跳跃和完整的 PLT。当然,您仍然需要一些代码来调用绑定例程,但这可以在 PLT 之外。
我有什么遗漏吗?额外 PLT 的目的是什么?
更新: 正如 cmets 中所建议的,我创建了一些(伪)代码 ASCII 艺术来进一步解释我所指的内容:
据我了解,目前PLT方案在惰性绑定之前的情况是这样的:(PLT和printf之间的一些间接关系用“...”表示。)
Program PLT printf
+---------------+ +------------------+ +-----+
| ... | | push [0x603008] |<---+ +-->| ... |
| call j_printf |--+ | jmp [0x603010] |----+--...--+ +-----+
| ... | | | ... | |
+---------------+ +-->| jmp [printf@GOT] |-+ |
| push 0xf |<+ |
| jmp 0x400da0 |----+
| ... |
+------------------+
…在惰性绑定之后:
Program PLT printf
+---------------+ +------------------+ +-----+
| ... | | push [0x603008] | +-->| ... |
| call j_printf |--+ | jmp [0x603010] | | +-----+
| ... | | | ... | |
+---------------+ +-->| jmp [printf@GOT] |--+
| push 0xf |
| jmp 0x400da0 |
| ... |
+------------------+
在我想象的没有 PLT 的替代方案中,惰性绑定之前的情况如下所示:(我将“惰性绑定表”中的代码保留为类似于 PLT 中的代码。它也可能看起来不同,我没关系。)
Program Lazy Binding Table printf
+-------------------+ +------------------+ +-----+
| ... | | push [0x603008] |<-+ +-->| ... |
| call [printf@GOT] |--+ | jmp [0x603010] |--+--...--+ +-----+
| ... | | | ... | |
+-------------------+ +-->| push 0xf | |
| jmp 0x400da0 |--+
| ... |
+------------------+
现在在惰性绑定之后,不再使用该表了:
Program Lazy Binding Table printf
+-------------------+ +------------------+ +-----+
| ... | | push [0x603008] | +-->| ... |
| call [printf@GOT] |--+ | jmp [0x603010] | | +-----+
| ... | | | ... | |
+-------------------+ | | push 0xf | |
| | jmp 0x400da0 | |
| | ... | |
| +------------------+ |
+------------------------+
【问题讨论】:
标签: assembly executable elf dynamic-linking