I am new to x86_64 assembly programming. I was writing simple "Hello World" program in x86_64 assembly. Below is my code, which runs perfectly fine.
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
Now when I disassemble in gdb, it gives below output:
(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.
My question is why NASM behaves in such way? I know it changes instructions based on opcode, but I am not sure about same behaviour with registers.
Also does this behaviour affects functionality of executable?
I am using Ubuntu 16.04 (64 bit) installed in VMware on i5 processor.
Thank you in advance.
答案 0 :(得分:3)
在64位模式下mov eax, 1
将清除rax
寄存器的上半部分(请参阅here以获得解释),因此mov eax, 1
在语义上等同于{{1} }}。
前者无需使用 REX.W (mov rax, 1
数字)前缀(指定x86-64引入的寄存器所需的字节),两个指令的操作码相同(48h
后跟DWORD或QWORD)
所以汇编器继续前进并选择最短的形式。
这是NASM的典型行为,请参阅NASM手册的Section 3.3,其中0b8h
的示例汇编为[eax*2]
以免[eax+eax]
SIB 字节 1 之后的字段(disp32
仅可编码为[eax*2]
,其中汇编程序将[eax*2+disp32]
设置为0)。
我无法强制NASM发出真实的disp32
指令(即mov rax, 1
),即使前缀为48 B8 01 00 00 00 00 00 00 00
指令。
如果需要真正的o64
(这不是您的情况),则必须使用mov rax, 1
和类似方法手动组装它。
编辑:Peter Cordes' answer表明实际上有一种方法可以告诉NASM 不使用strict
修饰符来优化指令。
db
生成指令的10字节版本(mov rax, STRICT 1
),而mov r64, imm64
生成7字节版本(mov rax, STRICT DWORD 1
,其中mov r64, imm32
是符号-extended 使用前)。
附注:使用RIP-relative addressing会更好,这可以避免64位立即常量(从而减少代码大小),并且是mandatory in MacOS(如果你关心的话)。 />
将imm32
更改为mov esi, msg
(RIP相对是寻址模式,因此它需要&#34;寻址&#34;,方括号,以避免从中读取地址我们使用的lea esi, [REL msg]
只计算有效地址但没有访问权限
您可以使用指令lea
来避免在每次内存访问中键入DEFAULT REL
。
我的印象是Mach-O文件格式需要PIC代码,但this may not be the case。
1 Scale Index Base 字节,用于编码当时以32位模式引入的新寻址模式。
答案 1 :(得分:3)
这是一种非常安全且有用的优化,非常类似于在编写add eax, 1
时使用8位立即数而不是32位立即数。
NASM仅在较短形式的指令具有相同的架构效果时进行优化,因为mov eax,1
implicitly zeros the upper 32 bits of RAX。
但是请注意YASM没有这样做,所以如果你关心代码大小(甚至间接是出于性能原因),最好自己在asm源中进行优化。
对于32和64位操作数大小不相等的指令,如果你有非常大(或负数)的数字,你需要明确使用32位操作数大小,即使你正在组装NASM而不是YASM,如果你想要32位操作数大小的大小/性能优势。 的 The advantages of using 32bit registers/instructions in x86-64 强>
对于没有设置高位的32位常量,将其扩展为64位的零或符号可得到相同的结果。因此,将mov rax, 1
汇编为5字节mov r32, imm32
(隐式零扩展为64位)而不是7字节mov r/m64, sign_extended_imm32
是一种纯粹的优化。
在所有当前的x86 CPU上,它与7字节编码之间的唯一性能差异是代码大小,因此只有间接效果(如对齐和L1I $压力)是一个因素。在内部,它只是一个mov-immediate,所以这个优化不会改变你的代码的微体系结构效果(当然除了代码大小/对齐/它如何打包在uop缓存中)。
对于代码大小,10字节mov r64, imm64
编码更糟糕。如果常量实际上设置了任何高位,则它在Intel Sandybridge系列CPU上的uop缓存中具有额外的低效率(在uop缓存中使用2个条目,并且可能需要额外的周期来从uop缓存读取)。但是如果常量在-2 ^ 31 .. + 2 ^ 31范围内(有符号32位),它只在内部存储效率很高,只使用一个uop-cache条目,即使它是在x86机器中编码的代码使用64位立即数。 (参见Agner Fog's microarch doc,表9.1.Sandybridge部分中μop缓存中不同指令的大小)
从How many ways to set a register to zero?,您可以强制使用NASM进行三种编码中的任何一种编码:
mov eax, 1 ; 5 bytes to encode (B8 imm32)
mov rax, strict dword 1 ; 7 bytes: REX mov r/m64, sign-extended-imm32. NASM optimizes mov rax,1 to the 5B version, but dword or strict dword stops it for some reason
mov rax, strict qword 1 ; 10 bytes to encode (REX B8 imm64). movabs mnemonic for AT&T. Normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.
请注意,NASM使用10字节编码(AT&amp; T语法调用movabs
,英特尔语法模式中objdump
也是如此),地址是链路时间常数但是在集合时不知道。
YASM选择mov r64, imm32
,即假定代码模型中标签地址为32位,除非您使用mov rsi, strict qword msg
YASM的行为通常是好的(尽管使用mov r32, imm32
作为静态绝对地址,比如C编译器会更好)。默认的非PIC代码模型将所有静态代码/数据放在虚拟地址空间的低2GiB中,因此零或符号扩展的32位常量可以保存地址。
如果您需要64位标签地址,通常应使用lea r64, [rel address]
来执行RIP相对LEA。 (至少在Linux上,依赖于位置的代码可以在低32位,所以除非你使用大型/大型代码模型,否则任何时候你需要关心64位标签地址,你也要制作PIC代码你应该使用RIP相对LEA来避免需要绝对地址常量的文本重定位。)
即。 gcc和其他编译器会使用mov esi, msg
或lea rsi, [rel msg]
,而不是mov rsi, msg
。