WIN32汇编语言教程:第03章 使用MASM · 3.1 Win32汇编源程序的结构(2)
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段大小限制。对比一下DOS的Hello World和Win32的Hello 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汇编的源程序,没有一处可以找到ds或es等段寄存器的使用,因为所有的4 GB空间用32位的寄存器全部都能访问到了,不必在头脑中随时记着当前使用的是哪个数据段,这就是“平坦”内存模式带来的好处。
如果定义了 .model flat,MASM自动为各种段寄存器做了如下定义:
ASSUME cs:FLAT, ds:FLAT, ss:FLAT, es:FLAT, fs:ERROR, gs:ERROR
也就是说,CS,DS,ES和SS段全部使用平坦模式,FS和GS寄存器默认不使用,这时若在源程序中使用FS或GS,在编译时会报错。如果有必要使用它们,只需在使用前用下面的语句声明一下就可以了:
assume fs:nothing, gs:nothing 或者 assume fs:flat, gs:flat
在Win32汇编中,.model语句中还应该指定语言模式,即子程序的调用方式,例子中用的是stdcall,它指出了调用子程序或Win32 API时参数传递的次序和堆栈平衡的方法,相对于stdcall,不同的语言类型还有C,SysCall,BASIC,FORTRAN和PASCAL,虽然各种高级语言在调用子程序时都是使用堆栈来传递参数,但它们的处理方法各有不同。要和别的语言配合,就必须指定相应的语言种类。Windows的API调用使用的是stdcall格式,所以在Win32汇编中没有选择,必须在 .model中加上stdcall参数。关于参数传递的具体细节,在3.4.2节中有详细的描述。
3. option语句
用option语句定义的选项有很多,如option language定义和option segment定义等,在Win32汇编程序中,需要的只是定义option casemap:none,这个语句定义了程序中的变量和子程序名是否对大小写敏感,由于Win32 API中的API名称是区分大小写的,所以必须指定这个选项,否则在调用API的时候会有问题。
3.1.2 段的定义
1. 段的概念
把上面的Win32的Hello World源程序中的语句归纳精简一下,再列在下面:
.386 .model flat,stdcall option casemap:none <一些include语句> .data <一些字符串、变量定义> .code <代码> <开始标号> <其他语句> end 开始标号
上一节讲到的选项、模式等定义并不会在编译好的可执行程序中产生什么东西,它们只是“说明”,而真正的数据和代码是定义在各个段中的,如上面的 .data段和 .code段,考虑到不同的数据类型,还可以有其他种类的数据段,下面是包含全部段的源程序结构:
.386 .model flat,stdcall option casemap:none <一些include语句> .stack [堆栈段的大小] .data <一些初始化过的变量定义> .data? <一些没有初始化过的变量定义> .const <一些常量定义> .code <代码> <开始标号> <其他语句> end 开始标号
.stack,.data,.data?,.const和 .code是分段伪指令,Win32中实际上只有代码和数据之分,.data,.data?和 .const是数据段,.code是代码段,和DOS汇编不同,Win32汇编不必考虑堆栈,系统会为程序分配一个向下扩展的、足够大的段作为堆栈段,所以 .stack段定义常常被忽略。
前面不是说过Win32环境下不用段了吗?是的,这些“段”实际上并不是DOS汇编中那种意义的段,而是内存的“分段”。上一个段的结束就是下一个段的开始,所有的“分段”合起来,包括系统使用的地址空间,就组成了整个可以寻址的4 GB空间。Win32环境的内存管理使用了80386处理器的分页机制,每个页(4 KB大小)可以自由指定属性,所以上一个4 KB可能是代码,属性是可执行但不可写,下一个4 KB就有可能是既可读也可写但不可执行的数据,再下面呢?有可能是可读不可写也不可执行的数据。Win32汇编源程序中“分段”的概念实际上是把不同类型的数据或代码归类,再放到不同属性的内存页(也就是不同的“分段”)中,这中间不涉及使用不同的段选择器。虽然使用和DOS汇编同样的 .code和 .data语句来定义,意思可是完全不同了!为了简单起见,在本书中还是简称“段”,读者应该注意到其中不同的含义。
上页:第03章 使用MASM · 3.1 Win32汇编源程序的结构(1) 下页:第03章 使用MASM · 3.1 Win32汇编源程序的结构(3)