assembly 压入/弹出与带寄存器的移动

anauzrmj  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(249)

在x86_64(在Linux上用nasm进行组装)中,我的直觉告诉我,rax在(1)和(2)之后会有相同的内容。

push qword 11               ; (1) Works!
pop rax

;mov rax, qword 11          ; (2) Doesn't Work!

但是(1)工作得很好,(2)使我的程序segfault。这是怎么回事?

blpfk2vs

blpfk2vs1#

push imm8/pop是一种将小整数放入寄存器的紧凑方式(3字节)。效率低,但效果与mov eax,11mov rax, strict qword 11完全相同。* x86/x64机器码中的高尔夫技巧 *
除了在堆栈空间中留下11之外,最终结果应该是相同的。当然,它们有不同的机器代码,所以如果这是外壳代码,那么mov rax, 11版本包括一些00字节,这将终止strcpy或类似的缓冲区溢出。
请注意,NASM将mov rax, 11优化为mov eax, 11,因为它更短,并且对体系结构状态(寄存器值和内存,当然RIP除外,因为它是一条更短的指令)的影响 * 完全 * 相同。
如果您想在以后用不同的常量修补二进制文件,或者您的目标是代码对齐,则可以使用覆盖strict qword 11强制将指令编码为mov r64, imm64,即mov eax, 11的雷克斯.W版本。
YASM不执行更改指令操作数大小属性的代码大小优化,但默认情况下NASM会执行。
mov rax, qword 11并不 * 强制它,它主要只是设置操作数的大小,而不是编码。例如,mov [rdi], qword 11是一种非常规的写mov qword [rdi], 11的方法,其中在某处需要大小说明符,因为两个操作数都不是暗示大小的寄存器。
当我第一次发布这个答案时,我没有测试mov rax, strict qword 11。可以对立即数X1 M18 N1 X的X1 M17 N1 X到寄存器中的3种方式进行编码,其确实示出了用于此的语法和机器代码:

  • 为什么Linux上的NASM会更改x86_64汇编中的寄存器
  • Difference between movq and movabsq in x86-64(AT&T语法,但支持的机器代码指令是相同的。)
  • 将立即数移动到64位寄存器的十六进制机器码没有雷克斯.W前缀?

第一个
objconv是Agner Fog非常好的反汇编器。请查看x86 wiki上的链接。objdump -Mintel -d也可以工作,但没有关于潜在问题的有用评论。
在64位模式下,push的默认操作数大小是一个qword。因此,push imm8符号扩展常量编码仅占用两个字节。
所以push/pop编码更短。如果你的代码的其余部分有错误,可能有一个问题,只有不同的代码对齐,或不同的填充?
Intel's manual表示可以使用REX.W前缀或操作数大小前缀覆盖操作数大小(66H),但实际上并非如此。雷克斯.W=0不能将操作数大小更改为32位,在64位模式下,push/pop的操作数大小默认为64位(雷克斯前缀在其它模式下不可用)。因此,REX.W=1并不 * 覆盖 * 它。
在64位模式下,push/pop只有16位和64位两种大小。(在其他模式下,则为16位或32位)。
立即数的2种大小是符号扩展的8位或32位(对于16位操作数大小,则为16位)。
NASM、YASM和GNU as都正确地拒绝汇编push dword [rsi]pushl (%rsi)。使用db 0x40push qword [rsi]前面手动插入一个雷克斯.W=0前缀会使反汇编程序无法接受。
实际上,在真实的硬件上运行push rdx时,雷克斯.W=0(db 0x40)或REX.W=1(db 0x48)会产生相同的结果:64位全寄存器压入,将rsp减8。(我在Sandybridge上测试了这个答案,后来还在Skylake或Core 2上测试了,我忘记了在回答 * How many bytes does the push instruction push onto the stack when I don't specify the operand size? * 时使用的是哪一个,这两个答案涵盖了相同的细节。)
这不是英特尔手册第一次出错。:/指令集参考中同一entry中的有效编码表确实确认push r32push r/m32在64位模式下不可编码,仅在兼容/传统模式下不可编码,但说明部分中有关雷克斯.W可以覆盖操作数大小的措辞肯定是错误的。

相关问题