繁体   English   中英

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

[英]Why are rbp and rsp called general purpose registers?

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

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

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

感谢您的帮助。

通用意味着所有这些寄存器都可以与任何使用通用寄存器进行计算的指令一起使用,而例如,您不能对指令指针 (RIP) 或标志寄存器 (RFLAGS) 做任何您想做的事情。

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

如果您需要将它们用于自己的目的,您应该先保存它们的内容,然后再将其他东西存储在其中,并在完成后将它们恢复到原始值。

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

在计算机体系结构中,CPU 在内部处理整数寄存器/指令与 FP/SIMD 寄存器/指令是很常见的。 例如,英特尔 Sandybridge 系列 CPU具有单独的物理寄存器文件,用于重命名 GP 整数与 FP/向量寄存器。 这些被简称为整数与 FP 寄存器文件。 (其中 FP 是内核不需要保存/恢复以使用 GP 寄存器同时保持用户空间的 FPU/SIMD 状态不变的所有内容的简写。)FP 寄存器文件中的每个条目都是 256 位宽(以保存一个 AVX ymm 向量),但整数寄存器文件条目只需为 64 位宽。

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


这种用法中的“通用目的”是指“数据或地址”,而不是像 m68k 这样的 ISA,其中有 d0..7 数据寄存器和 a0..7 地址寄存器,所有 16 个都是整数寄存器。 不管寄存器通常如何使用,通用是关于它可以如何使用。


每个寄存器对于某些指令都有一些特殊性,除了一些用 x86-64 添加的全新寄存器:R8-R15。 这些并没有取消它们作为通用用途资格(低 16 个)原始 8 可以追溯到 8086,即使在原始 8086 中也隐含使用它们中的每一个。

对于 RSP,它对于 push/pop/call/ret 是特殊的,所以大多数代码从不将它用于其他任何事情。 (在内核模式下,异步用于中断,所以你真的不能把它藏在某个地方来获得额外的 GP 寄存器,就像在用户空间代码中一样: ESP 和 EAX 一样通用吗?

但是在受控条件下(例如没有信号处理程序),您不必将 RSP 用于堆栈指针。 例如,您可以使用它在带有 pop 的循环中读取数组,就像在此代码高尔夫答案中一样 (我实际上在 32 位代码中使用了esp ,但同样的区别: pop比 Skylake 上的lodsd快,而两者都是 1 个字节。)


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

另请参阅x86 程序集 - 为什么 [e]bx 保留在调用约定中? 部分列表。

我主要将其限制为用户空间指令,尤其是现代编译器实际上可能从 C 或 C++ 代码发出的指令。 我不想详尽无遗地介绍有很多隐含用途的 reg。

  • rax : 单操作数 [i]mul / [i]div / cdq / cdqe, 字符串指令 (stos), cmpxchg等。以及许多即时指令的特殊短编码,如 2 字节cmp al, 1或5 字节add eax, 12345 (无 ModRM 字节)。 另请参阅codegolf.SE 在 x86/x64 机器代码中打高尔夫球的技巧

    还有xchg -with-eax 这是0x90 nop来源(在nop成为 x86-64 中单独记录的指令之前,因为xchg eax,eax零扩展 eax 到 RAX,因此不能使用0x90编码。但是xchg rax,rax仍然可以组装成 REX.W=1 0x90。)

  • rcx : 移位计数、 rep字符串计数、 loop指令

  • rdx : rdx:rax用于除法和乘法,cwd / cdq / cqo 为它们设置。 rdtsc BMI2 mulx

  • rbx :8086 xlatb cpuid使用 EAX..EDX 的所有四个。 486 cmpxchg8b , x86-64 cmpxchg16b 大多数 32 位编译器将为std::atomic<long long>::compare_exchange_weak发出cmpxchg8 (纯加载/纯存储可以使用 SSE MOVQ 或 x87 fild/fistp,不过,如果面向 Pentium 或更高版本。)64 位编译器将使用 64 位lock cmpxchg ,而不是 cmpxchg8b。

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

  • rsi / rdi : 字符串操作,包括某些编译器有时内联的rep movsb (在某些情况下,gcc 还为字符串文字内联rep cmpsb ,但这可能不是最佳的)。

  • rbpleave (仅比mov rsp, rbp / pop rbp慢 1 uop。gcc 实际上在带有帧指针的函数中使用它,当它不能只是pop rbp )。 还有没有人使用过的极其缓慢的enter

  • rsp :堆栈操作:push/pop/call/ret 和leave (并enter )。 并且在内核模式(不是用户空间)中由硬件异步使用来保存中断上下文。 这就是内核代码不能有红区的原因。

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

寻址模式编码特殊情况:

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

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

rsp / r12作为基址寄存器总是需要一个 SIB 字节。 (base=RSP 的 ModR/M 编码是转义码,用于表示 SIB 字节,同样,如果r12处理方式不同,则更多的解码器将不得不关心 REX 前缀)。

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

r12可以是索引寄存器 与其他情况不同,这不会影响指令长度解码。 此外,它不能像其他情况一样使用更长的编码来解决。 AMD 希望 AMD64 的寄存器组尽可能正交,因此他们花一些额外的晶体管来检查 REX.X 作为索引/无索引解码的一部分是有道理的。 例如, [rsp + r12*4]需要 index=r12,因此如果r12不是完全通用的,会使 AMD64 成为更糟糕的编译器目标。

   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

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

取消引用 rbp 可能会导致 #SS(stack segment) 错误。

最近,我遇到了一个带有“堆栈段错误”的 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段寄存器,但并未将其用作堆栈帧基指针。

根据英特尔 SDMv1 3.4.1 通用寄存器:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM