在x86_64(在Linux上用nasm进行组装)中,我的直觉告诉我,rax在(1)和(2)之后会有相同的内容。
rax
push qword 11 ; (1) Works! pop rax ;mov rax, qword 11 ; (2) Doesn't Work!
但是(1)工作得很好,(2)使我的程序segfault。这是怎么回事?
blpfk2vs1#
push imm8/pop是一种将小整数放入寄存器的紧凑方式(3字节)。效率低,但效果与mov eax,11或mov 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种方式进行编码,其确实示出了用于此的语法和机器代码:
push imm8
pop
mov eax,11
mov rax, strict qword 11
11
mov rax, 11
00
strcpy
mov eax, 11
strict qword 11
mov r64, imm64
mov rax, qword 11
mov [rdi], qword 11
mov qword [rdi], 11
第一个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 0x40在push 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 r32和push r/m32在64位模式下不可编码,仅在兼容/传统模式下不可编码,但说明部分中有关雷克斯.W可以覆盖操作数大小的措辞肯定是错误的。
objdump -Mintel -d
push
REX.W
66H
as
push dword [rsi]
pushl (%rsi)
db 0x40
push qword [rsi]
push rdx
db 0x48
push r32
push r/m32
1条答案
按热度按时间blpfk2vs1#
push imm8
/pop
是一种将小整数放入寄存器的紧凑方式(3字节)。效率低,但效果与mov eax,11
或mov 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种方式进行编码,其确实示出了用于此的语法和机器代码:第一个
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 0x40
在push 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 r32
和push r/m32
在64位模式下不可编码,仅在兼容/传统模式下不可编码,但说明部分中有关雷克斯.W可以覆盖操作数大小的措辞肯定是错误的。