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

Windows 汇编语言简明教程 —— 基础篇
 
2007 新版)
 

(V0.01

 

作者:TBsoft Software Studio2007

 

作者前言

 

本教程是笔者199912月草作《Win32汇编语言教程》的新版,目前国内关于Win32汇编语言的优秀书籍是罗云彬所著,电子工业出版社出版的《Windows环境下32位汇编语言程序设计》,本教程较为简单,仅作为初学者的参考资料使用。

 

目前Windows已经从Win32发展到了Win64,因此本教程名称改为《Windows汇编语言简明教程》,但本教程的基础篇仍然只涉及Win32汇编语言。

 

引言

 

WindowsWin32)应用程序一般使用C语言编程,但是在某些需要进行底层编程的情况下,例如Win32应用程序执行机制分析、加密解密、反病毒等底层编程,或者对于某些速度要求较高的程序,需要使用汇编语言(甚至机器语言)直接开发Win32应用程序。Win32应用程序虽然和其他32位应用程序(例如32位保护模式DOS程序)一样可以使用386汇编语言和保护模式编程,但是Win32应用程序的执行机制与其他32位应用程序有一定的差别,例如消息循环、动态链接等,Win32汇编语言也有其特殊的编程方式。

 

为了使大家能对Win32汇编语言的基本编程方法有一定的了解,笔者草拟了本教程,旨在抛砖引玉,如果本教程能够带领你走进Win32汇编语言世界,笔者心愿足矣。

 

使用本教程,要求读者具有80386汇编语言和C语言开发Win32应用程序(Win32 SDK编程)的基础。

 

进行Win32汇编语言编程的基本软件

 

进行Win32汇编语言编程,通常需要使用MASM 6.11以上版本的汇编器,以及Win32 SDK中的资源编译器(RC.EXE)和链接器(LINK.EXE),还需要用到Win32 SDK中的引入库文件(KERNEL32.LIBUSER32.LIBGDI32.LIB等)。

 

目前进行Win32汇编语言编程,准备开发环境较为简单的方法是使用MASM32软件包,可以使用MASM32 V7或者MASM32 V8版本。

 

MASM32软件包中已经包括了进行Win32汇编语言编程所需的各种软件,包括MASM 6.1x、资源编译器、链接器、包含文件、引入库等,还包括一个可以作为简单的汇编语言集成环境(IDE)的编辑器Quick EditorQEDITOR.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.DLLUSER32.DLLGDI32.DLL等,还需要包含对应的包含文件,例如kernel32.incuser32.incgdi32.inc等。

 

Win32 API中,凡是与字符或者字符串有关的API都有两种不同的类型:ANSI字符集APIUnicode字符集API,分别对应ANSI字符和Unicode字符,相应与字符或者字符串相关的数据结构也有两种不同的类型。Windows NT(包括Windows 2000XP及其以后版本)支持两种类型的APIWindows 9x通常只支持ANSI字符集APIANSI字符集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 9xWindows NT下的兼容性,如果读者需要改用Unicode字符集API或者数据结构,则需明确使用Unicode字符集API或者数据结构,例如调用GetModuleHandleW函数,初学者可以暂时不去管它。

 

非结构化的汇编语言和结构化的汇编语言

 

读者可能一听到“汇编语言”四个字就觉得十分头疼!汇编语言给人的第一印象就是一大堆难以看懂又不直观的指令,而且不结构化,大量的标号、无条件跳转指令(JMP)和条件跳转指令让你难以看懂程序;过程(或者函数)的调用参数传递又不直观,要么直接使用寄存器传递参数,不符合结构化程序设计原则;要么使用堆栈传递参数,又不能有效地检验参数类型……想必Win32汇编语言更麻烦吧!

 

汇编语言不是结构化编程语言,但不等于汇编语言不能进行结构化编程,MASM 6.0以上版本的汇编器对汇编语言进行了扩展,提供了很多结构化汇编语言伪指令,可以方便地实现汇编语言结构化程序设计,当你看完本教程以后,你可能会感觉到:Win32汇编语言并不比C语言麻烦多少。(如果读者看不懂本教程中的汇编语言源程序,可以对照MASM32中有关结构化汇编语言语法的帮助看)

 

下面是一个使用非结构化的汇编语言编写的Win32汇编语言程序,本程序没有使用结构化汇编语言伪指令。本程序的功能很简单:在屏幕上显示一个消息框。本程序只调用了两个Win32 APIMessageBoxExitProcess,源程序如下(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汇编语言编程。

 

显示一个窗口的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语言麻烦多少。

 

结束语

 

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

 

汇编语言确实比较复杂,但是Win32汇编语言对某些特殊方面有高级语言不可比拟的优势,如果你正在开发Win32加密加壳软件,或者你正在想编程清除Win32病毒,或者你正在编写对速度要求较高的程序(例如大量计算的程序),不妨试试Win32汇编语言——或许正能够解决你的燃眉之急。本教程还简单介绍了Win32应用程序的执行机制,相信会对你探索Win32底层有一定的帮助。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值