为什么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
。 附:我知道这些调用约定一般是如何不同的,如果需要,我知道在哪里可以找到详细信息。我想知道的是为什么。 编辑:有关方法,请参阅例如维基百科条目和那里的链接。     
已邀请:
在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位而言,订购了“更高”的regs,微软和UN * X / Linux都是第一个
R8
/
R9
。 记住这一点,如果你选择四个参数寄存器,微软选择
RAX
(返回值)和
RCX
RDX
R8
R9
(arg [0..3])是一个可以理解的选择。 我不知道为什么AMD64 UN * X ABI在
RCX
之前选择了
RDX
。 在x64上选择六个参数寄存器 - 特定于UN * X. 在RISC体系结构上,UN * X传统上在寄存器中进行了参数传递 - 特别是对于前六个参数(至少在PPC,SPARC,MIPS上)。这可能是AMD64(UN * X)ABI设计人员选择在该架构上使用六个寄存器的主要原因之一。 所以如果你想要六个寄存器来传递参数,那么选择
RCX
RDX
R8
R9
是合乎逻辑的,其中两个应该选择? “更高”的regs需要一个额外的指令前缀字节来选择它们,因此具有更大的指令大小,所以如果你有选项,你不会想要选择其中任何一个。在经典寄存器中,由于
RBP
RSP
的隐含含义,这些都不可用,并且
RBX
传统上特别用于UN * X(全局偏移表),似乎AMD64 ABI设计者不希望不必要地变得不兼容用。 因此,唯一的选择是
RSI
/
RDI
。 所以如果你必须把
RSI
/
RDI
作为参数寄存器,它们应该是哪个参数? 使它们成为
arg[0]
arg[1]
有一些优点。见cHao的评论。
?SI
?DI
是字符串指令源/目标操作数,正如cHao所提到的,它们用作参数寄存器意味着使用AMD64 UN * X调用约定,最简单的
strcpy()
函数,例如,只包含两个CPU指令
repz movsb; ret
,因为源/目标地址已被调用者放入正确的寄存器中。特别是在低级和编译器生成的“粘合”代码中(例如,想想一些C ++堆分配器在构造上零填充对象,或者在
sbrk()
上的内核零填充堆页面,或者写入时复制) pagefaults)大量的块复制/填充,因此对于经常用于保存两个或三个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上,调用者必须为函数参数分配堆栈空间,即使args 0 ... 3在寄存器中传递。另一方面,在UN * X上,如果一个叶子函数需要不超过128个字节,那么它甚至根本不需要分配堆栈空间(是的,你拥有并且可以使用它)一定数量的堆栈而不分配它...好吧,除非你是内核代码,一个漂亮的bug的来源)。所有这些都是特别的优化选择,大多数理由都在原始海报的维基百科参考指向的完整ABI参考文献中进行了解释。     
IDK为什么Windows做了他们做的事情。请参阅此答案的结尾以进行猜测。我很好奇SysV调用约定是如何决定的,所以我挖掘了邮件列表存档并发现了一些简洁的东西。 阅读AMD64邮件列表中的一些旧线程很有意思,因为AMD架构师积极参与其中。例如选择寄存器名称是其中一个难点:AMD考虑重命名原来的8个寄存器r0-r7,或者调用新的寄存器,如
UAX
。 此外,来自内核开发人员的反馈确定了使
syscall
swapgs
的原始设计无法使用的事情。这就是AMD在发布任何实际芯片之前更新指令以解决这个问题的方法。同样有趣的是,在2000年末,英特尔可能不会采用AMD64。 SysV(Linux)调用约定,以及关于应该保留被调用者数量的寄存器与调用者保存的决定,最初是在2000年11月由Jan Hubicka(gcc开发人员)制作的。他编译了SPEC2000并查看了代码大小和指令数量。这个讨论主题围绕着一些与SO问题的答案和评论相同的想法反弹。在第二个线程中,他提出当前序列是最优的并且希望是最终的,产生比一些替代品更小的代码。 他使用术语“全局”来表示调用保留的寄存器,如果使用则必须按下/弹出。 选择
rdi
rsi
rdx
作为前三个args的动机是: 在其args上调用
memset
或其他C字符串函数的函数中的次要代码大小保存(其中gcc内联一个rep字符串操作?)
rbx
是呼叫保留的,因为有两个没有REX前缀(rbx和rbp)的呼叫保留的regs是一个胜利。大概选择因为它是唯一没有被任何指令隐含使用的其他reg。 (rep字符串,移位计数和mul / div输出/输入触摸其他所有内容)。 没有具有特殊用途的寄存器是调用保留的(参见上一点),因此想要使用rep字符串指令或变量计数移位的函数可能必须将函数args移动到其他地方,但不必保存/恢复来电者的价值。   我们试图在序列的早期避免使用RCX,因为它是寄存器   通常用于特殊目的,如EAX,因此它具有相同的目的   在序列中缺失。   它也不能用于系统调用,我们想制作系统调用序列   尽可能匹配函数调用序列。 (背景:
syscall
/
sysret
不可避免地破坏
rcx
rip
)和
r11
RFLAGS
),所以当
syscall
跑时,内核无法看到
rcx
中最初的内容。) 选择内核系统调用ABI来匹配函数调用ABI,除了
r10
而不是
rcx
,所以像
mmap(2)
这样的libc包装函数可以只是
mov %rcx, %r10
/
mov $0x9, %eax
/
syscall
。 请注意,与Window的32位__vectorcall相比,i386 Linux使用的SysV调用约定很糟糕。它传递堆栈中的所有内容,并且仅在
edx:eax
中返回int64,而不是小结构。毫不奇怪,为保持与它的兼容性做了很多努力。当没有理由不这样做的时候,他们做了保持
rbx
呼叫保留的事情,因为他们认为在原来的8中有另一个(不需要REX前缀)是好的。 使ABI最优化比任何其他考虑更长远。我认为他们做得很好。我不完全确定返回打包到寄存器中的结构,而不是不同的regs中的不同字段。我想在没有实际操作字段的情况下按值传递它们的代码会以这种方式获胜,但是解压缩的额外工作看起来很愚蠢。它们可能有更多的整数返回寄存器,不仅仅是
rdx:rax
,所以返回一个包含4个成员的结构可以在rdi,rsi,rdx,rax或其他东西中返回它们。 他们考虑在向量寄存器中传递整数,因为SSE2可以对整数进行操作。幸运的是他们没有这样做。整数经常用作指针偏移,而堆栈内存的往返非常便宜。 SSE2指令也比整数指令占用更多的代码字节。 我怀疑Windows ABI设计人员可能一直致力于最大限度地减少32位和64位之间的差异,以便为那些必须从一个端口移植到另一个端口的人们的利益,或者可以在某些ASM中使用几个
#ifdef
,以便更容易构建相同的源一个32或64位版本的函数。 最小化工具链的变化似乎不太可能。 x86-64编译器需要一个单独的表,其中哪个寄存器用于什么,以及调用约定是什么。与32位的小重叠不太可能显着节省工具链代码大小/复杂性。     
Win32有自己的ESI和EDI用途,并且要求它们不被修改(或者至少在调用API之前它们被恢复)。我想象64位代码对RSI和RDI的作用是一样的,这可以解释为什么它们不用于传递函数参数。 我不知道为什么RCX和RDX会被切换。     
请记住,微软最初“对AMD64早期的努力正式不承认”(来自Matthew Kerner和Neil Padgett的“现代64位计算历史”),因为他们是英特尔在IA64架构上的强大合作伙伴。我认为这意味着即使他们原本愿意与ABCC的GCC工程师合作在Unix和Windows上使用它们,他们也不会这样做,因为它意味着当他们没有公开支持AMD64的努力时尚未正式完成(并且可能会让英特尔感到不安)。 最重要的是,在那些日子里,微软绝对没有倾向于与开源项目保持友好关系。当然不是Linux或GCC。 那他们为什么要在ABI上合作呢?我猜这些ABI之所以不同,仅仅是因为它们的设计或多或少是在同一时间并且是孤立的。 另一句话来自“现代64位计算史”:   与微软的合作并行,AMD也参与其中   开源社区为筹码做准备。 AMD与之签约   用于工具链工作的Code Sorcery和SuSE(Red Hat已经是   英特尔参与IA64工具链端口)。拉塞尔解释说   SuSE制作了C和FORTRAN编译器,Code Sorcery制作了一个   Pascal编译器。韦伯解释说,该公司也参与其中   Linux社区准备一个Linux端口。这个努力非常   重要的是:它是微软继续努力的动力   投资AMD64 Windows的努力,也确保了Linux,其中   当时正在成为一个重要的操作系统,可用一次   筹码被释放。      韦伯甚至说Linux工作绝对至关重要   AMD64的成功,因为它使AMD能够实现端到端的生产   没有任何其他公司帮助的系统,如有必要。这个   可能性确保AMD甚至可以采用最坏情况的生存策略   如果其他合作伙伴退出,反过来又保留其他合作伙伴   因害怕被抛在身后而订婚。 这表明即使AMD也不认为合作必然是MS和Unix之间最重要的事情,但是拥有Unix / Linux支持非常重要。甚至试图说服一方或双方妥协或合作也不值得努力或冒险(?)激怒他们中的任何一方?也许AMD认为即使建议一个共同的ABI可能会延迟或破坏更简单的目标,即在芯片准备就绪时准备好软件支持。 我个人的猜测,但我认为ABI不同的主要原因是MS和Unix / Linux方面没有合作的政治原因,AMD并没有把这看作是一个问题。     

要回复问题请先登录注册