我正在做大学里的理论测试,有人问我这个问题:在一些指令之后,ESP增长了4,EIP增长了20,指令可能是什么?我把“流行”和“流行”都标上了。在nasm 32位汇编中是否可以执行pop eip指令?
pftdvrlh1#
**pop eip不是真实的的x86指令。**没有汇编程序会汇编它,AFAIK。
pop eip
这是一个伪代码,用来解释ret的作用。参见操作部分in the manual。具体为正常“近”ret;far jmp/call/ret在“正常”32位代码中基本上不使用。ret有自己的操作码,与pop的任何编码分离,x86也选择给予它一个单独的助记符。如果pop eip也被接受为0xc3操作码的另一个名称,这将是一个有效的设计。x86确实使mov助记符重载用于许多不同的操作码,包括mov到/来自control registers,mov到/来自debug registers,以及整数寄存器和/或存储器或立即数之间的standard mov。(不过,mov的“标准”形式也有几种不同的操作码可供选择。)但这会有点奇怪,因为push eip除了call +0之外并不存在,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上的错误和他们的代码搞乱堆栈联系起来。我肯定写过类似“ret是我们在x86上为pop eip使用的名称”的东西在SO回答和评论中很多次。有趣的事实:在ARM 32位上,程序计数器 * 是16个整数寄存器之一,r15,所以你真的可以在一条指令中将pop {r4, pc}恢复保存的R4并将保存的lr(链接寄存器=返回地址)弹出到程序计数器中。因此,ARM实际上可以使用用于弹出通用整数寄存器的相同操作码来执行与pop eip等效的操作。esp增长了4 eip增长了20是的,我认为C3 ret or C2 00 00 ret 0是仅有的两个操作码可以做到这一点,并且都使用ret助记符。如果EIP增长了15或更少,则add esp, 4或pop eax的长编码可以解释它,例如。例如,具有多个冗余rep和/或fs前缀以及用于立即4的imm32编码。x86指令最多可以是15字节长;如果解码在15个字节之前没有到达指令的末尾,CPU会出现#UD异常,就像处理其他非法指令一样。因此,使用一条指令将EIP更改20个字节,只能使用跳转。而唯一 * 增加 * ESP的跳跃是ret;jmp / jcc不修改它,call推送返回地址。iret几乎是可能的,但它弹出CS:IP,一个FLAGS值,和一个新的SS:SP:你不能让它弹出4个字节。(特别是在32位模式下。)sysret不会修改ESP,并且只能由内核使用(环0)。sysexit从RCX和RIP = RDX设置RSP,但我很确定这不是他们想要的答案。:P
ret
pop
0xc3
mov
push eip
call +0
mul
div
pushf
call
r15
pop {r4, pc}
lr
ret 0
add esp, 4
pop eax
rep
fs
4
imm32
#UD
iret
CS:IP
SS:SP
sysret
sysexit
1条答案
按热度按时间pftdvrlh1#
**
pop eip
不是真实的的x86指令。**没有汇编程序会汇编它,AFAIK。这是一个伪代码,用来解释
ret
的作用。参见操作部分in the manual。具体为正常“近”ret
;far jmp/call/ret在“正常”32位代码中基本上不使用。ret
有自己的操作码,与pop
的任何编码分离,x86也选择给予它一个单独的助记符。如果pop eip
也被接受为0xc3
操作码的另一个名称,这将是一个有效的设计。x86确实使mov
助记符重载用于许多不同的操作码,包括mov
到/来自control registers,mov
到/来自debug registers,以及整数寄存器和/或存储器或立即数之间的standardmov
。(不过,mov
的“标准”形式也有几种不同的操作码可供选择。)但这会有点奇怪,因为
push eip
除了call +0
之外并不存在,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
上的错误和他们的代码搞乱堆栈联系起来。我肯定写过类似“
ret
是我们在x86上为pop eip
使用的名称”的东西在SO回答和评论中很多次。有趣的事实:在ARM 32位上,程序计数器 * 是16个整数寄存器之一,
r15
,所以你真的可以在一条指令中将pop {r4, pc}
恢复保存的R4并将保存的lr
(链接寄存器=返回地址)弹出到程序计数器中。因此,ARM实际上可以使用用于弹出通用整数寄存器的相同操作码来执行与pop eip
等效的操作。esp增长了4 eip增长了20
是的,我认为C3
ret
or C2 00 00ret 0
是仅有的两个操作码可以做到这一点,并且都使用ret
助记符。如果EIP增长了15或更少,则
add esp, 4
或pop eax
的长编码可以解释它,例如。例如,具有多个冗余rep
和/或fs
前缀以及用于立即4
的imm32
编码。x86指令最多可以是15字节长;如果解码在15个字节之前没有到达指令的末尾,CPU会出现
#UD
异常,就像处理其他非法指令一样。因此,使用一条指令将EIP更改20个字节,只能使用跳转。而唯一 * 增加 * ESP的跳跃是ret
;jmp / jcc不修改它,call
推送返回地址。iret
几乎是可能的,但它弹出CS:IP
,一个FLAGS值,和一个新的SS:SP
:你不能让它弹出4个字节。(特别是在32位模式下。)sysret
不会修改ESP,并且只能由内核使用(环0)。sysexit
从RCX和RIP = RDX设置RSP,但我很确定这不是他们想要的答案。:P