32位保护模式下push指令不同操作数长度行为差异咨询
push word指令行为的解释 这是个非常典型的x86指令集细节问题,核心在于**操作数大小前缀(0x66)**的作用,以及不同宽度push指令的设计逻辑——其实并不存在你担心的“不一致”,只是需要明确指令前缀和默认操作数宽度的对应关系:
1. 32位模式下的默认操作数宽度
在32位保护模式中,CPU的默认操作数宽度是32位,栈指针使用32位的ESP。不带任何前缀的push指令默认是push dword,会将4字节数据压入栈,同时ESP -= 4。
2. push word的特殊之处:操作数大小前缀
当你在32位模式下写push word 0x0101时,NASM会自动为指令加上0x66前缀(也就是你看到的字节码66 68 01 01)。这个前缀的作用是临时将操作数宽度从默认的32位切换为16位,此时push操作的对象是16位数据:
- 仅将2字节的
0x0101压入栈 - 栈指针
ESP只减2(而不是4)
这就是为什么你看到栈中只压入了2字节——这是x86指令集的设计,前缀用来临时覆盖默认的操作数宽度,和当前模式的宽度反向切换(32位模式下用0x66切到16位,16位模式下用0x66切到32位)。
3. 为什么push byte会压入4字节?
你提到的push byte行为,其实是x86指令集里push imm8指令的特性:
push byte imm8对应的 opcode 是6A,不带操作数大小前缀,所以会遵循32位模式下的默认操作数宽度。- 它的逻辑是:将8位立即数符号扩展为32位(比如
0x12会变成0x00000012,0xF0会变成0xFFFFFFF0),然后将扩展后的4字节数据压入栈,ESP -=4。
这和push word的逻辑完全不同:前者是默认32位操作,对小宽度立即数做符号扩展后压栈;后者是主动用前缀切换到16位操作,直接处理16位数据。
4. 你的程序崩溃原因
你遇到的程序崩溃,本质是栈指针错位:你误以为push word压入了4字节,所以清理栈时用了类似add esp,4的操作,但实际上栈只被减了2,这会导致后续的栈操作(比如ret、其他push/pop)使用错误的栈地址,最终引发崩溃。正确的清理方式应该是add esp,2。
内容的提问来源于stack exchange,提问作者felipeek




