为什么rbp和rsp称为通用寄存器?

问题描述 投票:18回答:2

根据英特尔在x64中,以下寄存器称为通用寄存器(RAX,RBX,RCX,RDX,RBP,RSI,RDI,RSP和R8-R15)https://software.intel.com/en-us/articles/introduction-to-x64-assembly

在下面的文章中,写了RBP和RSP是专用寄存器(RBP指向当前堆栈帧的基础,RSP指向当前堆栈帧的顶部)。 https://www.recurse.com/blog/7-understanding-c-by-learning-assembly

现在我有两个相互矛盾的陈述。英特尔声明应该是值得信赖的,但是什么是正确的,为什么RBP和RSP被称为通用目的?

谢谢你的帮助。

assembly x86-64 cpu-registers
2个回答
16
投票

通用意味着所有这些寄存器都可以与任何使用通用寄存器进行计算的指令一起使用,例如,您无法通过指令指针(RIP)或标志寄存器(RFLAGS)执行任何操作。

其中一些寄存器被设想用于特定用途,通常是。最关键的是RSP和RBP。

如果您需要将它们用于您自己的目的,您应该在将其他内容存储在内部之前保存其内容,并在完成后将它们恢复为原始值。


7
投票

如果寄存器可以是add的操作数,或者用于寻址模式,则它是“通用的”,而不是像FS段寄存器或RIP那样的寄存器。 GP寄存器也称为“整数寄存器”,即使其他类型的寄存器也可以保存整数。

在计算机体系结构中,CPU通常在内部处理整数寄存器/指令与FP / SIMD寄存器/指令分开。例如Intel Sandybridge-family CPUs有单独的物理寄存器文件,用于重命名GP整数与FP /向量寄存器。这些简称为整数与FP寄存器文件。 (对于内核不需要保存/恢复以使用GP寄存器而保持用户空间的FPU / SIMD状态不变的所有内容,FP都是短手​​的。)FP寄存器文件中的每个条目都是256位宽(到保持AVX ymm向量),但整数寄存器文件条目只需要64位宽。

在重命名段寄存器(Skylake does not)的CPU上,我猜这将是整数状态的一部分,RFLAGS + RIP也是如此。但是当我们说“整数寄存器”时,我们通常特指一个通用寄存器。


除了一些添加了x86-64:R8-R15的全新寄存器外,每个寄存器对某些指令都有一些特殊性。这些不会使他们失去作为通用目的(原来的8个)的原始8日期回到8086,并且即使在最初的8086中也隐含使用它们。

对于RSP,它特别适用于push / pop / call / ret,因此大多数代码从不将它用于其他任何事情。 (在内核模式下,异步使用中断,所以你真的不能把它藏在某个地方,以便在用户空间代码中获得额外的GP寄存器:Is ESP as general-purpose as EAX?

但是在受控条件(如无信号处理程序)中,您不必使用RSP作为堆栈指针。例如你可以使用它来读取一个带有pop的循环中的数组,比如in this code-golf answer。 (我实际上在32位代码中使用了esp,但差别相同:在Skylake上poplodsd快,而两者都是1个字节。)


Implicit uses and special-ness for each register:

另请参阅x86 Assembly - Why is [e]bx preserved in calling conventions?以获取部分列表。

我主要将此限制为用户空间指令,尤其是现代编译器实际上可能从C或C ++代码发出的指令。对于那些有很多隐含用途的注册表,我并没有试图详尽无遗。

  • rax:单操作数[i] mul / [i] div / cdq / cdqe,字符串指令(stos),cmpxchg等。以及许多直接指令的特殊较短编码,如2字节cmp al, 1或5字节add eax, 12345(没有ModRM字节)。另见codegolf.SE Tips for golfing in x86/x64 machine code。 还有xchg-with-eax是0x90 nop来自的地方(在nop成为x86-64中单独记录的指令之前,因为xchg eax,eax将eax零扩展到RAX,因此不能使用0x90编码。但xchg rax,rax仍然可以组装到REX.W = 1 0x90。)
  • rcx:轮班计数,rep-string计数,the slow loop instruction
  • rdxrdx:rax由divide和multiply使用,cwd / cdq / cqo用于设置它们。 rdtscBMI2 mulx
  • rbx:8086 xlatbcpuid使用所有四个EAX..EDX。 486 cmpxchg8bx86-64 cmpxchg16b。大多数32位编译器将为cmpxchg8发出std::atomic<long long>::compare_exchange_weak。 (纯负载/纯存储可以使用SSE MOVQ或x87 fild / fistp,但是,如果针对Pentium或更高版本。)64位编译器将使用64位lock cmpxchg,而不是cmpxchg8b。 一些64位编译器将为cmpxchg16b发出atomic<struct_16_bytes>。 RBX对原始8的隐含用途最少,但lock cmpxchg16b是为数不多的编译器实际使用的。
  • rsi / rdi:string ops,包括rep movsb,有些编译器有时会内联。 (在某些情况下,gcc还会为字符串文字内联rep cmpsb,但这可能不是最佳的)。
  • rbpleave(比mov rsp, rbp / pop rbp慢一点.gcc实际上在带有帧指针的函数中使用它,当它不能只是pop rbp时)。也是没人用过的可怕的慢enter
  • rsp:堆栈操作:push / pop / call / ret和leave。 (和enter)。并且在内核模式(非用户空间)中由硬件异步使用以保存中断上下文。这就是内核代码不能有红区的原因。
  • r11syscall / sysret用它来保存/恢复用户空间的RFLAGS。 (与RCX一起保存/恢复用户空间的RIP)。

寻址模式编码特例:

(另请参阅rbp not allowed as SIB base?,它只是关于寻址模式,我复制了这部分答案。)

rbp / r13不能是没有位移的基址寄存器:该编码代替:(在ModRM中)rel32(RIP相对),或(在SIB中)没有基址寄存器的disp32。 (r13在ModRM / SIB中使用相同的3位,因此这种选择通过不使指令长度解码器查看the REX.B bit来获得第4个基址寄存器位来简化解码)。 [r13]组装到[r13 + disp8=0][r13+rdx]组装到[rdx+r13](当这是一个选项时,通过交换基数/索引来避免问题)。

rsp / r12作为基址寄存器总是需要一个SIB字节。 (base = RSP的ModR / M编码是用于发信号通知SIB字节的转义码,并且如果r12被不同地处理,则更多的解码器将不得不关心REX前缀)。

rsp不能成为索引注册。这使得编码[rsp]成为可能,[rsp + rsp][eax + esp*4]更有用。 (英特尔本可以设计用于32位寻址模式的ModRM / SIB编码(386中新增),因此只有base = ESP才能实现SIB-no-index。这将使[esp + esp*1/2/4/8]成为可能并且只排除r12。但这不是有用的,所以他们通过使index = ESP成为无索引的代码来简化硬件,无论基数如何。这允许两种冗余方式对任何base或base + disp寻址模式进行编码:有或没有SIB。)

[rsp + r12*4]可以是一个索引寄存器。与其他情况不同,这不会影响指令长度解码。此外,它不能像其他情况一样使用更长的编码。 AMD希望AMD64的寄存器设置尽可能正交,因此有必要花一些额外的晶体管来检查REX.X作为索引/无索引解码的一部分。例如,r12需要index = r12,因此如果 0: 41 8b 03 mov eax,DWORD PTR [r11] 3: 41 8b 04 24 mov eax,DWORD PTR [r12] # needs a SIB like RSP 7: 41 8b 45 00 mov eax,DWORD PTR [r13+0x0] # needs a disp8 like RBP b: 41 8b 06 mov eax,DWORD PTR [r14] e: 41 8b 07 mov eax,DWORD PTR [r15] 11: 43 8b 04 e3 mov eax,DWORD PTR [r11+r12*8] # *can* be an index 不完全通用,会使AMD64成为更糟糕的编译器目标。

qazxswpoi

当所有寄存器都可以用于任何事情时,编译器喜欢它,只限制一些特殊情况操作的寄存器分配。这就是寄存器正交性的含义。

© www.soinside.com 2019 - 2024. All rights reserved.