根据Intel在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被称为通用的呢?
谢谢你的帮助。
3条答案
按热度按时间i2byvkas1#
通用意味着所有这些寄存器都可以与使用通用寄存器执行计算的任何指令一起使用,例如,您不能对指令指针(RIP)或标志寄存器(RFLAGS)执行任何操作。
这些寄存器中的一些被设想用于特定用途,并且通常是这样。最关键的是RSP和RBP。
如果您需要将它们用于您自己的目的,您应该在将其他内容存储在其中之前保存它们的内容,并在完成后将它们恢复为原始值。
vd8tlhqk2#
如果寄存器可以作为
add
的操作数,或用于寻址模式,则它是“通用”寄存器,与FS
段寄存器或RIP等寄存器相反。GP寄存器也称为“整数寄存器”,尽管其他类型的寄存器也可以保存整数。在计算机体系结构中,CPU通常在内部处理与FP/SIMD寄存器/指令分开的整数寄存器/指令。例如,Intel Sandybridge-family CPUs具有单独的物理寄存器文件,用于重命名GP整数与FP/向量寄存器。这些简单地称为整数与FP寄存器文件。(其中FP是内核不需要保存/恢复以使用GP寄存器而不改变用户空间的FPU/SIMD状态的所有内容的简写。)FP寄存器文件中的每个条目都是256位宽(用于保存AVX ymm矢量),但整数寄存器文件条目仅需为64位宽1。
但是,当我们说“整数寄存器”时,我们通常特指通用寄存器。
注一:实际上,一个典型的设计是整数PRF条目为FLAGS结果和/或GP寄存器留有空间,因此可能是70位。由于整数指令也写入FLAGS,因此将它们放在一起,而不是从单独的小寄存器表中分配,这是有意义的。(寄存器分配表于是将仅具有2个额外的条目,一个用于CF,一个用于FLAGS、SPAZO组的其余部分,)在重命名段寄存器(Skylake does not)的CPU上,我猜这些 * 将 * 放入整数PRF条目中。
至于内核在中断和系统调用时保存/恢复的用户空间任务的体系结构状态的整数部分,这将包括它的RFLAGS和RIP。(通常只是不触及FP状态。)
在这种用法中,“通用”意味着“数据或地址”,而不是像m68 k这样的伊萨,其中有d0..7个数据寄存器和a0..7个地址寄存器,所有16个寄存器都是整数寄存器。无论寄存器“通常”是如何使用的,“通用”是指它“可以"如何使用。
每个寄存器对于某些指令都有一些特殊性,除了一些随x86-64添加的全新寄存器:R8-R15。这些并不妨碍它们作为通用(低16个)原始8可以追溯到8086,甚至在原始8086中也隐含使用了它们中的每一个。
对于RSP来说,它是push/pop/call/ret的专用寄存器,因此大多数代码不会将它用于其他任何操作。(在内核模式中,它异步用于中断,因此您确实不能像在用户空间代码中那样,将它隐藏在某个地方以获得额外的GP寄存器:(Is ESP as general-purpose as EAX?)
但是在受控条件(比如没有信号处理程序)中,你不必使用RSP作为堆栈指针。例如,你可以使用它在一个循环中读取一个数组,并弹出,就像在这个代码高尔夫答案中一样。(我实际上在32位代码中使用了
esp
,但有相同的区别:在Skylake上,pop
比lodsd
快,但两者都是1字节。)每个寄存器的隐式使用和特殊性:
另请参阅x86 Assembly - Why is [e]bx preserved in calling conventions?以取得部分清单。
我主要将其限制在用户空间指令,尤其是现代编译器可能实际从C或C++代码中发出的指令。我并不试图详尽地介绍具有大量隐式使用的regs。
rax
:单操作数[i]穆尔/ [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 0x 90。)rcx
:移位计数,rep
-string计数,the slowloop
instructionrdx
:rdx:rax
用于除法与扩大乘法(单操作数形式),cwd
/cdq
/cqo
用于设置idiv
.还有rdtsc
与BMI2mulx
.rbx
:8086xlatb
.cpuid
使用EAX..EDX. 486cmpxchg8b
、x86-64cmpxchg16b
的所有四个字符。大多数32位编译器将为std::atomic<long long>::compare_exchange_weak
发出cmpxchg8
。(不过,如果目标是奔腾或更高版本,则纯加载/纯存储可以使用SSE MOVQ或x87 fild/factp。)64位编译器将使用64位lock cmpxchg
,而不是cmpxchg 8b。一些64位编译器会为
atomic<struct_16_bytes>
发出cmpxchg16b
。RBX隐式使用原始8的次数最少,但lock cmpxchg16b
是少数几个会实际使用的编译器之一。rsi
/rdi
:字符串操作,包括一些编译器有时内联的rep movsb
。(gcc在某些情况下也内联rep cmpsb
用于字符串文字,但这可能不是最佳的)。rbp
:leave
(只比mov rsp, rbp
/pop rbp
慢1个uop。gcc实际上在带有帧指针的函数中使用它,而它不能只使用pop rbp
)。还有非常慢的enter
,没有人使用过。rsp
:堆栈操作:push/pop/call/ret和leave
。(和enter
)。在内核模式(不是用户空间)中,硬件异步使用以保存中断上下文。这就是内核代码不能有红色区域的原因。r11
:syscall
/sysret
使用它来保存/恢复用户空间的RFLAGS。(与RCX沿着保存/恢复用户空间的RIP)。寻址模式编码特殊情况:
(See还有rbp not allowed as SIB base?,它只是关于寻址模式,我在这里复制了这部分答案。)
rbp
/r13
不能是无位移的基址寄存器:该编码表示:(在调制RM中)rel32
(RIP相对),或(在SIB中)disp32
,无基址寄存器。(r13
在ModRM/SIB中使用相同的3个位,因此,此选择通过使指令尺子解码器不查看雷克斯.B位来获取第4个基址寄存器位,从而简化了解码).[r13]
汇编为[r13 + disp8=0]
.[r13+rdx]
汇编为[rdx+r13]
(通过交换基址/索引来避免此问题(如果可以))。rsp
/r12
作为基址寄存器总是需要SIB字节。(base=RSP的ModR/M编码是用于发信号通知SIB字节的转义码,并且同样,如果r12
被不同地处理,则更多的解码器将不得不关心雷克斯前缀)。rsp
不能是变址寄存器。这样就可以对[rsp]
进行编码,其比X1 M67 N1 X更有用。(英特尔可能已经为32位寻址模式(386中的新功能)设计了ModRM/SIB编码,因此无索引SIB只能在base= ESP的情况下使用。这将使[eax + esp*4]
成为可能,但仅排除[esp + esp*1/2/4/8]
。但这并没有什么用,所以他们简化了硬件,把index=ESP作为不考虑基址的无索引代码,这样就有了两种冗余的方式来编码任何基址或基址+disp寻址模式:具有或不具有SIB。)r12
* 可以 * 是索引寄存器。与其他情况不同,这不会影响指令长度解码。而且,它不能像其他情况那样使用更长的编码。AMD希望AMD 64的寄存器集尽可能正交,因此他们将花费一些额外的晶体管来检查雷克斯.X作为索引/无索引解码的一部分是有意义的。例如,[rsp + r12*4]
要求index=r12,因此如果r12
不是完全通用的,则会使AMD 64成为更差的编译器目标。编译器喜欢所有的寄存器都可以用于任何事情,只有少数特殊情况的操作限制了寄存器的分配。这就是寄存器正交性的含义。
du7egjpx3#
取消引用rbp可能会导致#SS(堆栈段)错误。
最近,我遇到了一个linux内核崩溃与'堆栈段故障'。
rbp中的内存地址是非规范地址。这就是此崩溃的原因。我从此崩溃中了解到,访问rbp会隐式访问ss段寄存器,即使rbp未用作堆栈帧基指针。
根据英特尔SDMv 1 3.4.1通用寄存器:
EBP -指向堆栈上数据的指针(在SS段中)