我正在大学上这项理论考试,并且被问到以下问题: 经过一些指令后,esp增长了4,eip增长了20,这可能是什么指令? 我标记了“ pop eip”和“ ret”。 可以在nasm 32位汇编中执行pop eip指令吗?
答案 0 :(得分:6)
pop eip
不是真正的x86指令。 AFAIK不会编译任何汇编程序。
它是伪指令,用于解释ret
的作用。请参阅“操作”一节in the manual。特别是正常的“附近” ret
;到目前为止,jmp / call / ret基本上没有在“正常”的32位代码中使用。
ret
有自己的操作码,与pop
的任何编码都不同,并且x86还选择为其赋予单独的助记符。 pop eip
作为0xc3
操作码的另一个名称将是一个有效的设计。 x86确实为许多不同的操作码重载了mov
助记符,包括mov
到control registers的往返,mov
到debug registers的往返,以及{ {3}}在整数寄存器和/或内存或立即数之间。 (不过,mov
的“标准”格式也有几种不同的操作码可供选择。)
但这有点奇怪,因为push eip
除了call +0
以外,不存在,它具有跳跃的性能副作用。
EIP不是8个通用整数寄存器之一,因此pop
的常规编码无法对ret
进行编码。这就是ret
需要自己的操作码的原因,也是为什么在asm源中使用单独的助记符才有意义。 x86指令将寄存器编码为3位数字,或者在x86-64上编码为4位。或作为隐式源或目标,例如mul
或div
的EDX:EAX,或pushf
隐式读取EFLAGS:该操作码仅隐含EFLAGS专门
ret
并不是魔术:它所做的只是弹出堆栈并将结果用作跳转目标。程序员应确保ESP指向您要跳转到的地址,通常是返回地址。
一些初学者无法理解这一点,并认为ret
将神奇地返回到最后一个call
,因此他们不会在ret
的错误和代码混乱之间建立联系。堆栈。
在SO问题中,我肯定写过类似“ ret
是我们在x86上为pop eip
使用的名称”的答案。
有趣的事实:在ARM 32位上,程序计数器 是16个整数寄存器r15
之一,因此您真的可以pop {r4, pc}
恢复保存的R4并通过一条指令将保存的lr
(链接寄存器=返回地址)弹出到程序计数器中。因此,ARM可以使用与弹出通用整数寄存器相同的操作码来实现pop eip
的等效功能。
esp增长了4,eip增长了20
是的,我认为standard mov
是唯一可以做到这一点的2个操作码,并且都使用ret
助记符。
如果EIP增长了15或更少,则add esp, 4
或pop eax
的长编码可能会造成这种情况,例如具有多个冗余rep
和/或fs
前缀,以及用于直接imm32
的{{1}}编码。
x86指令的最大长度为15个字节;如果解码在15个字节之前没有到达指令的末尾,则CPU会发生4
异常,就像其他非法指令一样。因此,只有一条跳转才能通过一条指令将EIP更改20字节。而增加 ESP的唯一跳是#UD
; jmp / jcc保持不变,ret
推送一个返回地址。
call
几乎是可能的,但它会弹出iret
和FLAGS值:您不能让它仅弹出4个字节。 (尤其是在32位模式下。)
CS:IP
不会修改ESP,并且只能由内核(0环)使用。 C3 ret
or C2 ret 0
从RCX设置RSP,并且RIP = RDX,但是我很确定这不是他们想要的答案。 :P