为什么不能直接设置指令指针?

19 x86 cpu-registers program-counter

关于x86程序集的维基百科文章称"程序员无法直接访问IP寄存器".

直接表示像mov和add这样的指令.

为什么不?这背后的原因是什么?有哪些技术限制?

Pol*_*ial 25

您无法直接访问它,因为没有合法的用例.任何指令的任意改变eip都会使分支预测变得非常困难,并且可能会引发一系列安全问题.

您可以编辑eip使用jmp,callret.您无法直接读取或写入eip使用正常操作

设置eip到寄存器就像这样简单jmp eax.你也可以这样做push eax; ret,它将值推eax送到堆栈然后返回(即弹出和跳转).第三个选项是call eax调用eax中的地址.

阅读可以这样做:

call get_eip
  get_eip:
pop eax ; eax now contains the address of this instruction
Run Code Online (Sandbox Code Playgroud)

  • 你的第一段是虚假的.[**ARM的程序计数器完全暴露用于读/写为R15**](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0473f/Babbdajb. HTML).ARM64放弃了这一点,但它没有使ARM32成为不可能.部分分支预测需要在指令被解码之前发生,以避免获取气泡.在解码时,检测到EIP是目的地寄存器并将其标记为分支并不是特别困难.没有安全隐患,因为安全性不依赖于扫描指令流来检测分支指令. (4认同)
  • 关于指令缓存,分支预测和其他花哨的东西的任何解释看起来都很愚蠢,原因很简单:x86诞生于微控制器架构,没有这些装饰.它不像是他们拿走了ip访问权限,因为它使得超级标量体系结构变得困难 - 它从一开始就不存在.可能他们没有添加它,因为已经有'jmp`来设置它,并且没有足够强大的用例来添加特定的指令来读取它或从通用的mod-reg-rm字节中窃取宝贵的位说明. (3认同)
  • 这不是一个很好的推理;有一个非常真实的用例,那就是标签的替代品。无法直接读取eip并保存,您需要计算字节数。能够获取指令指针,而无需根据相对“调用”来制定指令指针。 (2认同)
  • @Dmitry:可写 EIP(带有除 `jmp` 之外的指令)不是必需的,并且您不希望它用完 8 个通用寄存器编码之一。但能够有效地读取它对于位置无关的代码来说是很好的。幸运的是,x86-64 通过 RIP 相对寻址修复了这个问题,包括 `lea rax, [rip]`,否则 `call`/`pop` 通常是最好的。(有趣的事实:“call +=”(目标=下一条指令)是一种特殊情况,并且不会使返回地址预测器堆栈失衡。http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/ #call0,所以 call/pop 还不错。) (2认同)

Pet*_*des 9

那可能是x86的可能设计.ARM确实将其程序计数器公开为读/写为R15.不过这很不寻常.

它允许一个非常紧凑的功能序言/结语,以及使用单个指令推送或弹出多个寄存器的能力: push {r5, lr}在进入和pop {r5, pc}返回时.(将保存的链接寄存器值弹出到程序计数器中).

但是,它使得高性能/无序ARM实现不太方便,并且因为AArch64而被删除.


所以它是可能的,但是用完了其中一个寄存器.32位ARM有16个整数寄存器(包括PC),因此寄存器号需要4位才能在ARM机器代码中进行编码.另一个寄存器几乎总是被绑定为堆栈指针,因此ARM有14个通用整数寄存器.(LR可以保存到堆栈中,因此它可以用作函数体内的通用寄存器).

现代x86的大多数都是从8086继承而来的.它采用相当紧凑的可变长度指令编码设计,只有8个寄存器,每个src只需3位,dst寄存器在机器码中.

在最初的8086中,它们不是非常通用的,并且在16位模式下不可能进行SP相对寻址,因此基本上2个寄存器(SP和BP)被用于堆栈内容.这只留下了6个有点通用的寄存器,并且其中一个是PC而不是通用的,这将大大减少可用寄存器,大大增加了典型代码中的溢出/重载量.


AMD64增加了r8-r15和RIP相对寻址模式. lea rsi, [rip+whatever]和直接访问静态数据和常量的RIP相对寻址模式,是高效的与位置无关的代码所需要的.间接JMP指令完全足以写入RIP.

通过允许使用任意指令来读取或写入PC,实际上没有任何东西可以获得,因为你总是可以使用整数寄存器和间接跳转来做同样的事情.x86-64的R15与RIP完全相同,特别是对于架构作为编译器目标的性能,这几乎是纯粹的缺点.(手写的asm奇怪的东西在2000年设计AMD64时已经非常普遍了.)

因此AMD64真的是第一次有可能获得像ARM这样的完全暴露的程序计数器,但有很多很好的理由不这样做.