关于x86汇编语言的维基百科文章指出,“程序员不能直接访问IP寄存器。”
直接意味着使用像mov
和add
这样的指令,就像我们可以读写EAX一样。
为什么不能?这背后的原因是什么?有哪些技术限制?
有特殊的指令如jmp
用于设置它,以及call
用于在设置新值之前推送旧值。(而在x86-64中,则使用RIP相对寻址模式使用LEA进行读取。)有关详细信息,请参见直接读取程序计数器。
关于x86汇编语言的维基百科文章指出,“程序员不能直接访问IP寄存器。”
直接意味着使用像mov
和add
这样的指令,就像我们可以读写EAX一样。
为什么不能?这背后的原因是什么?有哪些技术限制?
有特殊的指令如jmp
用于设置它,以及call
用于在设置新值之前推送旧值。(而在x86-64中,则使用RIP相对寻址模式使用LEA进行读取。)有关详细信息,请参见直接读取程序计数器。
因为没有合法的使用情况,所以您无法直接访问它。任何任意指令改变eip
将使分支预测非常困难,并可能引发一系列安全问题。
您可以使用jmp
、call
或ret
编辑eip
。只是不能使用普通操作直接读取或写入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
call
指令将指令指针推入堆栈顶部,而 pop eax
将指令指针移动到 eax
中并将堆栈恢复到其正确位置。请注意,没有 ret
指令;在这种情况下,call
指令不打算用作调用,因此将返回指针留在堆栈上会使其失衡,并在下一个 ret
上导致无限循环(或崩溃)。 - Polynomialjmp
来设置它,也没有足够紧迫的用例来添加一个特定的读取它的指令或从通用目的指令的mod-reg-rm字节中窃取宝贵的位数。 - Matteo Italiajmp
之外的指令),而且您不希望它使用其中一个8个通用寄存器编码。但是,能够有效地读取它对于位置无关代码来说是很好的。幸运的是,x86-64通过RIP相对寻址修复了这个问题,包括lea rax,[rip]
,否则call
/pop
通常是最好的选择。(有趣的事实:call +=
(目标=下一条指令)是一种特殊情况,并且不会使返回地址预测器堆栈失衡。http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0,因此call/pop并不差。) - Peter Cordes进入时push {r5, lr}
,返回时pop {r5, pc}
(将保存的链接寄存器值弹出到程序计数器中)。但是,这使得高性能/乱序的ARM实现不太方便,并且已被AArch64放弃。
lea rsi, [rip+whatever]
和RIP相对寻址模式可以直接访问静态数据和常量,并且非常适用于高效的位置无关代码。间接JMP指令完全足够用于写入RIP。call +0
是可以的,不会破坏返回地址预测器,所以 call
/pop
实际上是最好的选择。http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0. - Peter Cordes我认为他们的意思是IP寄存器不能像其他寄存器一样直接访问。程序员可以通过发出跳转指令来向IP写入数据。
jmp
指令将设置EIP
寄存器。
这段代码将把eip
设置为00401000:
mov eax, 00401000
jmp eax ;set Eip to 00401000
获取 EIP
的方法如下:
call GetEIP
.
.
GetEIP:
mov eax, [esp]
ret
db 0xeb, 0x0
用于相对近距离跳转到当前ip。如果汇编器知道如何在预处理器级别上计算 sizeof(nop;nop;nop;nop)
,我们可以内联计算偏移量以避免计数错误。 - Dmytrocall +0
是可以的,不会破坏返回地址预测器,所以 call
/pop
实际上是最好的选择。 http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0. - Peter Cordes
jmp XXX
来做同样的事情。 - Mysticial