2011-07-05 54 views
6

我在x86_64汇编中了解到,例如(64位)rax寄存器,但它也可以作为32位寄存器,eax,16位,ax和8位,al。在什么情况下我不会使用完整的64位,为什么会有什么优势?64位汇编,何时使用更小的寄存器

作为一个例子,用这个简单的Hello World程序:

section .data 
msg: db "Hello World!", 0x0a, 0x00 
len: equ $-msg 

section .text 
global start 

start: 
mov rax, 0x2000004  ; System call write = 4 
mov rdi, 1    ; Write to standard out = 1 
mov rsi, msg   ; The address of hello_world string 
mov rdx, len   ; The size to write 
syscall     ; Invoke the kernel 
mov rax, 0x2000001  ; System call number for exit = 1 
mov rdi, 0    ; Exit success = 0 
syscall     ; Invoke the kernel 

RDI和RDX,至少,只需要8个位,而不是64,是吗?但是,如果我分别将它们更改为dil和dl(它们的低8位等价物),则程序将汇编并链接,但不输出任何内容。

但是,如果我使用eax,edi和edx,它仍然有效,那么我应该使用那些而不是完整的64位?为什么或者为什么不?

+1

实际上,在Linux(也许还有其他一些?)系统调用的参数是32位宽,所以你应该使用EDI和EDX。 http://www.win.tue.nl/~aeb/linux/lk/lk-4.html#ss4.3 –

+0

rax又如何改变为eax呢?我试图改变这三个方法,但它的工作原理,但我想知道的是为什么我应该这样做,以及什么是优势。 – mk12

+0

在这个程序的情况下,唯一明显的区别是字面值(4,1,0等)是64位时的两倍,所以你的程序将会增大几个字节,在理论上,可能需要更长的时间从磁盘/内存加载到CPU中。 –

回答

2

首先并且最重要的是从存储器(读取字符,处理数据结构,解串网络数据包等)中加载较小(例如8位)的值到寄存器中。

MOV AL, [0x1234] 

MOV RAX, [0x1234] 
SHR RAX, 56 
# assuming there are actually 8 accessible bytes at 0x1234, 
# and they're the right endianness; otherwise you'd need 
# AND RAX, 0xFF or similar... 

当然或者,写说值回内存。


(编辑一样,后来6年):

因为这样下去上来:

MOV AL, [0x1234] 
  • 只读取内存在0x1234的单字节(逆会仅覆盖单个字节的存储器)
  • 保留RAX的其他56位中的任何内容
    • 这会在RAX的过去值和未来值之间产生依赖关系,因此CPU无法使用register renaming优化指令。

相比之下:

MOV RAX, [0x1234] 
  • 读取8个字节的存储器中,起始为0x1234(逆会覆盖8个字节的存储器)
  • 覆盖所有RAX
  • 假定内存中的字节与CPU具有相同的字节序(在网络数据包中通常不是这样,因此m ŸSHR指令年前)

另外需要注意的:

MOV EAX, [0x1234] 

然后,如在评论所提到的,有:

MOVZX EAX, byte [0x1234] 
  • 只读取的存储器在为0x1234
  • 一个字节延伸值填充所有EAX的(并因此RAX)与零(消除依赖性并允许寄存器重命名优化)。

在所有这些情况下,如果你想从“A”注册到你有记忆写来挑选你的宽度:

MOV [0x1234], AL ; write a byte (8 bits) 
MOV [0x1234], AX ; write a word (16 bits) 
MOV [0x1234], EAX ; write a dword (32 bits) 
MOV [0x1234], RAX ; write a qword (64 bits) 
+2

Erm ... x86_64是_always_ little endian,所以你的例子会产生不同的结果。 – Ruslan

+1

这里最好的选择是'movzx eax,[0x1234]'。 –

+0

Peter cordes是对的。 “mov al”不会破坏依赖链。 –

1

如果你想只工作一个8位的数量,那么你可以使用AL寄存器。 AX和EAX也一样。

例如,您可以有一个包含两个32位值的64位值。您可以通过访问EAX寄存器来处理低32位。当您想要处理高32位时,可以交换两个32位量(反转寄存器中的DWORD),以使高位现在位于EAX中。

+0

我将如何去交换32位数量? – mk12

+0

通过32位旋转RAX。 –

+0

虽然nasm中的实际指令是什么?我对此很陌生。 – mk12

1

64-bit是您可以作为一个单元使用的最大内存块。这并不意味着你需要使用多少。

如果您需要8位,请使用8.如果您需要16位,请使用16.如果位数不重要,那么使用多少位无关紧要。

不可否认,当使用64位处理器时,使用完整64位的开销很小。但是,例如,如果您正在计算一个字节值,则使用一个字节将意味着结果已经是正确的大小。

+0

我编辑了我的问题,以了解这如何让我困惑。 – mk12

5

你在这里问几个问题。

如果你只是加载一个寄存器的低8位,其余的寄存器将保持其以前的值。这可以解释为什么你的系统调用得到错误的参数。

当你需要的时候使用32位的一个原因是许多使用EAX或EBX的指令比使用RAX或RBX的指令短一个字节。这也可能意味着加载到寄存器的常量更短。

该指令集已经发展了很长时间,并有不少怪癖!

2

如果你只是需要32位寄存器,你可以安全地使用它们,这在64位下是可以的。但是,如果您只需要16位或8位寄存器,请尽量避免使用它们,或者始终使用movzx/movsx清除剩余的位。众所周知,在x86-64下,使用32位操作数会清除64位寄存器的高位。这样做的主要目的是避免错误的依赖关系链。

请参阅相关部分 - 3.4.1.1 - 的Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1

32位操作数产生一个32位结果,零扩展到64位结果在目的地中普通目的寄存器

打破依赖性链允许指令以并联于1995年

AQ执行,以随机顺序,通过由于Pentium Pro的由CPU的内部实现的Out-of-Order algorithmIntel® 64 and IA-32 Architectures Optimization Reference Manual uote,章节3.5.1.8:

修饰部分寄存器

码序列可以体验在其依赖关系链一些延迟,但可以通过使用依赖断裂习语来避免。在基于英特尔酷睿微体系结构的处理器中,当软件使用这些指令将寄存器内容清零时,许多指令可以帮助清除执行依赖性。通过操作32位寄存器而不是部分寄存器来中断指令间寄存器部分的依赖关系。对于移动,这可以通过32位移动或使用MOVZX来完成。

汇编/编译器编码规则37.(M影响,MH通用性):通过操作32位寄存器而不是部分寄存器来中断指令间寄存器部分的相关性。对于移动,这可以通过32位移动或使用MOVZX来完成。

对于x64,带有32位操作数的MOVZX和MOV是等效的 - 它们都会中断相关链。

这就是为什么如果您在使用较小的寄存器时总是尝试清除较大寄存器的最高位,那么您的代码执行速度会更快。当这些位总是清除时,thre不依赖于寄存器的前一个值,CPU可以在内部重新命名寄存器。

Register renaming是一种由CPU内部使用的技术,消除了通过连续指令重用寄存器而导致错误的数据依赖性,这些指令在它们之间没有任何实际的数据依赖关系。