当我没有指定操作数大小时,push指令压入堆栈的字节数是多少?

11 x86 assembly

我可以通过这样做将4个字节压入堆栈:

push DWORD 123
Run Code Online (Sandbox Code Playgroud)

但我发现我可以在push不指定操作数大小的情况下使用:

push 123
Run Code Online (Sandbox Code Playgroud)

在这种情况下,push指令将多少字节压入堆栈?推送的字节数是否取决于操作数大小(因此在我的示例中它将推送1个字节)?

Pet*_*des 12

推送的字节数是否取决于操作数大小

它不依赖于数字的值.push推动多少字节的技术x86术语是"操作数大小",但这与数字是否适合imm8是分开的.

另请参阅每个PUSH指令是否在x64上推送8个字节的倍数?

(所以在我的例子中它会推1个字节)?

不,立即数的大小不是操作数大小.它总是在32位代码中推送4个字节,或者在64位代码中推送64个字节,除非你做了一些奇怪的事情.

建议:始终只是编写push 123push 0x12345使用push您所在模式的默认大小,并让汇编器选择编码.这几乎总是你想要的.如果这就是你想知道的,你现在就可以停止阅读了.


首先,了解x86机器代码中甚至可能的大小push有用的:

  • 在16位模式下,您可以按16或(在386及更高版本上使用操作数大小前缀)32位.
  • 在32位模式下,您可以按32或(带操作数大小前缀)16位.
  • 在64位模式下,您可以按64或(带操作数大小前缀)16位.
    一个REX.W = 0前缀并不能帮助你编码是32位推.1

没有其他选择.堆栈指针总是按推送2的操作数大小递减.(因此可以通过按16位来"错位"堆栈). pop具有相同的大小选择:16,32或64,除了在64位模式下没有32位弹出.

这适用于您是推送寄存器还是立即注册,无论是立即适合符号扩展imm8还是需要imm32(或imm1616位推送).(64位push imm32符号 - 扩展到64位.没有push imm64,只有mov reg, imm64)

在NASM源代码中,push 123汇编到与您所处的模式匹配的操作数大小. 在您的情况下,我认为您正在编写32位代码,因此push 123是32位推送,即使它可以(并且)使用push imm8编码.

你的汇编程序总是知道它正在组装什么类型的代码,因为当你强制操作数大小时它必须知道何时使用或不使用操作数大小的前缀.

MASM是一样的; 唯一可能不同的是强制使用不同操作数大小的语法.

你在汇编程序中编写的任何内容都将汇编为一个有效的机器代码选项(因为编写汇编程序的人知道什么是可编码的,哪些不可编码),所以不,你不能用push指令推送单个字节.如果你想要,你可以用dec esp/ 模仿它mov byte [esp], 123


NASM示例:

输出nasm -l /dev/stdout来将列表转储到终端以及原始源代码行.

轻微编辑以从操作数中分离操作码和前缀字节.(与objdump -drwC -MintelNASM的反汇编格式不同,机器码hexdump中的字节之间不留空格).

 68 80000000         push 128
 6A 80               push -128                 ;; signed imm8 is -128 to +127
 6A 7B               push byte 123
 6A 7B               push dword 123            ;; still optimized to the imm8 encoding
 68 7B000000         push strict dword 123
 6A 80               push strict byte 0x80     ;; will decode as push -128
 ******************       warning: signed byte value exceeds bounds [-w+number-overflow]
Run Code Online (Sandbox Code Playgroud)

dword通常是一个操作数大小的东西,而strict dword你是如何请求汇编程序不将它优化为较小的编码.

所有前面的指令都是32位推送(或64位模式下的64位,具有相同的机器代码).以下所有指令均为16位推送,无论您将它们组装到何种模式.(如果在16位模式下组装,它们将没有0x66操作数大小前缀)

 66 6A 7B            push word 123
 66 68 8000          push word 128
 66 68 7B00          push strict word 123
Run Code Online (Sandbox Code Playgroud)

NASM显然似乎将这些bytedword覆盖视为应用于立即word数的大小,但适用于指令的操作数大小.实际上o32 push 12在64位模式下使用也没有得到警告. push eax但是:"错误:64位模式不支持指令".

请注意,所有模式push imm8都编码6A ib.如果没有操作数大小前缀,则操作数大小是模式的大小.(例如,6A FF在长模式下解码为具有操作数的64位操作数大小,将-1RSP递减8并执行8字节存储.)


地址大小前缀仅影响用于推送内存源的显式寻址模式,例如在64位模式下:( push qword [rsi]无前缀)与push qword [esi](32位寻址模式的地址大小前缀). push dword [rsi]是不可编码的,因为在64位代码1中没有任何东西可以使操作数大小为32位. push qword [esi]不会截断rsp为32位.显然"堆栈地址宽度"是一个不同的东西,可能在段描述符中设置.(在普通操作系统上64位代码总是64位,我认为即使对于Linux的x32 ABI:长模式下的ILP32.)


你什么时候想要推16位?如果你因为性能原因而写asm,那么可能永远不会.在我的code-golf adler32中,一个窄推 - >宽弹出占用较少的代码字节而不是移位/或将两个16b整数组合成一个32b的值.

或者也许在64位代码的漏洞利用中,您可能希望将一些数据推送到堆栈中而没有间隙.您不能只使用push imm32,因为该符号或零扩展到64位.您可以在具有多个16位推送指令的16位块中执行此操作.但对于mov rax, imm64/ push rax(10B + 1B = 11B,对于8B imm有效载荷)仍然可能更有效.或push 0xDEADBEEF/ mov dword [rsp+4], 0xDEADC0DE(5B + 8B = 13B且不需要寄存器).四次16位推送需要16B.


脚注:

  1. 实际上REX.W = 0被忽略,并且不会修改操作数大小远离其默认的64位.NASM,YASM和GAS都汇集push r12到了41 54,而不是49 54.GNU objdjump认为49 54很不寻常,并将其解码为49 54 rex.WB push r12.(两者都执行相同).微软同意,在某些Windows DLL中使用40hREX作为填充push rbx.

    英特尔只是说在长模式下32位推送是"不可编码的"(表中的NE).我不明白为什么W = 1不是push/ pop当需要REX前缀时的标准编码,但显然选择是任意的.

    有趣的事实:在64位模式下,只有堆栈指令和其他一些指令默认为64位操作数大小.在机器代码中,add rax, rdx需要一个REX前缀(设置W位).否则它会解码为add eax, edx.但是,REX.W=0当默认为64位时,不能减小操作数大小,只有在默认为32时才增加它.

    http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix列出了在64位模式下默认为64位的指令.注意,jrcxz并不严格属于该列表,因为它检查的寄存器(cx/ecx/rcx)由地址大小决定,而不是操作数大小,因此它可以被重写为32位(但不是16位) )在64位模式下. loop是一样的.

    奇怪的是,英特尔的指令参考手册push(HTML摘录:http://felixcloutier.com/x86/PUSH.html) 显示了在64位模式下32位操作数大小推送会发生什么情况(唯一的情况是堆栈地址宽度可以是64,所以它使用rsp).也许它可以通过代码段描述符中的某些非标准设置以某种方式实现,因此您无法在正常操作系统下运行的普通64位代码中执行此操作.或者更可能是疏忽,如果它是可编码的,那就是会发生什么,但事实并非如此.

  2. 除了段寄存器是16位,但法线push fs仍然会将堆栈指针递减堆栈宽度(操作数大小).英特尔证明,在这种情况下,最近的英特尔CPU仅执行16b存储,而32或64b的其余部分未经修改.

    x86没有正式具有在硬件中强制执行的堆栈宽度.它是一个软件/调用约定术语,例如char,short在任何调用约定中在堆栈上传递的args被填充到4B或8B,因此堆栈保持对齐.(现代32位和64位调用约定,例如Linux使用的x86-32 System V psABI,在函数调用之前保持堆栈16B对齐,即使堆栈上的arg"槽"仍然只是4B).无论如何,"堆栈宽度" 只是任何架构上的编程约定.

    在x86 ISA的最接近"堆栈宽度"的默认操作数大小push/ pop.但是你可以根据需要操纵堆栈指针,例如sub esp,1.你可以,但不是出于性能原因:P


Dav*_*zer 1

计算机中的“堆栈宽度”是可以压入堆栈的最小数据量,被定义为处理器的寄存器大小。这意味着如果您正在处理具有 16 位寄存器的处理器,则堆栈宽度将为 2 个字节。如果处理器有32位寄存器,则堆栈宽度为4字节。如果处理器有 64 位寄存器,则堆栈宽度为 8 字节。

使用现代 x86/x86_64 系统时不要感到困惑;如果系统运行在32位模式下,堆栈宽度和寄存器大小为32位或4字节。如果切换到 64 位模式,那么寄存器和堆栈大小才会发生变化。

  • IIRC,在 x86-64 中,您也可以压入 2 个字节值,即堆栈宽度的 1/4。 (2认同)
  • 但这绝对是错误的。无法在 64 位长模式下将 32 位值压入堆栈。该表非常清楚地表明这不是有效的编码。我怀疑这只是其中一种情况,即使英特尔也很难在其手册中清楚地记录下来,特别是因为他们尝试了这种一刀切的方法,其中一本手册必须记录数十年的微处理器和多个指令集。非常清楚的是:*“在 64 位模式下,堆栈指针的大小始终是 64 位。”* @Peter (2认同)