Windows汇编函数调用

1、MASM的过程声明和调用

Windows的应用程序接口API采用C、C+语言语法定义,不便于汇编语言调用。但微软汇编程序从MASM6.0版本开始引入了高级语言具有的程序设计特性,为了配合调用高级语言函数,引入了过程声明PROTO和过程调用INVOKE伪指令。

(1)、过程声明伪指令PROTO

PROTO用于事先声明过程的结构,包括外部的操作系统API函数、高级语言的函数。它的格式如下:

过程名 proto[调用距离][语言类型][,[参数]:类型]…

其中,过程名可以是用PROC定义的过程名,也可以是API函数名或高级语言的函数名。

调用距离是指NEAR或FAR类型,通常默认由存储模型确定。

语言类型有STDCALL(对应windows系统API的调用规范)、C(对应C语言使用的调用规范)等。如果该过程使用的语言类型与存储模型MODEL伪指令定义的相同,则可以省略,否则必须说明。

PROTO语句最后是该过程带有的参数以及类型,冒号前的参数名可以省略,但冒号和类型不能省略。类型可以使用任何MASM有效的类型:对于变量可以是DWORD、WORD、BYTE等;对于地址可以用DWORD(32位地址)或WORD(16位地址)说明,也可以是PTR,说明为指针(为了简化,转换过程中一律使用DWORD);对于参数个数、类型不定,需要使用VARARG进行说明(Variable Argument,表示可变参数)。

(2)、过程调用伪指令INVOKE

处理器使用CALL指令实现子程序调用。但由于涉及堆栈传递参数的传递规范,直接使用CALL调用高级语言函数比较烦琐。另外,经过PROTO过程声明的过程或函数,汇编系统会对其进行类型检测,也需要配合使用过程调用伪指令NVOKE来实现调用,它的格式如下:

invoke 过程名[,参数,...]

过程调用伪指令自动创建调用过程所需要的代码序列,调用前将参数压入堆栈,调用后平衡堆栈。其中“参数”表示通过堆栈传递给过程的实参数,可以是各种常量组成的数值表达式、通用寄存器、寄存器对(格式是reg:reg)、标号或变量地址等。

对于地址参数常会使用“ADDR”操作符,后跟标号或者变量名字,表示它们的地址。ADDR操作符类似OFFSET操作符,但ADDR只用在INVOKE语句中,常用于获取局部变量的地址:而OFFSET只能获取全局变量的偏移地址。MASM中在数据段定义的变量都是全局变量。局部变量使用LOCAL伪指令定义,占用堆栈区域(详见7.2节),需要使用ADDR,而不能使用OFFSET来获取地址。

使用INVOKE调用之前,需要用PROTO先进行声明,或者先用扩展的PROC进行过程定义。

2、程序退出函数

程序执行结束需要退出,Windows使用ExitProcess函数实现(对应DOS的4CH号功能调用),该函数存在于32位核心动态链接库(KERNEL32.DLL)中。它是一个标准的windowsAPI,结束一个进程及其所有线程,即程序退出。在win32程序员参考手册中,它的定义如下:

VOID ExitProcess(
    UINT uExitCode // exit code for all threads
);

其中参数uExitCode表示该进程的退出代码,其作用与DOS操作系统的退出功能调用(如4CH号)的入口参数相同。例如,常用0表示程序正常执行结束。

退出代码的类型UINT表示无符号整型,而int型在32位系统中是32位整数,所以UINT表示32位无符号整数类型。在文档中,API函数的声明采用C/C++语法,所有函数的参数类型都是基于标准C语言的数据类型或者Windows的预定义类型。我们需要正确地区别这些类型,才能转换成汇编语言的数据类型。例如,类型UNIT对应汇编语言的双字类型DWORD。

注意,汇编语言通常不区分大小写字母,所以经常使用汇编语言可能形成一律使用小写或大写,或者按照一定规律大小写混用(如保留字用大写,其他标识符用小写)的习惯,但是,高级语言C和C+对大小写敏感,大写字母与小写字母须严格区分,所以在汇编语言中使用高级语言的函数、类型或者变量等标识符,都要按照高级语言的语法正确书写大小写,不能像汇编语言那样随意。

这样,ExIPrcess函数在汇编语言中,需要进行如下声明:

ExitProcess proto:dword

应用程序中使用该功能,这个应用程序就会立即退出,返回windows。汇编语言的调用方法如下:

invoke ExitProcess,0

其中,返回代码是0,表示没有错误。返回代码也可以是其他数值。

利用MASM的 PROTO和INVOKE语句,不仅可以在调用函故时对函数声明的原要进行类型检测,以便发现是否有参数不匹配的情况,而且汇编语言中调用Windows的API函数就像C/C++等高级语言一样。

还可以利用MASM的宏汇编能力,将函数调用定义成宏。例如:

exit    macro dwexitcode
        invoke ExitProcess,dwexitcode
        endm

利用这个宏实现程序退出,宏指令是:

exit 0

这样使用,更加简单方便。

3、Windows程序格式

类似DOS环境的汇编语言程序,Windows应用程序的格式如下:

;exampled.asm in Windows
.686
.model flat,stdcall
option casemap:none
includelib bin32\kernel32.lib   ;包含基本API函数的导入库文件
ExitProcess proto,:dword        ;Windows函数声明
    .data                       ;定义数据段
    ......                      ;数据定义(数据待填)
    .code                       ;定义代码段
    ......                      ;程序执行起始位置
start:
    ......                      ;主程序(指令待填)
    invoke ExitProcess,0        ;程序正常执行终止
    ......                      ;子程序(指令待填)
    end start                   ;汇编结束

编写32位Windows应用程序,肯定要使用32位指令,所以必须有处理器选择伪指令,这里使用“.686”声明采用Pentium Pro(原被称为80686处理器)支持的指令系统。还应注意到,该伪指令必须书写在存储模型语句之前,默认采用32位地址和操作数长度。

32位Windows使用线性地址空间,存储模型语句“.MODEL”只能选择FLAT平展模型。程序需要使用Windows提供的系统函数,它的应用程序接口API采用标准调用语言类型“STDCALL”。

汇编语言默认不区分大小写,但选项伪指令“OPTIONCASEMAP:NONE”告知MASM要区分标识符的大小写,因为Windows的API函数区别大小写。汇编程序ML.EXE的参数“/Cp”具有同样的效果,也是告知MASM不要更改程序员自己定义的标识符的大小写。这种情况下,虽然MASM保留字使用大小写均可,但用户自定义的符号不能随意使用大小写。

堆栈段通常由Windows操作系统维护,用户可以不设置;如果程序使用的堆栈空间较大,也可以设置。同样,Windows不需要用户设置代码段CS、数据段DS内容,因为它们共用一个平展(不分段)的线性地址空间。

程序格式中定义了一个标号START(也可以使用其他标识符),汇编结束END语句作为参数,用于指明程序开始执行的位置。