在 x64 上选择四个参数寄存器 - UN*X / Win64 通用
关于 x86 需要记住的一件事是,“reg number”编码的寄存器名称并不明显;就指令编码而言(MOD R/M字节,请参见http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm),寄存器编号 0...7 是 - 按此顺序 - ?AX
, ?CX
, ?DX
, ?BX
, ?SP
, ?BP
, ?SI
, ?DI
.
因此,选择 A/C/D (regs 0..2) 作为返回值和前两个参数(这是“经典”32 位__fastcall
约定)是一个合乎逻辑的选择。就 64 位而言,“更高”的 reg 是有序的,Microsoft 和 UN*X/Linux 都选择R8
/R9
作为第一个。
请记住,如果您选择四个寄存器作为参数,Microsoft 选择RAX
(return value) 和RCX
, RDX
, R8
, (arg[0..3]) 是可以理解的选择。R9
不知道RDX
之前为什么选择AMD64 UN*X ABI RCX
。
在 x64 上选择六个参数寄存器 - 特定于 UN*X
在 RISC 架构上,UN*X 传统上会在寄存器中传递参数 - 特别是对于前六个参数(至少在 PPC、SPARC、MIPS 上也是如此)。这可能是 AMD64 (UN*X) ABI 设计人员选择在该架构上使用六个寄存器的主要原因之一。
因此,如果您想要六个寄存器来传递参数,并且选择RCX
,和其中四个是合乎逻辑的,那么您应该选择另外两个RDX
?R8
R9
“更高”的 regs 需要一个额外的指令前缀字节来选择它们,因此具有更大的指令大小占用空间,因此如果您有选项,您不会想要选择其中的任何一个。在经典寄存器中,由于和这些不可用的隐含含义,并且传统上对 UN*X(全局偏移表)有特殊用途,似乎 AMD64 ABI 设计人员不想不必要地与之不兼容。
因此,唯一的选择是/ 。RBP
RSP
RBX
RSI
RDI
因此,如果您必须将RSI
/RDI
作为参数寄存器,它们应该是哪些参数?
制作它们arg[0]
并arg[1]
具有一些优势。见 cHao 的评论。
?SI
并且?DI
是字符串指令源/目标操作数,正如 cHao 所提到的,它们用作参数寄存器意味着使用 AMD64 UN*X 调用约定,strcpy()
例如,最简单的可能函数仅包含两条 CPU 指令repz movsb; ret
,因为源/目标调用者已将地址放入正确的寄存器中。特别是在低级和编译器生成的“胶水”代码中(例如,一些 C++ 堆分配器在构造时零填充对象,或者内核零填充堆页面在sbrk()
,或写时复制页面错误)大量的块复制/填充,因此它对于经常用于保存两个或三个 CPU 指令的代码很有用,否则这些指令会将此类源/目标地址参数加载到“正确”的寄存器。
因此,在某种程度上,UN*X 和 Win64 的不同之处仅在于 UN*X 在有目的地选择的RSI
/寄存器中将两个附加参数“附加”到、和RDI
中的四个参数的自然选择中。RCX
RDX
R8
R9
除此之外 ...
UN*X 和 Windows x64 ABI 之间的区别不仅仅是将参数映射到特定寄存器。有关 Win64 的概述,请查看:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64 和 AMD64 UN*X 在堆栈空间的使用方式上也有显着差异;例如,在 Win64 上,调用者必须为函数参数分配堆栈空间,即使参数 0...3 在寄存器中传递。另一方面,在 UN*X 上,如果叶函数(即不调用其他函数)需要不超过 128 个字节(是的,您拥有并且可以使用一定数量的堆栈而不分配它......好吧,除非你是内核代码,一个漂亮的错误的来源)。所有这些都是特定的优化选择,其中的大部分基本原理都在原始发布者的维基百科参考指向的完整 ABI 参考中进行了解释。