Windows汇编语言简明教程——基础篇
(2007新版)
(V0.01)
作者:TBsoft Software Studio(2007)
作者前言
本教程是笔者1999年12月草作《Win32汇编语言教程》的新版,目前国内关于Win32汇编语言的优秀书籍是罗云彬所著,电子工业出版社出版的《Windows环境下32位汇编语言程序设计》,本教程较为简单,仅作为初学者的参考资料使用。
目前Windows已经从Win32发展到了Win64,因此本教程名称改为《Windows汇编语言简明教程》,但本教程的基础篇仍然只涉及Win32汇编语言。
1 引言
Windows(Win32)应用程序一般使用C语言编程,但是在某些需要进行底层编程的情况下,例如Win32应用程序执行机制分析、加密解密、反病毒等底层编程,或者对于某些速度要求较高的程序,需要使用汇编语言(甚至机器语言)直接开发Win32应用程序。Win32应用程序虽然和其他32位应用程序(例如32位保护模式DOS程序)一样可以使用386汇编语言和保护模式编程,但是Win32应用程序的执行机制与其他32位应用程序有一定的差别,例如消息循环、动态链接等,Win32汇编语言也有其特殊的编程方式。
为了使大家能对Win32汇编语言的基本编程方法有一定的了解,笔者草拟了本教程,旨在抛砖引玉,如果本教程能够带领你走进Win32汇编语言世界,笔者心愿足矣。
使用本教程,要求读者具有80386汇编语言和C语言开发Win32应用程序(Win32 SDK编程)的基础。
2 进行Win32汇编语言编程的基本软件
进行Win32汇编语言编程,通常需要使用MASM 6.11以上版本的汇编器,以及Win32 SDK中的资源编译器(RC.EXE)和链接器(LINK.EXE),还需要用到Win32 SDK中的引入库文件(KERNEL32.LIB、USER32.LIB、GDI32.LIB等)。
目前进行Win32汇编语言编程,准备开发环境较为简单的方法是使用MASM32软件包,可以使用MASM32 V7或者MASM32 V8版本。
MASM32软件包中已经包括了进行Win32汇编语言编程所需的各种软件,包括MASM 6.1x、资源编译器、链接器、包含文件、引入库等,还包括一个可以作为简单的汇编语言集成环境(IDE)的编辑器Quick Editor(QEDITOR.EXE),这样在汇编链接Win32汇编语言源程序时可以无需使用命令行工具,很适合初学者使用。
安装MASM32很简单,运行安装文件INSTALL.EXE,选择安装目标驱动器然后确认安装即可完成安装,MASM32通常安装在安装目标驱动器根目录下的masm32子目录中。
使用Quick Editor,可以直接使用资源管理器或者在命令提示符(MS-DOS方式)下运行MASM32安装目录下的QEDITOR.EXE文件。
3 Win32汇编语言与Win32 API
使用Win32汇编语言开发Win32应用程序,使用的应用程序框架仍然是Win32 SDK编程的应用程序框架,只不过将通常使用的C语言换成80386汇编语言。
显然,使用Win32汇编语言开发Win32应用程序同样存在调用Win32 API的问题。和使用C语言进行Win32 SDK编程需要WINDOWS.H头文件以及其他头文件定义常量、数据结构和Win32 API一样,Win32汇编语言也需要包含文件(INC文件)定义常量、数据结构和Win32 API。
MASM32中提供了使用Win32汇编语言开发Win32应用程序所需的包含文件,通常位于MASM32安装目录下的INCLUDE子目录中。Win32汇编语言源程序应该包含WINDOWS.INC文件,如果需要调用的Win32 API位于Win32系统DLL中,例如KERNEL32.DLL、USER32.DLL、GDI32.DLL等,还需要包含对应的包含文件,例如kernel32.inc、user32.inc、gdi32.inc等。
Win32 API中,凡是与字符或者字符串有关的API都有两种不同的类型:ANSI字符集API和Unicode字符集API,分别对应ANSI字符和Unicode字符,相应与字符或者字符串相关的数据结构也有两种不同的类型。Windows NT(包括Windows 2000/XP及其以后版本)支持两种类型的API,Windows 9x通常只支持ANSI字符集API。ANSI字符集API或者数据结构的实际名称以API或者数据结构名称末尾加字符“A”表示,Unicode字符集API或者数据结构的实际名称以API或者数据结构名称末尾加字符“W”表示,例如GetModuleHandle函数相应的ANSI字符集函数实际名称为GetModuleHandleA,相应的Unicode字符集函数实际名称为GetModuleHandleW。
在Win32 SDK中的WINDOWS.H头文件和其他头文件中,凡是与字符有关的API或者数据结构都有两种不同的定义,并使用条件编译和宏定义实现自动根据当前字符集使用对应的API或者数据结构定义。例如,下列程序段是WINBASE.H头文件中对GetModuleHandle函数的定义:
WINBASEAPI
HMODULE
WINAPI
GetModuleHandleA(
LPCSTR lpModuleName
);
WINBASEAPI
HMODULE
WINAPI
GetModuleHandleW(
LPCWSTR lpModuleName
);
#ifdef UNICODE
#define GetModuleHandle GetModuleHandleW
#else
#define GetModuleHandle GetModuleHandleA
#endif // !UNICODE
但是在MASM32提供的包含文件中,API或者数据结构名称标识符通常只对应ANSI字符集API或者数据结构,例如GetModuleHandle函数只对应GetModuleHandleA函数,这样可以保证在Windows 9x和Windows NT下的兼容性,如果读者需要改用Unicode字符集API或者数据结构,则需明确使用Unicode字符集API或者数据结构,例如调用GetModuleHandleW函数,初学者可以暂时不去管它。
4 非结构化的汇编语言和结构化的汇编语言
读者可能一听到“汇编语言”四个字就觉得十分头疼!汇编语言给人的第一印象就是一大堆难以看懂又不直观的指令,而且不结构化,大量的标号、无条件跳转指令(JMP)和条件跳转指令让你难以看懂程序;过程(或者函数)的调用参数传递又不直观,要么直接使用寄存器传递参数,不符合结构化程序设计原则;要么使用堆栈传递参数,又不能有效地检验参数类型……想必Win32汇编语言更麻烦吧!
汇编语言不是结构化编程语言,但不等于汇编语言不能进行结构化编程,MASM 6.0以上版本的汇编器对汇编语言进行了扩展,提供了很多结构化汇编语言伪指令,可以方便地实现汇编语言结构化程序设计,当你看完本教程以后,你可能会感觉到:Win32汇编语言并不比C语言麻烦多少。(如果读者看不懂本教程中的汇编语言源程序,可以对照MASM32中有关结构化汇编语言语法的帮助看)
下面是一个使用非结构化的汇编语言编写的Win32汇编语言程序,本程序没有使用结构化汇编语言伪指令。本程序的功能很简单:在屏幕上显示一个消息框。本程序只调用了两个Win32 API:MessageBox和ExitProcess,源程序如下(MSGBOX1.asm):
.386
.MODEL flat,stdcall
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\user32.lib
.STACK 4096
.DATA
MsgText BYTE 'This is a simple Win32 application!',0
MsgTitle BYTE 'Information',0
.CODE
_start:
mov eax,MB_ICONINFORMATION
or eax,MB_OK
push eax
lea eax,MsgTitle
push eax
lea eax,MsgText
push eax
xor eax,eax
push eax
call MessageBox
xor eax,eax
push eax
call ExitProcess
END _start
使用Quick Editor汇编链接本程序的基本方法如下(下同):
1、启动MASM32中的Quick Editor,编辑汇编语言源程序,将上述汇编语言源程序全部输入或者粘贴,然后选择“File”—“Save As”菜单项将汇编语言源程序保存到一个目录下,注意该目录必须与MASM32的安装目录位于同一个驱动器上,汇编语言源程序文件的扩展名应该是“asm”。
2、选择Quick Editor中的“Project”—“Build All”菜单项即可汇编链接汇编语言源程序。汇编链接时会显示一个命令提示符窗口,如果没有出现错误或者警告信息,说明汇编链接成功,将会生成相应的OBJ文件和EXE文件。
3、选择Quick Editor中的“Project”—“Run Program”菜单项即可运行汇编链接后生成的EXE文件。
本程序汇编链接后,运行生成的MSGBOX1.exe文件,屏幕上将显示出一个消息框,消息框的标题是“Information”,消息框中显示的字符串是“This is a simple Win32 application!”。
Win32汇编语言源程序开始处通常有3条伪指令:
.386
.MODEL flat,stdcall
OPTION CASEMAP:NONE
指示汇编器汇编80386指令,并使用平坦内存模式(Win32内存模式)和stdcall函数调用方式(Win32标准函数调用方式),标识符区分大小写。源程序中使用.STACK、.DATA和.CODE伪指令分别定义堆栈、数据和代码。
Win32 API通常使用stdcall函数调用方式,stdcall函数调用方式中,函数的参数使用堆栈传递,函数调用之前参数自右向左进栈,函数调用返回时同时将参数出栈,函数返回值通过eax寄存器返回。
因此,程序中调用MessageBox函数和ExitProcess函数之前,都使用push指令将参数自右向左进栈,然后使用call指令直接调用Win32 API的入口点地址。本程序调用MessageBox函数显示消息框以后,调用ExitProcess函数终止程序的执行,ExitProcess函数的作用是终止当前进程。这种直接使用汇编语言指令调用Win32 API开发Win32应用程序的方式直接对应CPU的指令代码,最接近CPU硬件,但并不直观,编写程序一不小心就容易出错。
下面是一个与上述程序等价的Win32汇编语言源程序,使用了结构化汇编语言伪指令,实现了结构化的汇编语言。源程序如下(MSGBOX2.asm):
.386
.MODEL flat,stdcall
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\user32.lib
.STACK 4096
.DATA
MsgText BYTE 'This is a simple Win32 application!',0
MsgTitle BYTE 'Information',0
.CODE
_start:
INVOKE MessageBoxA,0,ADDR MsgText,ADDR MsgTitle,MB_ICONINFORMATION or MB_OK
INVOKE ExitProcess,0