1

在某些可以使用 PC(RIP) 相对寻址模式引用数据 (mov eg) 和代码 (jmp, call) 的架构(如 x86_64)中,是否真的有技术原因证明需要这种结构(got, plt ) ?

我的意思是,如果我想将全局数据(例如)移动到寄存器中,我可以执行以下指令(标准 PIE):

mov rax,QWORD PTR [rip+0x2009db]

mov eax,DWORD PTR [rax]

(其中 0x2009db 是 rip 和 get 中包含符号地址的正确条目之间的偏移量)

为什么我们不能做这样的事情:

mov rax, 翻录+0xYYYYYY

mov eax,DWORD PTR [rax]

(0xYYYYYYY 是 RIP 值和符号之间的直接增量(例如全局变量))

我不习惯做 ASM,所以我的例子可能是错误的。但我的想法是:为什么不简单地根据 RIP 计算符号的绝对地址,将其放入 EAX,然后访问其内容。如果指令集允许使用相对寻址做任何我们想做的事情,为什么要使用这样的结构 (got, plt) ?

同样的问题也适用于 call/jmp 指令。

是因为指令集不允许吗?是不是因为偏移值不能覆盖整个地址空间?但是..重要吗?由于一个节的结构被维护一个映射到进程的虚拟寻址空间(例如.dat 节后跟.got 或类似的东西)。我的意思是,为什么直接引用符号地址而不是 get 中的入口地址时偏移量会更大?其他原因?

谢谢 !

4

1 回答 1

1

基本上,这些结构的原因正是具有额外的间接级别。

这样,您可以在动态库中插入符号LD_PRELOAD。即使没有它,动态绑定规则也使得在可执行文件中定义的符号会覆盖在共享库中定义的符号,即使对于来自该库的调用也是如此(参见this)。

另外,请考虑这些要点。

  1. 预先不知道保存被调用函数实现的共享库的加载地址(这是设计使然,特别是故意这样做:它是ld.so称为ASLR的一个特性),因此需要应用动态加载器重新定位到至少在运行时执行的所有调用站点。
  2. 如果不是 PLT,这将扼杀共享由不同进程映像映射到内存的库的代码段的优势,因为在不同的进程中,同一个库可能具有不同的地址,从而导致不同的补丁代码。PLT 是一块相对较小的不共享数据。看到这个帖子
  3. PLT 允许在第一次调用时懒惰地绑定函数。PLT 插槽最初保存解析器的地址。解析完成后,结果缓存在 PLT stot 中。

此处介绍了 GOT/PLT 的重定位机制。总而言之,互联网上有足够的信息来说明 PLT 和 GOT 如何(以及为什么)工作。

另外,查看 GCC 的-fno-plt选项。这是一种优化,但请注意,仍然需要 GOT,并且对于没有 PLT 条目的函数不支持惰性绑定。

于 2019-05-17T12:39:18.797 回答