为什么Windows64在x86-64上使用来自所有其他操作系统的不同调用约定?
AMD有一个ABI规范,描述了在x86-64上使用的调用约定。所有操作系统都遵循它,除了具有自己的x86-64调用约定的Windows。为什么?
有没有人知道这种差异的技术,历史或政治原因,还是纯粹是NIH综合症的问题?
我知道不同的操作系统可能对更高级别的东西有不同的需求,但这并不能解释为什么例如Windows上的寄存器参数传递顺序是
rcx - rdx - r8 - r9 - rest on stack
而其他人都使用rdi - rsi - rdx - rcx - r8 - r9 - rest on stack
。
附:我知道这些调用约定一般是如何不同的,如果需要,我知道在哪里可以找到详细信息。我想知道的是为什么。
编辑:有关方法,请参阅例如维基百科条目和那里的链接。
没有找到相关结果
已邀请:
4 个回复
呈辖玫割善
,
,
,
,
,
,
,
。 因此,选择A / C / D(regs 0..2)作为返回值,前两个参数(即“经典”32位
约定)是一个合理的选择。就64位而言,订购了“更高”的regs,微软和UN * X / Linux都是第一个
/
。 记住这一点,如果你选择四个参数寄存器,微软选择
(返回值)和
,
,
,
(arg [0..3])是一个可以理解的选择。 我不知道为什么AMD64 UN * X ABI在
之前选择了
。 在x64上选择六个参数寄存器 - 特定于UN * X. 在RISC体系结构上,UN * X传统上在寄存器中进行了参数传递 - 特别是对于前六个参数(至少在PPC,SPARC,MIPS上)。这可能是AMD64(UN * X)ABI设计人员选择在该架构上使用六个寄存器的主要原因之一。 所以如果你想要六个寄存器来传递参数,那么选择
,
,
和
是合乎逻辑的,其中两个应该选择? “更高”的regs需要一个额外的指令前缀字节来选择它们,因此具有更大的指令大小,所以如果你有选项,你不会想要选择其中任何一个。在经典寄存器中,由于
和
的隐含含义,这些都不可用,并且
传统上特别用于UN * X(全局偏移表),似乎AMD64 ABI设计者不希望不必要地变得不兼容用。 因此,唯一的选择是
/
。 所以如果你必须把
/
作为参数寄存器,它们应该是哪个参数? 使它们成为
和
有一些优点。见cHao的评论。
和
是字符串指令源/目标操作数,正如cHao所提到的,它们用作参数寄存器意味着使用AMD64 UN * X调用约定,最简单的
函数,例如,只包含两个CPU指令
,因为源/目标地址已被调用者放入正确的寄存器中。特别是在低级和编译器生成的“粘合”代码中(例如,想想一些C ++堆分配器在构造上零填充对象,或者在
上的内核零填充堆页面,或者写入时复制) pagefaults)大量的块复制/填充,因此对于经常用于保存两个或三个CPU指令的代码非常有用,否则这些指令会将这些源/目标地址参数加载到“正确”的寄存器中。 所以在某种程度上,UN * X和Win64的区别在于UN * X“预先”有两个额外的参数,在有意选择的
/
寄存器中,自然选择
,
,
和
中的四个参数。 除此之外 ... UN * X和Windows x64 ABI之间存在更多差异,而不仅仅是将参数映射到特定寄存器。有关Win64的概述,请检查: http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx Win64和AMD64 UN * X在使用堆栈空间的方式上也有惊人的不同;例如,在Win64上,调用者必须为函数参数分配堆栈空间,即使args 0 ... 3在寄存器中传递。另一方面,在UN * X上,如果一个叶子函数需要不超过128个字节,那么它甚至根本不需要分配堆栈空间(是的,你拥有并且可以使用它)一定数量的堆栈而不分配它...好吧,除非你是内核代码,一个漂亮的bug的来源)。所有这些都是特别的优化选择,大多数理由都在原始海报的维基百科参考指向的完整ABI参考文献中进行了解释。
芯伶句餐绕
。 此外,来自内核开发人员的反馈确定了使
和
的原始设计无法使用的事情。这就是AMD在发布任何实际芯片之前更新指令以解决这个问题的方法。同样有趣的是,在2000年末,英特尔可能不会采用AMD64。 SysV(Linux)调用约定,以及关于应该保留被调用者数量的寄存器与调用者保存的决定,最初是在2000年11月由Jan Hubicka(gcc开发人员)制作的。他编译了SPEC2000并查看了代码大小和指令数量。这个讨论主题围绕着一些与SO问题的答案和评论相同的想法反弹。在第二个线程中,他提出当前序列是最优的并且希望是最终的,产生比一些替代品更小的代码。 他使用术语“全局”来表示调用保留的寄存器,如果使用则必须按下/弹出。 选择
,
,
作为前三个args的动机是: 在其args上调用
或其他C字符串函数的函数中的次要代码大小保存(其中gcc内联一个rep字符串操作?)
是呼叫保留的,因为有两个没有REX前缀(rbx和rbp)的呼叫保留的regs是一个胜利。大概选择因为它是唯一没有被任何指令隐含使用的其他reg。 (rep字符串,移位计数和mul / div输出/输入触摸其他所有内容)。 没有具有特殊用途的寄存器是调用保留的(参见上一点),因此想要使用rep字符串指令或变量计数移位的函数可能必须将函数args移动到其他地方,但不必保存/恢复来电者的价值。 我们试图在序列的早期避免使用RCX,因为它是寄存器 通常用于特殊目的,如EAX,因此它具有相同的目的 在序列中缺失。 它也不能用于系统调用,我们想制作系统调用序列 尽可能匹配函数调用序列。 (背景:
/
不可避免地破坏
(
)和
(
),所以当
跑时,内核无法看到
中最初的内容。) 选择内核系统调用ABI来匹配函数调用ABI,除了
而不是
,所以像
这样的libc包装函数可以只是
/
/
。 请注意,与Window的32位__vectorcall相比,i386 Linux使用的SysV调用约定很糟糕。它传递堆栈中的所有内容,并且仅在
中返回int64,而不是小结构。毫不奇怪,为保持与它的兼容性做了很多努力。当没有理由不这样做的时候,他们做了保持
呼叫保留的事情,因为他们认为在原来的8中有另一个(不需要REX前缀)是好的。 使ABI最优化比任何其他考虑更长远。我认为他们做得很好。我不完全确定返回打包到寄存器中的结构,而不是不同的regs中的不同字段。我想在没有实际操作字段的情况下按值传递它们的代码会以这种方式获胜,但是解压缩的额外工作看起来很愚蠢。它们可能有更多的整数返回寄存器,不仅仅是
,所以返回一个包含4个成员的结构可以在rdi,rsi,rdx,rax或其他东西中返回它们。 他们考虑在向量寄存器中传递整数,因为SSE2可以对整数进行操作。幸运的是他们没有这样做。整数经常用作指针偏移,而堆栈内存的往返非常便宜。 SSE2指令也比整数指令占用更多的代码字节。 我怀疑Windows ABI设计人员可能一直致力于最大限度地减少32位和64位之间的差异,以便为那些必须从一个端口移植到另一个端口的人们的利益,或者可以在某些ASM中使用几个
,以便更容易构建相同的源一个32或64位版本的函数。 最小化工具链的变化似乎不太可能。 x86-64编译器需要一个单独的表,其中哪个寄存器用于什么,以及调用约定是什么。与32位的小重叠不太可能显着节省工具链代码大小/复杂性。
茂坦湿床够
香腔弥胯瓤