根据英特尔在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被称为通用目的?
感谢您的帮助。
答案 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 rdx
:rdx:rax
被除法和乘法使用,而cwd / cdq / cqo用于为其设置。 rdtsc
。 BMI2 mulx
。 rbx
:8086 xlatb
。 cpuid
使用全部四个EAX..EDX。 486 cmpxchg8b
,x86-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
来表示字符串文字,但这可能不是最佳选择。)rbp
:leave
(仅比mov rsp, rbp
/ pop rbp
慢1uop。gcc实际上不能在具有帧指针的函数中使用它,{ {1}})。也是pop rbp
令人难以置信的缓慢{one}。 enter
:堆栈操作:push / pop / call / reg和rsp
。 (还有leave
)。并且在内核模式(不是用户空间)中,硬件异步使用它来保存中断上下文。这就是为什么内核代码不能有红色区域的原因。
enter
:r11
/ 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段中)