1.16 微软ABI注释
微软ABI是一个程序中各个模块之间的契约,用于确保模块(特别是使用不同程序设计语言编写的模块)之间的兼容性[10]。在本书中,C++程序将调用汇编语言代码,汇编模块将调用C++代码,因此汇编语言代码必须遵守微软ABI的契约。
即使编写独立的汇编语言代码,也仍然会调用C++代码,因为将(毫无疑问地)调用Windows应用程序编程接口(application programming interface,API)。Windows API函数都是使用C++编写的,所以对Windows的调用必须遵循Windows ABI的契约。
由于遵循微软ABI的契约非常重要,因此本书每一章(如果适用的话)的最后都包括一小节,重点讨论本章介绍的或者大量使用的微软ABI组件。本节介绍微软ABI中的几个概念:变量大小、寄存器的用途和栈对齐。
1.16.1 变量大小
尽管在汇编语言中处理不同的数据类型完全取决于汇编语言程序员(以及对使用在该数据上的机器指令的选择),但在C++和汇编语言程序之间保持数据大小(以字节为单位)的一致性是至关重要的。表1-6列出了几种常见的C++数据类型和对应的汇编语言类型(类型中蕴含数据大小信息)。
表1-6 C++和汇编语言类型
(续)
尽管MASM提供有符号类型声明(sbyte、sword、sdword和sqword),但汇编语言指令不区分无符号类型变体和有符号类型变体。可以使用无符号指令序列处理有符号整数(sdword),也可以使用有符号指令序列处理无符号整数(dword)。在汇编语言源文件中,这些不同的伪指令主要发挥文档辅助功能,用以帮助描述程序员的意图[11]。
程序清单1-9是一个简单的程序,用于验证这些C++数据类型的大小。
注意:%2zd格式字符串显示size_t类型值(sizeof运算符返回size_t类型的值)。这样可以避免MSVC编译器产生警告信息(如果仅使用%2d,则会生成警告信息)。大多数编译器可以使用%2d。
程序清单1-9 输出常见C++数据类型的大小
程序清单1-9的build命令和输出结果如下所示:
1.16.2 寄存器的用途
汇编语言过程(包括汇编语言主函数)中寄存器的用途(register usage)也受某些微软ABI规则的约束。在一个过程中,微软ABI对寄存器的用途有如下说明[12]。
●调用函数的代码可以通过寄存器RCX、RDX、R8和R9,将前四个(整数)参数分别传递给函数(过程)。程序可以通过寄存器XMM0、XMM1、XMM2和XMM3传递前四个浮点参数。
●寄存器RAX、RCX、RDX、R8、R9、R10和R11是易失性(volatile)寄存器,这意味着函数(过程)不需要在函数(过程)调用中保存寄存器的值。
●寄存器XMM0/YMM0到XMM5/YMM5也是易失性寄存器。函数(过程)不需要在调用过程中保存这些寄存器的值。
●寄存器RBX、RBP、RDI、RSI、RSP、R12、R13、R14和R15是非易失性(nonvolatile)寄存器。过程(函数)必须在调用过程中保存这些寄存器的值。如果一个过程修改了其中一个寄存器的值,则该过程必须在第一次修改之前保存寄存器的值,并在从函数(过程)返回之前从保存的位置恢复寄存器的值。
●寄存器XMM6到XMM15是非易失性寄存器。函数必须在调用其他函数(过程)期间保存这些寄存器的值(即当过程返回时,这些寄存器必须包含在进入该过程时所具有的相同值)。
●使用x86-64浮点协处理器指令的程序,必须在过程调用中保存浮点控制字的值。此类程序还应清除浮点栈的内容。
●任何使用x86-64方向标志位的过程(函数),必须在从过程(函数)中返回时清除该标志位。
微软C++期望函数返回值出现在以下两个位置。整数(和其他非标量)结果返回到RAX寄存器(最多64位)。如果返回值类型小于64位,则RAX寄存器的高位未定义。例如,如果函数返回短整型(16位)结果,则RAX寄存器中的第16位至第63位可能包含垃圾数据。微软的ABI规定,浮点(和向量)函数返回结果必须返回到XMM0寄存器。
1.16.3 栈对齐
在本章的源代码程序清单中,包含一些“魔法”指令,这些“魔法”指令基本上是从RSP寄存器中加或者减一个值。这些指令与栈对齐有关(这是微软ABI契约的要求)。本章(以及随后的几章)在代码中提供了这些指令,但没有做进一步解释。有关这些指令用途的更多详细信息,请参见第5章。