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

时间:2016-04-10 12:02:22

标签: assembly x86-64 cpu-registers

根据英特尔在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被称为通用目的?

感谢您的帮助。

2 个答案:

答案 0 :(得分:7)

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

在计算机体系结构中,CPU在内部通常将整数寄存器/指令与FP / SIMD寄存器/指令分开处理。例如Intel Sandybridge-family CPUs具有单独的物理寄存器文件,用于重命名GP整数和FP /向量寄存器。这些简称为整数与FP寄存器文件。 (在这里FP是内核不需要保存/恢复即可使用GP寄存器的所有事物的简写,而保留用户空间的FPU / SIMD状态不变。)FP寄存器文件中的每个条目都是256位宽(包含一个AVX ymm向量),但整数寄存器文件条目仅必须为64位宽。

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


每个寄存器在某些指令上都有某些特殊性,但x86-64中添加了一些全新的寄存器:R8-R15。这些不会使它们失去通用的资格。(原始的8个(低16个)可以追溯到8086年,即使在原始的8086中也隐含了它们的用法。

对于RSP,它特别适用于push / pop / call / ret,因此大多数代码从不将其用于其他任何用途。 (并且在内核模式下,异步用于中断,因此您实际上无法像在用户空间代码:Is ESP as general-purpose as EAX?中那样,将其存储在某个地方以获取额外的GP寄存器)

但是在受控条件下(就像没有信号处理程序一样),您不必对堆栈指针使用RSP。例如您可以使用它读取弹出循环中的数组,例如in this code-golf answer。 (我实际上在32位代码中使用了esp,但存在相同的区别:pop比Skylake上的lodsd快,而两者均为1字节。)


每个寄存器的隐式用法和特殊性:

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

我主要将其限制为用户空间指令,尤其是现代编译器实际上可能从C或C ++代码发出的指令。对于具有很多隐式用法的reg,我不会尝试穷举。

  • 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-带有传真,它是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被除法和乘法使用,而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。)64位编译器将使用64位lock cmpxchg,而不是cmpxchg8b。

    某些64位编译器将为cmpxchg16b发出atomic<struct_16_bytes>。 RBX具有原始8的最少隐式用法,但是lock cmpxchg16b是少数几个实际使用的编译器之一。

  • rsi / rdi:字符串操作,包括rep movsb,某些编译器有时会内联。 (在某些情况下,gcc还会内联rep cmpsb来表示字符串文字,但这可能不是最佳选择。)
  • rbpleave(仅比mov rsp, rbp / pop rbp慢1uop。gcc实际上不能在具有帧指针的函数中使用它,{ {1}})。也是pop rbp令人难以置信的缓慢{one}。
  • enter:堆栈操作:push / pop / call / reg和rsp。 (还有leave)。并且在内核模式(不是用户空间)中,硬件异步使用它来保存中断上下文。这就是为什么内核代码不能有红色区域的原因。

  • enterr11 / syscall使用它来保存/恢复用户空间的RFLAGS。 (与RCX一起保存/恢复用户空间的RIP)。

寻址模式编码的特殊情况:

(另请参阅rbp not allowed as SIB base?,这是关于寻址模式的内容,我在其中复制了此答案的这一部分。)

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

[rdx+r13] / rsp作为基址寄存器始终需要一个SIB字节。 (base = RSP的ModR / M编码是转义码,用于向SIB字节发送信号,如果r12的处理方式不同,则更多的解码器将不得不关心REX前缀。)

r12不能是索引寄存器。这样就可以对rsp进行编码,该编码比[rsp]更有用。 (英特尔本可以为32位寻址模式(386中的新增功能)设计ModRM / SIB编码,因此只有在base = ESP时才可以使用没有索引的SIB。这将使[rsp + rsp]成为可能,并且仅排除{ {1}},但这没有用,因此他们通过使index = ESP成为不带索引的代码来简化硬件,而不考虑基础,这允许两种冗余方式来编码任何基础或base + disp寻址模式:有或没有SIB。)

[eax + esp*4] 可以是索引寄存器。与其他情况不同,这不会影响指令长度解码。而且,不能像其他情况一样使用更长的编码来解决。 AMD希望AMD64的寄存器组尽可能正交,因此在索引/无索引解码中,他们花了一些额外的晶体管来检查REX.X才有意义。例如,[esp + esp*1/2/4/8]需要index = r12,因此r12的用途不完全会导致AMD64成为较差的编译器目标。

[rsp + r12*4]

当所有寄存器 可以用于任何东西时,编译器都喜欢它,仅限制了一些特殊情况下的寄存器分配。这就是寄存器正交性。

答案 1 :(得分:1)

取消引用rbp可能会导致#SS(堆栈段)错误。

最近,我遇到了“堆栈段错误”,导致Linux内核崩溃。

crash> dmesg
[...]
stack segment: 0000 [#1] SMP
[...]
RIP: 0010:[<ffffffff8125fa8b>]  lock_get_status+0x9b/0x3b0
RSP: 0018:ffff89954a317d90  EFLAGS: 00010282
[...]
RBP: 800000fa8c251867 R08: 0000000000001000 R09: 000000000000ffff
[...]
crash> dis lock_get_status+0x9b
0xffffffff8125fa8b <lock_get_status+0x9b>:      mov    0x28(%rbp),%rax

rbp 中的内存地址是非规范地址。这就是这次崩溃的原因。 我从这次崩溃中了解到,即使通过 rbp ,访问 rbp 隐式访问 ss 段寄存器也不会用作堆栈帧基址指针。< / p>

根据Intel SDMv1 3.4.1通用寄存器:

EBP —指向堆栈中数据的指针(在SS段中)