在 x86_64 中(在 Linux 上使用 nasm 进行组装),我的直觉告诉我,rax在 (1) 和 (2) 之后会有相同的内容。
push qword 11 ; (1) Works!
pop rax
;mov rax, qword 11 ; (2) Doesn't Work!
Run Code Online (Sandbox Code Playgroud)
但是(1)工作得很好,(2)使我的程序出现段错误。这里发生了什么?
push imm8/pop是一种将小整数放入寄存器的紧凑方式(3 个字节)。mov eax,11效率低下,但效果与或完全相同mov rax, strict qword 11。 使用 x86/x64 机器代码打高尔夫球的技巧
除了留下堆栈空间外,最终结果应该是相同的11。它们当然具有不同的机器代码,因此如果这是 shellcode,则该mov rax, 11版本包含一些00字节,这些字节将终止strcpy或类似的缓冲区溢出。
请注意,NASM 进行了优化mov rax, 11,mov eax, 11因为它更短,并且对架构状态具有完全相同的效果(寄存器值和内存,当然除了 RIP,因为它是更短的指令。)
如果您想稍后使用不同的常量修补二进制文件,或者您的目标是代码对齐,则可以使用覆盖strict qword 11来强制将指令编码为mov r64, imm64,即 的 REX.W 版本mov eax, 11。
YASM 不会进行代码大小优化来更改指令的操作数大小属性,但 NASM 默认情况下会这样做。
mov rax, qword 11不强制它,它主要只是设置该操作数的大小,而不是编码。例如mov [rdi], qword 11是一种非常规的 write 方式mov qword [rdi], 11,其中某个地方需要大小说明符,因为两个操作数都不是暗示大小的寄存器。
当我第一次发布这个答案时,我没有测试mov rax, strict qword 11。mov另请参阅 re:将立即数 a编码到寄存器中的 3 种方式,11其中确实显示了语法和机器代码:
; test-encoding.asm
...
mov eax, 11
mov rax, qword 11
push qword 11 ; yasm rejects this with "invalid size for operand 1"
; probably because there is no push imm64 encoding
pop rax
; push dword [rsi] ; no such instruction
push word [rsi]
Run Code Online (Sandbox Code Playgroud)
nasm -felf64 test-encoding.asm
objconv -fyasm test-encoding.o /dev/stdout
mov eax, 11 ; 0042 _ B8, 0000000B
mov eax, 11 ; 0047 _ B8, 0000000B
push 11 ; 004C _ 6A, 0B
pop rax ; 004E _ 58
Run Code Online (Sandbox Code Playgroud)
objconv 是 Agner Fog 的非常好的反汇编程序。请参阅x86 wiki 获取链接。 objdump -Mintel -d也有效,但没有关于潜在问题的有用评论。
在 64 位模式下,默认操作数大小为pushqword。因此push imm8符号扩展常数编码只需要两个字节。
所以push/pop编码更短。如果代码的其余部分有错误,是否可能存在仅通过不同的代码对齐或不同的填充才能体现的问题?
英特尔手册说操作数大小可以用REX.W前缀或操作数大小前缀 ( 66H) 覆盖,但实际上并非如此。REX.W=0 无法将操作数大小更改为 32 位,在 64 位模式下,push/pop 的操作数大小默认为 64 位(其他模式下不支持 REX 前缀)。所以 REX.W=1 不会覆盖它。
在 64 位模式下,push/pop 的唯一两种大小是 16 位和 64 位。(在其他模式下,16 或 32 位)。
立即数的 2 个大小是符号扩展的 8 位或 32 位。(或者 16 位表示 16 位操作数大小。)
NASM、YASM 和 GNUas都正确地拒绝汇编push dword [rsi]或pushl (%rsi). 使用db 0x40手动在前面插入 REX.W=0 前缀会使push qword [rsi]反汇编程序窒息。
实际上push rdx在真实硬件上运行,使用 REX.W=0 ( db 0x40) 或 REX.W=1 ( db 0x48) 给出相同的结果:完整寄存器的 64 位推送,将 rsp 递减 8。(当我写这个答案时在 Sandybridge 上进行了测试,以及后来在 Skylake 或 Core 2 上,我忘记了在回答当我不指定操作数大小时推送指令推送到堆栈上的字节数是多少?这涵盖了相同的细节。)
这并不是英特尔手册第一次出现错误。:/指令集参考中同一条目push r32中的有效编码表确实确认了这一点,并且push r/m32在 64 位模式下不可编码,仅在兼容/传统模式下,但描述部分的措辞表明 REX.W 可以覆盖操作数大小肯定是错误的。
| 归档时间: |
|
| 查看次数: |
2132 次 |
| 最近记录: |