维基百科关于 x86 汇编的文章说“程序员不能直接访问 IP 寄存器”。
直接表示使用 mov 和 add 等指令。
为什么不?这背后的原因是什么?有哪些技术限制?
您无法直接访问它,因为没有合法的用例。任意更改指令eip
都会使分支预测变得非常困难,并且可能会引发一系列安全问题。
您可以eip
使用jmp
或call
进行编辑ret
。您只是不能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
这可能是 x86 的一种设计。ARM 确实将其用于读/写的程序计数器公开为 R15。不过,这很不寻常。
它允许一个非常紧凑的函数序言/结尾,以及使用单个指令推送或弹出多个寄存器的能力: push {r5, lr}
进入和pop {r5, pc}
返回。(将链接寄存器的保存值弹出到程序计数器中)。
但是,它使高性能/无序 ARM 实现不太方便,并且在 AArch64 中被删除。
所以这是可能的,但会用完其中一个寄存器。32 位 ARM 有 16 个整数寄存器(包括 PC),所以一个寄存器号需要 4 位来编码为 ARM 机器码。另一个寄存器几乎总是作为堆栈指针绑定,因此 ARM 有 14 个通用整数寄存器。(LR可以保存到堆栈中,因此它可以并且被用作函数体内的通用寄存器)。
大多数现代 x86 都是从 8086 继承而来的。它被设计成具有相当紧凑的可变长度指令编码,并且只有 8 个寄存器,机器代码中每个 src 和 dst 寄存器只需要 3 位。
在最初的 8086 中,它们不是很通用,并且在 16 位模式下不可能实现与 SP 相关的寻址,因此基本上 2 个寄存器(SP 和 BP)被绑定用于堆栈内容。这只剩下 6 个通用寄存器,其中一个是 PC 而不是通用寄存器将大大减少可用寄存器,大大增加典型代码中的溢出/重新加载量。
AMD64 增加了 r8-r15,以及 RIP-relative 寻址模式。 lea rsi, [rip+whatever]
,以及用于直接访问静态数据和常量的 RIP 相对寻址模式,是高效的与位置无关的代码所需要的一切。间接 JMP 指令完全足以写入 RIP。
允许使用任意指令来读取或写入 PC 并没有任何好处,因为您总是可以使用整数寄存器和间接跳转来做同样的事情。x86-64 的 R15 与 RIP 相同几乎是纯粹的缺点,特别是对于作为编译器目标的体系结构的性能而言。(到 2000 年设计 AMD64 时,手写的 asm 奇怪的东西已经非常罕见了。)
所以 AMD64 确实是 x86 第一次有可能获得像 ARM 这样的完全公开的程序计数器,但有很多充分的理由不这样做。
jmp
将设置EIP
寄存器。
此代码将 eip 设置为 00401000:
mov eax, 00401000
jmp eax ;set Eip to 00401000
并且为了得到EIP
call GetEIP
.
.
GetEIP:
mov eax, [esp]
ret
我认为他们的意思是不能像访问其他寄存器一样直接访问 IP 寄存器。程序员绝对可以写入 IP,例如通过发出跳转指令。