您的位置:首页 > 其它

Windows汇编语言简明教程——基础篇

2009-05-03 09:03 447 查看
Windows汇编语言简明教程——基础篇

2007新版)

(V0.01)

作者:TBsoft Software Studio(2007)

作者前言

本教程是笔者199912月草作《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

END _start

本程序汇编链接后,运行生成的MSGBOX2.exe文件,结果与MSGBOX1.exe文件的运行结果完全相同。

在MASM32提供的包含文件中,使用PROTO伪指令定义函数原型(与C语言中函数原型的定义相似),可以定义函数名、调用方式和参数,例如在user32.inc包含文件中定义MessageBox的函数原型:

MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
MessageBox equ <MessageBoxA>

INVOKE伪指令调用由PROTO伪指令定义的函数,可以方便地传递参数和检查参数类型。包含文件中使用PROTO伪指令定义API函数,MSGBOX2.asm文件中使用INVOKE伪指令调用API函数,极其简单,连一条汇编语言指令也没有用到,可见MASM 6.0以上版本的汇编器提供的结构化汇编语言伪指令大大简化了Win32汇编语言编程。

5 显示一个窗口的Win32汇编语言程序

学习过Win32SDK编程的读者编写的第一个应用程序可能就是显示一个窗口的C语言程序,笔者也编写了这样一个C语言程序,源程序如下(SDKSIMPL.c):

#include <windows.h>
#include <tchar.h>

static TCHAR szWindowClass[]=_T("SIMPLE");

LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
WNDCLASSEX wcex;
HWND hWnd;
MSG msg;

if(!hPrevInstance)
{
wcex.cbSize=sizeof(WNDCLASSEX);
wcex.style=CS_HREDRAW|CS_VREDRAW;
wcex.lpfnWndProc=(WNDPROC)WndProc;
wcex.cbClsExtra=0;
wcex.cbWndExtra=0;
wcex.hInstance=hInstance;
wcex.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wcex.hCursor=LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName=NULL;
wcex.lpszClassName=szWindowClass;
wcex.hIconSm=LoadIcon(NULL,IDI_APPLICATION);

if(!RegisterClassEx(&wcex))
return FALSE;
}

hWnd=CreateWindow(szWindowClass,
_T("Simple"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);

if(hWnd==NULL) return FALSE;

ShowWindow(hWnd,nShowCmd);
UpdateWindow(hWnd);

while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;

switch(message)
{
case WM_PAINT:
hDC=BeginPaint(hWnd,&ps);
EndPaint(hWnd,&ps);

break;

case WM_DESTROY:
PostQuitMessage(0);

break;

default:
return DefWindowProc(hWnd,message,wParam,lParam);
}

return 0;
}

现在笔者用Win32汇编语言程序实现本程序的功能,源程序如下(SDKSIMP1.asm):

.386

.MODEL flat,stdcall

OPTION CASEMAP:NONE

INCLUDE /masm32/include/windows.inc

INCLUDE /masm32/include/user32.inc
INCLUDE /masm32/include/gdi32.inc
INCLUDE /masm32/include/kernel32.inc

INCLUDELIB /masm32/lib/user32.lib
INCLUDELIB /masm32/lib/gdi32.lib
INCLUDELIB /masm32/lib/kernel32.lib

WinMain PROTO stdcall, :HINSTANCE,:HINSTANCE,:LPSTR,:DWORD

.STACK 4096

.DATA
WindowClass BYTE 'SIMPLE',0
WindowTitle BYTE 'Simple',0
hInst1 HINSTANCE 0
lpCmdLine1 LPSTR 0

.CODE

_start:
INVOKE GetModuleHandle,NULL
mov hInst1,eax
INVOKE GetCommandLine
mov lpCmdLine1,eax
INVOKE WinMain,hInst1,NULL,lpCmdLine1,SW_SHOWDEFAULT
INVOKE ExitProcess,eax

WinMain PROC hInst:HINSTANCE,hPrevInst:HINSTANCE,lpCmdLine:LPSTR,nShowCmd:DWORD
LOCAL wcex:WNDCLASSEX
LOCAL hWnd:HWND
LOCAL msg:MSG

.IF !hPrevInst
mov wcex.cbSize,SIZEOF WNDCLASSEX
mov wcex.style,CS_HREDRAW or CS_VREDRAW
mov wcex.cbClsExtra,0
mov wcex.cbWndExtra,0
mov wcex.lpfnWndProc,OFFSET WndProc
mov eax,hInst
mov wcex.hInstance,eax
INVOKE LoadIcon,0,IDI_APPLICATION
mov wcex.hIcon,eax
INVOKE LoadCursor,0,IDC_ARROW
mov wcex.hCursor,eax
mov wcex.hbrBackground,COLOR_WINDOW+1
mov wcex.lpszMenuName,NULL
mov wcex.lpszClassName,OFFSET WindowClass
INVOKE LoadIcon,0,IDI_APPLICATION
mov wcex.hIconSm,eax

INVOKE RegisterClassEx,ADDR wcex

.IF !eax
mov eax,FALSE
ret
.ENDIF
.ENDIF

INVOKE CreateWindowEx,0,ADDR WindowClass,ADDR WindowTitle,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,hInst,NULL
mov hWnd,eax

.IF !eax
mov eax,FALSE
ret
.ENDIF

INVOKE ShowWindow,hWnd,nShowCmd
INVOKE UpdateWindow,hWnd

.WHILE TRUE
INVOKE GetMessage,ADDR msg,0,0,0

.BREAK .IF !eax

INVOKE TranslateMessage,ADDR msg
INVOKE DispatchMessage,ADDR msg
.ENDW

mov eax,msg.wParam
ret
WinMain ENDP

WndProc PROC hWnd:HWND,message:UINT,wParam:DWORD,lParam:DWORD
LOCAL hDC:HDC
LOCAL ps:PAINTSTRUCT

.IF message==WM_PAINT
INVOKE BeginPaint,hWnd,ADDR ps
mov hDC,eax
INVOKE EndPaint,hWnd,ADDR ps
xor eax,eax
ret
.ELSEIF message==WM_DESTROY
INVOKE PostQuitMessage,0
xor eax,eax
ret
.ELSE
INVOKE DefWindowProc,hWnd,message,wParam,lParam
ret
.ENDIF

xor eax,eax
ret
WndProc ENDP

END _start

本程序汇编链接后,运行生成的SDKSIMP1.exe文件,屏幕上将显示出一个标准的窗口,窗口的标题是“Simple”。

学习过Win32 SDK编程的读者都知道,Win32应用程序的入口点是WinMain函数,实际上WinMain函数是被C语言的初始化和结束代码调用的,Win32应用程序的真正入口点和DOS应用程序没有什么区别,都是在可执行文件(Win32的可执行文件称为PE文件)的文件头中指定的应用程序起始点。Win32汇编语言没有C语言的初始化和结束代码,必须自己编写初始化和结束代码调用WinMain函数(过程),WinMain函数的原型是:

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd);

WinMain函数有4个参数,分别是:

hInstance:应用程序当前实例的句柄,可以通过调用GetModuleHandle函数获取;
hPrevInstance:应用程序前一个实例的句柄,Win32中当前地址空间中不会有应用程序的其他实例在运行,该参数通常设置为NULL(提供该参数只是便于移植Win16应用程序源程序);
lpCmdLine:命令行参数,可以通过调用GetCommandLine函数获取;
nShowCmd:主窗口的显示状态,可以设置成SW_SHOWDEFAULT(缺省状态)。

本程序的初始化和结束代码如下:

INVOKE GetModuleHandle,NULL
mov hInst1,eax
INVOKE GetCommandLine
mov lpCmdLine1,eax
INVOKE WinMain,hInst1,NULL,lpCmdLine1,SW_SHOWDEFAULT
INVOKE ExitProcess,eax

可见本程序的初始化和结束代码很简单,只是调用GetModuleHandle函数获取应用程序当前实例的句柄,调用GetCommandLine函数获取命令行参数,然后调用WinMain函数(过程),WinMain函数返回后调用ExitProcess函数终止程序的执行。

将本程序与C语言程序比较,可以看出程序结构十分相似。本程序大量使用了MASM 6.0以上版本的汇编器提供的结构化汇编语言伪指令,有效地实现了选择结构和循环结构,大大简化了Win32汇编语言编程,使得汇编语言程序并不比C语言麻烦多少。

6 结束语

本教程读者阅读到这里,可能会莞尔一笑,原来Win32汇编语言也不过就是这么回事呀,确实,MASM 6.0以上版本的汇编器提供的结构化汇编语言伪指令大大简化了Win32汇编语言编程。

汇编语言确实比较复杂,但是Win32汇编语言对某些特殊方面有高级语言不可比拟的优势,如果你正在开发Win32加密加壳软件,或者你正在想编程清除Win32病毒,或者你正在编写对速度要求较高的程序(例如大量计算的程序),不妨试试Win32汇编语言——或许正能够解决你的燃眉之急。本教程还简单介绍了Win32应用程序的执行机制,相信会对你探索Win32底层有一定的帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: