Assembly 为什么Linux上的NASM会更改x86_64程序集中的寄存器

Assembly 为什么Linux上的NASM会更改x86_64程序集中的寄存器,assembly,nasm,x86-64,micro-optimization,shellcode,Assembly,Nasm,X86 64,Micro Optimization,Shellcode,我是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 r

我是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.
(gdb)disas
函数_start的汇编程序代码转储:
=>0x000000000004000b0:mov-eax,0x1
0x00000000004000b5:mov edi,0x1
0x00000000004000ba:movabs rsi,0x6000d8
0x00000000004000c4:mov edx,0x1d
0x00000000004000c9:系统调用
0x00000000004000cb:mov eax,0x3c
0x00000000004000d0:mov edi,0x4
0x00000000004000d5:系统调用
汇编程序转储结束。
我的问题是NASM为什么会这样做?我知道它会根据操作码更改指令,但我不确定寄存器的行为是否相同

这种行为是否也会影响可执行文件的功能

我使用的是安装在VMware i5处理器上的Ubuntu 16.04(64位)


提前感谢。

在64位模式下
mov-eax,1
将清除
rax
寄存器的上部(请参阅以获取解释),因此
mov-eax,1
在语义上等同于
mov-rax,1

然而,前者保留一个REX.W(
48h
数字)前缀(一个指定x86-64引入的寄存器所需的字节),两条指令的操作码相同(
0b8h
后跟一个DWORD或QWORD)。
因此,汇编程序继续进行,并选择最短的形式

这是NASM的一种典型行为,请参见NASM手册的一部分,其中
[eax*2]
的示例被组装为
[eax+eax]
,以在SIB字节1之后保留
disp32
字段(
[eax*2]
仅可编码为
[eax*2+disp32]
,其中汇编器将
disp32
设置为0)

我无法强制NASM发出真正的
mov rax,1
指令(即
48 B8 01 00 00
),即使在指令前面加上
o64

如果需要真正的
mov-rax,1
(这不是您的情况),则必须使用
db
和类似工具手动组装

EDIT:表明实际上有一种方法可以告诉NASM不要使用修饰符优化指令。
mov-rax,STRICT 1
生成10字节版本的指令(
mov-r64,imm64
),而
mov-rax,STRICT-DWORD 1
生成7字节版本(
mov-r64,imm32
,其中
imm32
在使用前进行符号扩展)


旁注:最好使用,这样可以避免64位立即常量(从而减少代码大小)和is(以防您介意)。
mov-esi,msg
更改为
lea-esi,[REL-msg]
(RIP-relative是一种寻址模式,因此它需要一个“寻址”,即方括号,以避免从我们使用的
lea
仅计算有效地址而不进行访问的地址读取)。
您可以使用指令
DEFAULT REL
避免在每次内存访问中键入
REL

我的印象是,文件格式需要PIC代码,但



1缩放索引基字节,用于编码32位模式引入的新寻址模式。

这是一种非常安全和有用的优化,类似于在编写
添加eax时使用8位立即数而不是32位立即数,1

NASM仅在较短形式的指令具有相同的体系结构效果时进行优化,因为。请注意,
add rax,0
add eax,0
不同,因此NASM无法优化:只有像
mov r32,
/
mov r64,
xor eax,eax等不依赖于32位与64位寄存器的旧值的指令才能以这种方式优化


您可以使用
nasm-O1
(默认值为
-O2
禁用它,但请注意,在这种情况下,您将获得10字节
mov-rax,严格的qword 1
:显然,nasm并不打算真正用于非正常优化。没有一个设置,它将使用不会改变反汇编的最短编码(例如,7字节
mov-rax,符号\u-extended\u-imm32
=
mov-rax,严格dword 1

-O0
-O1
之间的区别在于imm8和imm32,例如
添加rax,1

48 83 C0 01
添加r/m64,签名扩展\u imm8
)与
-O1
,vs.
48 05 01000000
add rax,sign_extended_imm32
)使用
nasm-O0

有趣的是,它仍然通过选择表示RAX目的地的特例操作码(而不是采用ModRM字节)进行优化。不幸的是,
-O1
没有优化
mov
的即时大小(其中不可能使用扩展符号imm8)

如果您需要某个特定的编码,请使用
strict
而不是禁用优化


请注意,YASM不会进行这种操作数大小优化,因此,如果您关心可以与其他NASM兼容的汇编器组装的代码中的代码大小(甚至间接出于性能原因),最好在asm源代码中自行进行优化

对于32位和64位操作数大小在非常大(或负数)的情况下不相等的指令,如果希望获得大小/性能优势,则需要明确使用32位操作数大小,即使是使用NASM而不是YASM进行汇编。


对于未设置高位的32位常量,零或符号将其扩展到64位会得到相同的结果。因此,将
mov-rax,1
组装成5字节
mov-r32,imm32
(隐式零扩展到64位)是一种纯粹的优化
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.