我是x86_64汇编编程的新手。我正在用x86_64汇编编写简单的"Hello World"程序。下面是我的代码,运行得非常好。
global _start
section .data
msg: db "Hello to the world of SLAE64", 0x0a
mlen equ $-msg
section .text
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, mlen
syscall
mov rax, 60
mov rdi, 4
syscall
现在,当我在gdb中进行反汇编时,它会给出以下输出:
(gdb) disas
Dump of assembler code for function _start:
=> 0x00000000004000b0 <+0>: mov eax,0x1
0x00000000004000b5 <+5>: mov edi,0x1
0x00000000004000ba <+10>: movabs rsi,0x6000d8
0x00000000004000c4 <+20>: mov edx,0x1d
0x00000000004000c9 <+25>: syscall
0x00000000004000cb <+27>: mov eax,0x3c
0x00000000004000d0 <+32>: mov edi,0x4
0x00000000004000d5 <+37>: syscall
End of assembler dump.
我的问题是为什么NASM的行为方式是这样的?我知道它改变指令的基础上操作码,但我不确定是否相同的行为与寄存器。
这种行为是否也会影响可执行文件的功能?
我使用的是i5处理器上安装在VMware中的Ubuntu 16.04(64位)。
先谢谢你。
2条答案
按热度按时间kzmpq1sx1#
在64位模式下,
mov eax, 1
将清除rax
寄存器的高位部分(有关说明,请参见here),因此mov eax, 1
在语义上等同于mov rax, 1
。然而,前者保留了一个 * 雷克斯.W*(
48h
数字)前缀(指定x86-64引入的寄存器所需的字节),两条指令的操作码相同(0b8h
后跟一个DWORD或QWORD)。所以汇编程序会选择最短的形式。
这是NASM的典型行为,参见NASM手册的第3.3节,其中
[eax*2]
的示例被汇编为[eax+eax]
,以备用 SIB 字节1之后的disp32
字段([eax*2]
仅可编码为[eax*2+disp32]
,其中汇编程序将disp32
设置为0)。我无法强制NASM发出真实的的
mov rax, 1
指令(即48 B8 01 00 00 00 00 00 00 00
),即使在该指令前面加上o64
前缀也是如此。如果需要一个真实的的
mov rax, 1
(这不是您的情况),则必须求助于使用db
等手动组装它。EDIT:Peter Cordes' answer表明,事实上,有一种方法可以告诉NASM not 使用
strict
修饰符优化指令。mov rax, STRICT 1
产生指令的10字节版本(mov r64, imm64
),而mov rax, STRICT DWORD 1
产生7字节版本(mov r64, imm32
,其中imm32
在使用前是 * 符号扩展 * 的)。边注:最好使用RIP-relative addressing,这避免了64位立即数(从而减少了代码大小),并且是mandatory in MacOS(如果你关心的话)。
将
mov esi, msg
更改为lea esi, [REL msg]
(RIP-relative是一种 * 寻址模式 *,因此它需要一个“寻址”,即方括号,为了避免从该地址阅读,我们使用lea
,它只计算有效地址,但不访问)。您可以使用指令
DEFAULT REL
来避免在每次内存访问时键入REL
。我的印象是Mach-O文件格式需要PIC代码,但事实可能并非如此。
1 Scale Index Base 字节,用于对当时32位模式引入的新寻址模式进行编码。
7cwmlq892#
mov eax, 1
(显式使用最佳操作数大小)b8 01 00 00 00
mov rax, strict dword 1
(符号扩展的32位立即数)48 c7 c0 01 00 00 00
mov rax, strict qword 1
(64位立即数,类似于AT & T语法中的movabs
)48 b8 01 00 00 00 00 00 00 00
(Also
mov rax, strict 1
与此等效,并且是禁用NASM优化后得到的结果。)add eax, 1
时使用8位立即数而不是32位立即数。由于
mov eax,1
implicitly zeros the upper 32 bits of RAX,NASM仅在指令的较短形式具有相同的体系结构效果时进行优化。请注意,add rax, 0
与add eax, 0
不同,因此NASM无法优化:只有像mov r32,...
/mov r64,...
或xor eax,eax
这样不依赖于32位寄存器和64位寄存器的旧值的指令才能用这种方法优化(不过NASM不会优化xor rax,rax
或其他置零习惯用法;you should always use 32-bit operand-size manually for xor-zeroing.)nasm -O1
禁用它(默认值为-Ox
multipass)**,但请注意,在这种情况下,您将获得10字节的mov rax, strict qword 1
:显然NASM并不打算真正用于低于正常优化。没有一个设置,它将使用最短的编码,不会改变反汇编(例如7字节mov rax, sign_extended_imm32
=mov rax, strict dword 1
)。-O0
和-O1
之间的差异在于imm8与imm32,例如add rax, 1
为48 83 C0 01
(add r/m64, sign_extended_imm8
)与-O1
的对比x一米27纳米1 x(x一米28纳米1 x)与x一米29纳米1 x。
有趣的是,它仍然通过选择暗示RAX目的地的特殊情况操作码而不是ModRM字节进行了优化。不幸的是,
-O1
没有优化mov
的立即数大小(其中sign_extended_imm8是不可能的)。如果您在某个地方需要特定的编码,请使用
strict
请求它,而不是禁用优化。其他装配工
请注意,YASM不进行这种操作数大小优化,因此,如果您关心可以用其他与NASM兼容的汇编器汇编的代码中的代码大小(即使是间接地出于性能原因),最好在asm源代码中自己进行优化。
对于32位与64位操作数大小不相等的指令(如果数字非常大(或为负数)),如果希望获得大小/性能优势,则需要显式使用32位操作数大小(即使使用NASM而不是YASM进行汇编)。The advantages of using 32bit registers/instructions in x86-64
-Os
**进行此优化,例如gcc -Wa,-Os -c foo.S
,但遗憾的是,这不是默认值。(gcc -O
选项不影响传递给as
的选项,即使显式输入是.s
或.S
。如果您不确定是否手动优化了任何内联asm,则使用gcc -O3 -Wa,-Os foo.c
是一个好主意,假设它没有因为对齐原因而手动优化以使用更长的指令)。适合32位零或符号扩展的64位常量
mov rax, 1
汇编为5字节mov r32, imm32
(隐式零扩展到64位)而不是7字节mov r/m64, sign_extended_imm32
是一种纯粹的优化。(See Difference between movq and movabsq in x86-64获取更多关于
mov
x86 - 64允许的形式的详细信息;AT & T语法对10字节立即数形式有一个特殊的名称,但NASM没有。)性能
在当前所有的x86 CPU上,它和7字节编码之间唯一的性能差异是代码大小,因此只有对齐和L1I $压力等间接影响是一个因素。在内部,它只是一个mov-immediate,因此这种优化也不会改变代码的微架构效果(当然除了代码大小/对齐/它在uop缓存中的压缩方式)。
10字节的
mov r64, imm64
编码对于代码大小来说更糟糕,如果常量实际上设置了任何高位,那么它在IntelSandybridge系列CPU上的uop缓存中的效率会非常低(使用微指令缓存中的2个条目,并且可能需要一个额外的周期来从微指令缓存中读取)。但是,如果常数在-2^31 .. +2^31范围内(有符号32位),即使它是使用64位立即数在x86机器码中编码的,它也同样有效地存储在内部,只使用一个微操作缓存条目(参见Agner Fog's microarch doc,* 表9.1. Sandybridge部分中微操作缓存中不同指令的大小 *)从How many ways to set a register to zero?开始,您可以强制使用以下三种编码中的任何一种:
注意,NASM使用10字节编码(AT & T语法称之为
movabs
,Intel语法模式也称之为objdump
)来表示链接时常量但在汇编时未知的地址。YASM选择
mov r64, imm32
,也就是说,它假设标签地址为32位的代码模型,除非您使用mov rsi, strict qword msg
YASM的行为通常是好的(尽管像C编译器那样使用
mov r32, imm32
作为静态绝对地址会更好)。默认的非PIC代码模型将所有静态代码/数据放在低2GiB的虚拟地址空间中,因此零或符号扩展的32位常量可以保存地址。如果需要64位标签地址,通常应使用
lea r64, [rel address]
执行RIP相关莱亚。(至少在Linux上,位置相关的代码可以位于低32位,因此除非您使用大型/巨型代码模型,否则任何时候您需要关心64位标签地址时,您还在编写PIC代码,其中应使用RIP相对莱亚,以避免绝对地址常量的文本重定位)。例如,gcc和其他编译器会使用
mov esi, msg
或lea rsi, [rel msg]
,而不会使用mov rsi, msg
。参见How to load address of function or label into register