64位汇编语言的编程艺术
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.14 在汇编语言中返回函数结果

在上一节中,我们学习了向使用汇编语言编写的过程传递多达4个参数的方法。本节将描述其相反的过程:向调用过程的调用方代码返回值。

在纯汇编语言中(也就是一个汇编语言过程调用另一个汇编语言过程),严格而言,传递参数和返回函数结果是调用方(caller)和被调用方(callee)过程共享的约定。被调用方或者调用方可以选择函数结果出现的位置。

从被调用方的角度来看,返回值的过程确定调用方可以在哪里找到函数结果,并且调用该函数的调用方都必须尊重该选择。如果过程在XMM0寄存器(这是返回浮点结果的常用位置)中返回函数结果,那么调用该过程的调用方必须期望在XMM0中找到结果。而另一个不同的过程可以在RBX寄存器中返回其函数结果。

从调用方的角度来看,选择是相反的。现有代码期望函数在特定位置返回其结果,而被调用的函数必须遵守该期望。

遗憾的是,如果没有适当的协调机制,则一段代码可能会要求它调用的所有函数在一个位置返回函数结果,同时一组现有的库函数可能会坚持在另一个位置返回自己的函数结果。显然,这些函数与调用代码不兼容。虽然有一些方法可以处理这种情况[通常通过编写位于调用方和被调用方之间能够移动返回结果的外观(facade)代码],但最好的解决方案是确保各位编程人员在编写代码之前就在何处可以找到函数返回结果等事项达成协议。

此协议被称为应用程序二进制接口(application binary interface,ABI)。某种程度上,ABI是不同代码段之间的一种契约,描述调用约定(在哪里传递值,在哪里返回值,等等)、数据类型、内存使用和对齐,以及其他属性。CPU制造商、编译器编写者和操作系统供应商都提供自己的ABI。出于显而易见的原因,本书使用了Microsoft Windows ABI。

再次强调,必须充分理解在编写自己的汇编语言代码时,在过程之间传递数据的方式完全取决于用户自身。使用汇编语言的好处之一是,用户可以根据具体的过程来确定相应的接口。如果遵循ABI协议,那么用户只需要在调用自己无法控制的代码时(或者在外部代码调用自己的代码时)担心。本书涵盖了在Microsoft Windows下编写汇编语言(特别是与MSVC接口的汇编代码)的内容,因此在处理外部代码(Windows和C++代码)时,必须使用Windows/MSVC ABI。微软ABI指定,传递给printf()(或者任何C++函数)的前4个参数必须传递到RCX、RDX、R8和R9中。

Windows ABI还声明,函数(过程)会返回RAX寄存器中存储的整数和指针值(可以放入64位中)。因此,如果某些C++代码希望汇编程序返回一个整数结果,则汇编程序在返回之前需要将整数结果加载到RAX中。

为了演示汇编程序如何返回函数结果,我们将使用程序清单1-7中的C++程序(c.cpp文件,这是本书之后演示大多数C++/汇编示例时使用的C++程序)。这个C++程序包括两个额外的函数声明:getTitle()(由汇编语言代码提供),该函数返回一个指向包含程序标题的字符串的指针(由C++代码打印这个标题);以及readLine()(由C++程序提供),汇编语言代码可以调用该函数从用户处读取一行文本(然后放入汇编语言代码的字符串缓冲区中)。

程序清单1-7 调用汇编语言程序的通用C++代码

try-catch语句块将捕获汇编代码生成的任何异常,当程序异常中止时,用户将得到某种提示。

程序清单1-8提供的汇编代码演示了汇编语言中的一些新概念,第一个新概念是如何将函数的结果返回C++程序中。汇编语言函数getTitle()返回一个指向字符串的指针,调用方的C++代码将该字符串作为程序的标题进行打印。在“.data”段中,包含一个字符串变量titleStr,该变量初始化为该汇编代码的名称(即“Listing 1-8”)。getTitle()函数将该字符串的地址加载到RAX寄存器中,并将该字符串指针返回到C++代码中。C++代码在运行汇编代码之前和之后均会打印标题(具体请参见程序清单1-7)。

该程序还演示了如何从用户处读取一行文本。汇编代码调用C++代码中定义的readLine()函数,readLine()函数需要两个参数:字符缓冲区的地址(C字符串)和最大缓冲区长度。程序清单1-8中的代码通过RCX寄存器将字符缓冲区的地址传递给readLine()函数,并通过RDX寄存器将最大缓冲区长度传递给readLine()函数。最大缓冲区长度必须包含两个额外字符的空间,用于存放换行符和零终止字节。

最后,程序清单1-8演示了如何声明一个字符缓冲区(即一个字符数组)。在“.data”段中,包括以下声明:

input byte maxLen dup(?)

“maxLen dup(?)”操作数指示MASM复制(?)(即未初始化的字节)maxLen次。maxLen是一个常量,由源文件开头的等于伪指令“=”设置为256。(有关更多详细信息,请参阅4.9.1节中的相关内容。)

程序清单1-8 返回函数结果的汇编语言程序

为了编译和运行程序清单1-7与程序清单1-8中的代码,可以使用如下的命令:

命令行中的选项“/Felisting1-8.exe”指示MSVC将可执行文件命名为listing1-8.exe。如果没有指定“/Fe”选项,那么MSVC将可执行文件命名为c.exe(因为程序清单1-7中的通用示例C++文件名保存为c.cpp,该文件在c.cpp之后)。