为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定? [英] Why does Windows64 use a different calling convention from all other OSes on x86-64?

查看:15
本文介绍了为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

AMD 有一个 ABI 规范,描述了在 x86-64 上使用的调用约定.所有操作系统都遵循它,除了具有自己的 x86-64 调用约定的 Windows.为什么?

AMD has an ABI specification that describes the calling convention to use on x86-64. All OSes follow it, except for Windows which has it's own x86-64 calling convention. Why?

有谁知道造成这种差异的技术、历史或政治原因,还是纯粹是 NIH 综合症的问题?

Does anyone know the technical, historical, or political reasons for this difference, or is it purely a matter of NIHsyndrome?

我知道不同的操作系统可能对更高级别的东西有不同的需求,但这并不能解释为什么例如 Windows 上的寄存器参数传递顺序是 rcx - rdx - r8 - r9 - 搁置在堆栈上 而其他人使用 rdi - rsi - rdx - rcx - r8 - r9 - 栈上休息.

I understand that different OSes may have different needs for higher level things, but that doesn't explain why for example the register parameter passing order on Windows is rcx - rdx - r8 - r9 - rest on stack while everyone else uses rdi - rsi - rdx - rcx - r8 - r9 - rest on stack.

附言我知道这些调用约定通常有何不同,如果需要,我知道在哪里可以找到详细信息.我想知道的是为什么.

P.S. I am aware of how these calling conventions differ generally and I know where to find details if I need to. What I want to know is why.

有关如何,请参阅例如维基百科条目和来自那里的链接.

for the how, see e.g. the wikipedia entry and links from there.

推荐答案

在 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.

Choosing four argument registers on x64 - common to UN*X / Win64

One of the things to keep in mind about x86 is that the register name to "reg number" encoding is not obvious; in terms of instruction encoding (the MOD R/M byte, see http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm), register numbers 0...7 are - in that order - ?AX, ?CX, ?DX, ?BX, ?SP, ?BP, ?SI, ?DI.

因此选择 A/C/D (regs 0..2) 作为返回值和前两个参数(这是经典"32 位 __fastcall 约定)是一个合乎逻辑的选择.就 64 位而言,更高"的regs 是有序的,Microsoft 和 UN*X/Linux 都将 R8/R9 作为第一个.

Hence choosing A/C/D (regs 0..2) for return value and the first two arguments (which is the "classical" 32bit __fastcall convention) is a logical choice. As far as going to 64bit is concerned, the "higher" regs are ordered, and both Microsoft and UN*X/Linux went for R8 / R9 as the first ones.

记住这一点,微软选择了RAX(返回值)和RCXRDXR8, R9 (arg[0..3]) 是一个可以理解的选择,如果您选择 四个 寄存器作为参数.

Keeping that in mind, Microsoft's choice of RAX (return value) and RCX, RDX, R8, R9 (arg[0..3]) are an understandable selection if you choose four registers for arguments.

我不知道为什么 AMD64 UN*X ABI 在 RCX 之前选择了 RDX.

I don't know why the AMD64 UN*X ABI chose RDX before RCX.

UN*X,在 RISC 架构上,传统上在寄存器中传递参数 - 特别是对于前 六个 参数(在 PPC、SPARC、MIPS 上也是如此)至少).这可能是 AMD64 (UN*X) ABI 设计人员选择在该架构上也使用六个寄存器的主要原因之一.

UN*X, on RISC architectures, has traditionally done argument passing in registers - specifically, for the first six arguments (that's so on PPC, SPARC, MIPS at least). Which might be one of the major reasons why the AMD64 (UN*X) ABI designers chose to use six registers on that architecture as well.

所以如果你想让六个寄存器传入参数,那么选择RCXRDX是合乎逻辑的, R8R9 四个,你应该选择另外两个?

So if you want six registers to pass arguments in, and it's logical to choose RCX, RDX, R8 and R9 for four of them, which other two should you pick ?

更高"regs 需要一个额外的指令前缀字节来选择它们,因此具有更大的指令大小占用空间,因此如果您有选项,您不会想要选择其中任何一个.在经典寄存器中,由于RBPRSP隐式含义,这些不可用,而RBX传统上对 UN*X(全局偏移表)有特殊用途,AMD64 ABI 设计人员似乎不想不必要地与此不兼容.
因此,唯一的选择RSI/RDI.

The "higher" regs require an additional instruction prefix byte to select them and therefore have a bigger instruction size footprint, so you wouldn't want to choose any of those if you have options. Of the classical registers, due to the implicit meaning of RBP and RSP these aren't available, and RBX traditionally has a special use on UN*X (global offset table) which seemingly the AMD64 ABI designers didn't want to needlessly become incompatible with.
Ergo, the only choice were RSI / RDI.

因此,如果您必须将 RSI/RDI 作为参数寄存器,它们应该是哪些参数?

So if you have to take RSI / RDI as argument registers, which arguments should they be ?

使它们 arg[0]arg[1] 有一些优点.见 cHao 的评论.
?SI?DI 是字符串指令源/目标操作数,正如 cHao 提到的,它们用作参数寄存器意味着使用 AMD64 UN*X 调用约定,最简单的可能的strcpy()函数,例如只包含两条​​CPU指令repz movsb;ret 因为源/目标地址已被调用者放入正确的寄存器中.特别是在低级和编译器生成的胶水"中代码(例如,一些 C++ 堆分配器在构造时对对象进行零填充,或者内核对 sbrk() 上的堆页面进行零填充,或者写时复制页面错误)大量的块复制/填充,因此它对于经常用于保存两个或三个 CPU 指令的代码很有用,否则这些指令会将此类源/目标地址参数加载到正确"的代码中.寄存器.

Making them arg[0] and arg[1] has some advantages. See cHao's comment.
?SI and ?DI are string instruction source / destination operands, and as cHao mentioned, their use as argument registers means that with the AMD64 UN*X calling conventions, the simplest possible strcpy() function, for example, only consists of the two CPU instructions repz movsb; ret because the source/target addresses have been put into the correct registers by the caller. There is, particularly in low-level and compiler-generated "glue" code (think, for example, some C++ heap allocators zero-filling objects on construction, or the kernel zero-filling heap pages on sbrk(), or copy-on-write pagefaults) an enormous amount of block copy/fill, hence it'll be useful for code so frequently used to save the two or three CPU instructions that'd otherwise load such source/target address arguments into the "correct" registers.

所以在某种程度上,UN*X 和 Win64 的不同仅在于 UN*X 前置"两个额外的参数,在有目的地选择的 RSI/RDI 寄存器中,在 RCX, RDX 中自然选择四个参数>、R8R9.

So in a way, UN*X and Win64 are only different in that UN*X "prepends" two additional arguments, in purposefully chosen RSI/RDI registers, to the natural choice of four arguments in RCX, RDX, R8 and R9.

UN*X 和 Windows x64 ABI 之间的区别不仅仅是参数到特定寄存器的映射.有关 Win64 的概述,请查看:

There are more differences between the UN*X and Windows x64 ABIs than just the mapping of arguments to specific registers. For the overview on Win64, check:

http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx

Win64 和 AMD64 UN*X 在堆栈空间的使用方式上也有显着不同;例如,在 Win64 上,调用者必须为函数参数分配堆栈空间,即使 args 0...3 在寄存器中传递.另一方面,在 UN*X 上,如果叶函数(即不调用其他函数的函数)需要的堆栈空间不超过 128 字节(是的,您拥有并可以使用它)甚至根本不需要分配堆栈空间一定数量的堆栈而不分配它......好吧,除非你是内核代码,一个漂亮的错误的来源).所有这些都是特定的优化选择,其中大部分理由在原始发布者的维基百科参考所指向的完整 ABI 参考中进行了解释.

Win64 and AMD64 UN*X also strikingly differ in the way stackspace is used; on Win64, for example, the caller must allocate stackspace for function arguments even though args 0...3 are passed in registers. On UN*X on the other hand, a leaf function (i.e. one that doesn't call other functions) is not even required to allocate stackspace at all if it needs no more than 128 Bytes of it (yes, you own and can use a certain amount of stack without allocating it ... well, unless you're kernel code, a source of nifty bugs). All these are particular optimization choices, most of the rationale for those is explained in the full ABI references that the original poster's wikipedia reference points to.

这篇关于为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆