pop eip 是合法指令吗?

Is pop eip legal instruction?

我正在做大学的理论考试,被问到这个问题: 经过一些指令后,esp 增长了 4,eip 增长了 20,这可能是什么指令? 我标记了“pop eip”和“ret”。 nasm 32位汇编是否可以执行pop eip指令?

pop eip 不是真正的 x86 指令。 没有 assembler 会 assemble 它,AFAIK。

它是 解释 ret 的伪代码。请参阅操作部分 in the manual。特别是正常的 "near" ret; far jmp/call/ret 在"normal" 32 位代码中基本不用。

ret 有自己的操作码,与 pop 的任何编码分开,x86 也选择给它一个单独的助记符。 pop eip 也被接受为 0xc3 操作码的另一个名称将是一个有效的设计。 x86 确实为许多不同的操作码重载了 mov 助记符,包括整数寄存器 and/or 内存或立即数之间的 mov to/from control registers, mov to/from debug registers, as well as the standard mov。 (不过,mov 的 "standard" 形式也有几个不同的操作码可供选择。)

但这会有点奇怪,因为 push eip 不存在,除非 call +0 具有跳跃的 side-effect 性能。

EIP 不是 8 个 general-purpose 整数寄存器之一,因此 pop 的正常编码无法编码 ret。这就是为什么 ret 需要自己的操作码,以及为什么在 asm 源代码中有一个单独的助记符是有意义的。 x86 指令将寄存器编码为 3 位数字,或者在 x86-64 上可选地编码为 4 位。或作为 隐式 源或目标,如 EDX:EAX 用于 muldiv,或 pushf 隐式读取 EFLAGS:它只是隐式通过该操作码,没有任何位 mean EFLAGS.


ret 不是魔术:它所做的只是弹出堆栈并将结果用作跳转目标。由程序员来确保 ESP 指向您要跳转到的地址,通常是 return 地址。

有些新手不理解这一点,认为ret会神奇return到最后call,所以不把[=11=上的错联系起来] 他们的代码弄乱了堆栈。

我肯定在 SO 答案和评论中多次写过类似“ret 是我们在 x86 上用于 pop eip 的名称”。

有趣的事实:在 ARM 32 位上,程序计数器 16 个整数寄存器之一,r15,所以你真的可以 pop {r4, pc}恢复已保存的 R4 并将已保存的 lr(link 寄存器 = return 地址)弹出到程序计数器中,全部在一条指令中完成。所以 ARM 确实可以使用它用于弹出 general-purpose 整数寄存器的相同操作码来执行 pop eip 的等效操作。


esp grew by 4 and eip grew by 20

是的,我认为 C3 ret or C2 00 00 ret 0 是唯一可以做到这一点的 2 个操作码,并且都使用 ret 助记符。

如果 EIP 增长了 15 或更少,add esp, 4pop eax 的长编码可以解释它,例如具有多个冗余 rep and/or fs 前缀和直接 4.

imm32 编码

x86 指令的长度最多为 15 个字节;如果解码在 15 个字节之前没有到达指令末尾,CPU 会出现 #UD 异常,就像其他非法指令一样。所以用一条指令将 EIP 改变 20 个字节只有通过跳转才有可能。 增加 ESP的唯一跳跃是ret; jmp / jcc 保持不变,call 推送一个 return 地址。

iret 几乎是可能的,但它弹出 CS:IP 和一个 FLAGS 值:你不能让它只弹出 4 个字节。 (尤其是在 32 位模式下。)

sysret 不修改 ESP,仅供内核使用(ring 0)。 sysexit 从 RCX 和 RIP = RDX 设置 RSP,但我很确定这不是他们正在寻找的答案。 :P