Win32汇编源程序的结构

任何种类的语言,总是有基本的源程序结构规范,在讨论C语言的书中,大家都会记得这个非常经典的Hello World程序:

#include <stdio.h>

main()

{

         printf("Hello, world"n");

}

像这样一个程序,就说明了C语言中最基本的格式,main()中的括号和下面的花括号说明了一个函数的定义方法,printf语句说明了一个函数的调用方法,调用函数语句后面的分号也是基本的格式。C是一种高级语言,在C源程序中,不必为堆栈段、数据段和代码段的定义而担心,编译器会把程序中的字符串和语句代码分别放到它们该去的地方,程序开始执行的时候也会自己找到main()函数。而汇编是低级语言,必须为所有的东西找到它们该去的地方,所以在DOS的汇编中,Hello World又长成了这样一副模样:

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 堆栈段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

stack                  segment stack

                       db        100 dup (?)

stack                  ends

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

data                   segment

szHello                db        'Hello, world',0dh,0ah,'$'

data                   ends

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

code                   segment

                       assume    cs:code,ds:data,ss:stack

start:

                       mov       ax,data

                       mov       ds,ax

 

                       mov       ah,9

                       mov       dx,offset szHello

                       int       21h

 

                       mov       ah,4ch

                       int       21h

code                   ends

                       end       start

在这个源程序中,stack段为堆栈找了个家,hello world字符串则跑到数据段中去了,代码则放在代码段中,程序的开始语句必须由最后一句end start来说明应该从start这个标号开始执行,整个程序在使用过DOS汇编的程序员眼里是非常的熟悉。

到了Win32汇编的时候,程序的基本结构还是如此,先来看一看这个看起来很新鲜的Win32Hello world程序:

                    .386

                    .model flat,stdcall

                    option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 文件定义

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include             windows.inc

include             user32.inc

includelib          user32.lib

include             kernel32.inc

includelib          kernel32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    .data

 

szCaption           db      'A MessageBox !',0

szText              db      'Hello, World !',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    .code

start:

                    invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK

                    invoke ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    end     start

怎么样,看来和上面的C以及DOS汇编又不同了吧!但从include.data.code等语句“顾名思义”也能看出一点苗头来,include应该就是包含别的文件,.data想必是数据段,.code应该就是代码段了吧!接下来通过这个例了程序逐段介绍Win32汇编程序的结构。

3.1.1 模式定义

程序的第一部分是模式和源程序格式的定义语句:

    .386

    .model flat,stdcall

    option casemap:none

这些指令定义了程序使用的指令集、工作模式和格式。

1. 指定使用的指令集

.386语句是汇编语言的伪指令,它在低版本的宏汇编中就已经存在,类似的指令还有:.8086.186.286.386/.386p.486/.486p .586/.586p等,用于告诉编译器在本程序中使用的指令集。在DOS的汇编中默认使用的是8086指令集,那时候如果在源程序中写入80386所特有的指令或使用32位的寄存器就会报错,为了在DOS环境下进行保护模式编程或仅为了使用32位寄存器,常在DOS的汇编中使用 .386来定义。Win32环境工作在80386及以上的处理器中,所以这一句 .386是必不可少的。

后面带p的伪指令则表示程序中可以使用特权指令,如:

    mov cr0,eax

这一类指令必须在特权级0上运行,如果只指定 .386,那么使用普通的指令是可以的,编译时到这一句就会报错,如果我们要写的程序是VxD等驱动程序,中间要用到特权指令,那么必须定义 .386p,在应用程序级别的Win32编程中,程序都是运行在优先级3上,不会用到特权指令,只需定义 .386就够了。80486Pentium处理器指令是80386处理器指令的超集,同样道理,如果程序中要用80486处理器或Pentium处理器的指令,则必须定义 .486 .586。另外,Intel公司的80x86系列处理器从Pentium MMX开始增加了MMX指令集,为了使用MMX指令,除了定义 .586之外,还要加上一句 .mmx伪指令:

    .586

    .mmx

 

 

Win32汇编源程序的结构

任何种类的语言,总是有基本的源程序结构规范,在讨论C语言的书中,大家都会记得这个非常经典的Hello World程序:

#include <stdio.h>

main()

{

         printf("Hello, world\n");

}

像这样一个程序,就说明了C语言中最基本的格式,main()中的括号和下面的花括号说明了一个函数的定义方法,printf语句说明了一个函数的调用方法,调用函数语句后面的分号也是基本的格式。C是一种高级语言,在C源程序中,不必为堆栈段、数据段和代码段的定义而担心,编译器会把程序中的字符串和语句代码分别放到它们该去的地方,程序开始执行的时候也会自己找到main()函数。而汇编是低级语言,必须为所有的东西找到它们该去的地方,所以在DOS的汇编中,Hello World又长成了这样一副模样:

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 堆栈段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

stack                  segment stack

                       db        100 dup (?)

stack                  ends

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

data                   segment

szHello                db        'Hello, world',0dh,0ah,'$'

data                   ends

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

code                   segment

                       assume    cs:code,ds:data,ss:stack

start:

                       mov       ax,data

                       mov       ds,ax

 

                       mov       ah,9

                       mov       dx,offset szHello

                       int       21h

 

                       mov       ah,4ch

                       int       21h

code                   ends

                       end       start

在这个源程序中,stack段为堆栈找了个家,hello world字符串则跑到数据段中去了,代码则放在代码段中,程序的开始语句必须由最后一句end start来说明应该从start这个标号开始执行,整个程序在使用过DOS汇编的程序员眼里是非常的熟悉。

到了Win32汇编的时候,程序的基本结构还是如此,先来看一看这个看起来很新鲜的Win32Hello world程序:

                    .386

                    .model flat,stdcall

                    option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 文件定义

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include             windows.inc

include             user32.inc

includelib          user32.lib

include             kernel32.inc

includelib          kernel32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    .data

 

szCaption           db      'A MessageBox !',0

szText              db      'Hello, World !',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    .code

start:

                    invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

                    invoke  ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                    end     start

怎么样,看来和上面的C以及DOS汇编又不同了吧!但从include.data.code等语句“顾名思义”也能看出一点苗头来,include应该就是包含别的文件,.data想必是数据段,.code应该就是代码段了吧!接下来通过这个例了程序逐段介绍Win32汇编程序的结构。

3.1.1  模式定义

程序的第一部分是模式和源程序格式的定义语句:

    .386

    .model flat,stdcall

    option casemap:none

这些指令定义了程序使用的指令集、工作模式和格式。

1. 指定使用的指令集

.386语句是汇编语言的伪指令,它在低版本的宏汇编中就已经存在,类似的指令还有:.8086.186.286.386/.386p.486/.486p .586/.586p等,用于告诉编译器在本程序中使用的指令集。在DOS的汇编中默认使用的是8086指令集,那时候如果在源程序中写入80386所特有的指令或使用32位的寄存器就会报错,为了在DOS环境下进行保护模式编程或仅为了使用32位寄存器,常在DOS的汇编中使用 .386来定义。Win32环境工作在80386及以上的处理器中,所以这一句 .386是必不可少的。

后面带p的伪指令则表示程序中可以使用特权指令,如:

    mov cr0,eax

这一类指令必须在特权级0上运行,如果只指定 .386,那么使用普通的指令是可以的,编译时到这一句就会报错,如果我们要写的程序是VxD等驱动程序,中间要用到特权指令,那么必须定义 .386p,在应用程序级别的Win32编程中,程序都是运行在优先级3上,不会用到特权指令,只需定义 .386就够了。80486Pentium处理器指令是80386处理器指令的超集,同样道理,如果程序中要用80486处理器或Pentium处理器的指令,则必须定义 .486 .586。另外,Intel公司的80x86系列处理器从Pentium MMX开始增加了MMX指令集,为了使用MMX指令,除了定义 .586之外,还要加上一句 .mmx伪指令:

    .586

    .mmx

2. .model语句

.model语句在低版本的宏汇编中已经存在,用来定义程序工作的模式,它的使用方法是:

.model 内存模式[,语言模式][,其他模式]

内存模式的定义影响最后生成的可执行文件,可执行文件的规模从小到大,可以有很多种类型,在DOS的可执行程序中,有只用到64 KB .com文件,也有大大小小的 .exe文件。到了Win32环境下,又有了可以用4 GB内存的PE格式可执行文件,编写不同类型的可执行文件要用 .model语句定义不同的参数,具体如表3.1所示。

3.1  内存模式

   

使

tiny

small

medium

compact

large

huge

flat

用来建立 .com文件,所有的代码、数据和堆栈都在同一个64 KB段内

建立代码和数据分别用一个64 KB段的 .exe文件

代码段可以有多个64 KB段,数据段只有一个64 KB

代码段只有一个64 KB段,数据段可以有多个64 KB

代码段和数据段都可以有多个64 KB

large,并且数据段中的一个数组也可以超过64 KB

Win32程序使用的模式,代码和数据段使用同一个4 GB

在前面章节中已经提到过:Windows程序运行在保护模式下,系统把每一个Win32应用程序都放到分开的虚拟地址空间中去运行,也就是说,每一个应用程序都拥有其相互独立的4 GB 地址空间,对Win32程序来说,只有一种内存模式,即flat(平坦)模式,意思是内存是很“平坦”地从0延伸到 4 GB,再没有64 KB段大小限制。对比一下DOSHello WorldWin32Hello World开始部分的不同,DOS程序中有这样两句:

mov     ax,data

mov     ds,ax

意思是把数据段寄存器DS指向data数据段,data数据段在前面已经用 data segment 语句定义,只要DS不重新设置,那么从此以后指令中涉及的数据默认将从data数据段中取得,所以下面的语句是从data数据段取出szHello字符串的地址后再显示:

mov     ah,9

mov     dx,offset szHello

int     21h

纵观Win32汇编的源程序,没有一处可以找到dses等段寄存器的使用,因为所有的4 GB空间用32位的寄存器全部都能访问到了,不必在头脑中随时记着当前使用的是哪个数据段,这就是“平坦”内存模式带来的好处。

如果定义了 .model flatMASM自动为各种段寄存器做了如下定义:

ASSUME  cs:FLAT, ds:FLAT, ss:FLAT, es:FLAT, fs:ERROR, gs:ERROR

也就是说,CSDSESSS段全部使用平坦模式,FSGS寄存器默认不使用,这时若在源程序中使用FSGS,在编译时会报错。如果有必要使用它们,只需在使用前用下面的语句声明一下就可以了:

assume  fs:nothing, gs:nothing 或者 assume fs:flat, gs:flat

Win32汇编中,.model语句中还应该指定语言模式,即子程序的调用方式,例子中用的是stdcall,它指出了调用子程序或Win32 API时参数传递的次序和堆栈平衡的方法,相对于stdcall,不同的语言类型还有CSysCallBASICFORTRANPASCAL,虽然各种高级语言在调用子程序时都是使用堆栈来传递参数,但它们的处理方法各有不同。要和别的语言配合,就必须指定相应的语言种类。WindowsAPI调用使用的是stdcall格式,所以在Win32汇编中没有选择,必须在 .model中加上stdcall参数。关于参数传递的具体细节,在3.4.2节中有详细的描述。

3. option语句

option语句定义的选项有很多,如option language定义和option segment定义等,在Win32汇编程序中,需要的只是定义option casemap:none,这个语句定义了程序中的变量和子程序名是否对大小写敏感,由于Win32 API中的API名称是区分大小写的,所以必须指定这个选项,否则在调用API的时候会有问题。

  

posted on 2009-04-11 13:37  jasonM  阅读(685)  评论(0编辑  收藏  举报