汇编语言.MODEL伪指令:确定程序的特性


16 位和 32 位模式中,MASM 使用 .MODEL 伪指令确定若干重要的程序特性:内存模式类型、过程命名模式以及参数传递规则。若汇编代码被其他编程语言程序调用,那么后两者就尤其重要。

.MODEL 伪指令的语法如下:

.MODEL memorymodel [,modeloptions]

MemoryModel

下表列出了 memorymodel 字段可选择的模式。除了平坦模式之外,其他所有模式都可以用于 16 位实地址编程。

模式 说明
微模式 一个既包含代码又包含数据的段。文件扩展名为 .com 的程序使用该模式
小模式 一个代码段和一个数据段。默认情况下,所有代码和数据都为近属性
中模式 多个代码段,一个数据段
紧凑模式 一个代码段,多个数据段
大模式 多个代码段和数据段
巨模式 与大模式相同,但是各个数据项可以大于单个段
平坦模式 保护模式。代码与数据使用 32 位偏移量。所有的数据和代码(包括系统资源)都在一个 32 位段内

32 位程序使用平坦内存模式,其偏移量为 32 位,代码和数据最大可达 4GB。比如,Irvine32.inc 文件包含了如下 .MODEL 伪指令:

.model flat, STDCALL

ModelOptions

.MODEL 伪指令中的 ModelOptions 字段可以包含一个语言说明符和一个栈距离。语言说明符指定过程与公共符号的调用和命名规范。栈距离可以是 NEARSTACK(默认值)或者 FARSTACK。

1) 语言说明符

伪指令 .MODEL 有几种不同的可选语言说明符,其中的一些很少使用(比如 BASIC、FORTRAN 和 PASCAL)。反之,C 和 STDCALL 则十分常见。结合平坦内存模式,示例如下:

.model flat, C
.model flat, STDCALL

语言说明符 STDCALL 用于 Windows 系统函数调用。本章在链接汇编代码和 C 与 C++ 程序时,使用 C 语言说明符。

2) STDCALL

STDCALL 语言说明符将子程序参数按逆序(从后往前)压入堆栈。为了便于说明,首先用高级语言编写如下函数调用:

AddTwo(5, 6);

若 STDCALL 被选为语言说明符,则等效的汇编语言代码如下:

push 6
push 5
call AddTwo

另一个重要的考虑是,过程调用后如何从堆栈中移除参数。STDCALL 要求在 RET 指令中带一个常量操作数。返回地址从堆栈中弹出后,该常数为 RET 执行与 ESP 相加的数值:

AddTwo PROC
    push ebp
    mov ebp,esp
    mov eax, [ebp + 12]    ;第二个参数
    add eax, [ebp + 8]       ;第一个参数
    pod ebp
ret 8                  ;清除堆栈
AddTwo ENDPP

堆栈指针加上 8 后,就指回了主调程序参数入栈之前指针的位置。

最后,STDCALL 通过将输出(公共)过程名保存为如下格式来修改这些名称:

_name@nn

前导下划线添加到过程名,@ 符号后面的整数指定了过程参数的字节数(向上舍入到 4 的倍数)。例如,假设过程 AddTwo 带有两个双字参数,那么汇编器传递给链接器的名称就为 _AddTwo@8。

Microsoft 链接器是区分大小写的,因此 _MYSUB@8 和 _MySub@8 是两个不同的名称。要查看 OBJ 文件中所有的过程名,使用 Visual Studio 中的 DUMPBIN 工具,选项为 /SYMBOLS。

3) C 说明符

和 STDCALL 一样,C 语言说明符也要求将过程参数按从后往前的顺序压入堆栈。对于过程调用后从堆栈中移除参数的问题,C 语言说明符将这个责任留给了主调方。在主调程序中,ESP 与一个常数相加,将其再次设置为参数入栈之前的位置:

push 6           ;第二个参数
push 5           ;第一个参数
call AddTwo
add esp,8       ;清除堆栈

C 语言说明符在外部过程名的前面添加前导下划线。示例如下:

_AddTwo