64位汇编,何时使用较小尺寸的寄存器

时间:2011-07-05 02:12:40

标签: assembly 64-bit x86-64 nasm cpu-registers

我理解在x86_64汇编中有例如(64位)rax寄存器,但它也可以作为32位寄存器,eax,16位,ax和8位来访问。在什么情况下我不会只使用完整的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位吗?为什么或为什么不呢?

5 个答案:

答案 0 :(得分:5)

你在这里问几个问题。

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

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

指令集已经发展了很长时间并且有很多怪癖!

答案 1 :(得分: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]
  • 从0x1234开始读取8个字节的内存(反向将覆盖8个字节的内存)
  • 覆盖所有的RAX
  • 假设内存中的字节具有与CPU相同的字节顺序(通常在网络数据包中不正确,因此多年前我的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 :(得分:2)

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

请参阅Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1

的相关章节 - 3.4.1.1
  

32位操作数生成32位结果,在目标通用寄存器中零扩展为64位结果

断开依赖链允许指令以随机顺序并行执行,自1995年Pentium Pro以来由CPU内部实现的Out-of-Order algorithm

来自Intel® 64 and IA-32 Architectures Optimization Reference Manual的报价,第3.5.1.8节:

  

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

     

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

具有32位x64操作数的MOVZX和MOV是等效的 - 它们都会破坏依赖链。

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

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

答案 3 :(得分:1)

如果您只想使用8位数量,那么您将使用AL寄存器。 AX和EAX也是如此。

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

答案 4 :(得分:1)

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

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

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