一、Win32 汇编源程序的结构
; Hello.asm ; 使用 Win32 ASM 写的 Hello, world 程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 使用 nmake 或下列命令进行编译和链接: ; ml /c /coff Hello.asm ; Link /subsystem:windows Hello.obj ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .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
1.1 模式定义
程序的第一部分是模式和源程序格式的定义语句:
.386 .model flat,stdcall option casemap:none
1.1.1 指定使用的指令集(.number)
.386 语句是汇编语言的伪指令,它在低版本的宏汇编中就已经存在
.386
后面带 p 的伪指令则表示程序中可以使用特权指令,如:
mov cr0,eax
这一类指令必须在特权级 0 上运行,那么必须定义 .386p
如果只使用 .386 ,那只能使用普通指令
1.1.2 .model 语句
.model语句在低版本的宏汇编中已经存在,用来定义程序工作的模式
.model memory_model, language_type
内存模型 | 平台 | 说明 |
---|---|---|
tiny | DOS | 代码和数据都在一个段内,适用于非常小的程序。 |
small | DOS | 代码和数据分别在单独的段内,适用于小型程序。 |
medium | DOS | 多个代码段和一个数据段,适用于中型程序。 |
compact | DOS | 一个代码段和多个数据段,适用于中型程序。 |
large | DOS | 多个代码段和多个数据段,适用于大型程序。 |
huge | DOS | 类似 large,但数据段可以跨越多个段。 |
flat | Windows/Linux | 所有代码和数据在一个平坦的 32 位或 64 位地址空间中,适用于现代编程。 |
调用约定 | 平台 | 说明 |
---|---|---|
C | 跨平台 | 参数从右到左压入堆栈,由调用者清理堆栈。常用于 C 语言编译器。 |
PASCAL | 跨平台 | 参数从左到右压入堆栈,由被调用者清理堆栈。常用于 Pascal 语言编译器。 |
STDCALL | Windows | 参数从右到左压入堆栈,由被调用者清理堆栈。常用于 Windows API。 |
FORTRAN | 跨平台 | 参数从左到右压入堆栈,不清理堆栈。常用于 Fortran 语言编译器。 |
SYSCALL | Windows/Linux | 用于系统调用,参数传递方式和堆栈管理由操作系统定义。 |
FASTCALL | Windows/Linux | 部分参数通过寄存器传递,剩余参数从右到左压入堆栈。 |
1.1.3 option 语句
option
语句用于设置汇编器的选项,控制代码生成和编译行为
option option_name:option_value
常见的 option 选项
- casemap:控制标识符的大小写映射。
- dotname:允许标识符中使用点号(.)。
- emulator:启用或禁用仿真器选项。
- language:设置调用约定和命名约定。
casemap 选项
casemap
选项用于控制汇编器是否区分标识符的大小写。常见的值有 none
、all
和 notpublic
。
none
:区分大小写。all
:不区分大小写。notpublic
:公共标识符不区分大小写,其他标识符区分大小写。
option casemap:none
dotname 选项
dotname
选项允许在标识符中使用点号(.)。
option dotname
emulator 选项
emulator
选项用于启用或禁用仿真器选项,主要用于调试。
option emulator:on
language 选项
language
选项设置调用约定和命名约定。常见的值有 C
、PASCAL
、STDCALL
等。
option language:STDCALL
1.2 段的定义
1.2.1 段的概念
把上面的 Win32 的 Hello World 源程序中的语句归纳精简一下,再列在下面:
.386 .model flat,stdcall option casemap:none <一些include 语句> .data <一些字符串、变量定义> .code <开始标号> <其他语句> end 开始标号
1.2.2 数据段(.data .data? .const)
.data,.data? 和 .const 定义的是数据段,分别对应不同方式的数据定义
.data
段用于定义已初始化的数据。这些数据在程序开始执行时已经被初始化为特定的值,并且存储在数据段中。
.data message db "Hello, World!", 0 number dd 12345678h
- message:一个包含字符串 "Hello, World!" 的字节数组,以 0 结尾。
- number:一个初始化为
12345678h
的双字(32 位)变量。
.data?
段用于定义未初始化的数据。这些数据在程序开始执行时没有被初始化,通常在运行时由程序初始化。.data?
段中的数据在可执行文件中不会占用物理空间,通常在加载程序时由操作系统分配和清零。
.data? buffer db 256 dup (?) counter dd ?
- buffer:一个 256 字节长的缓冲区,初始值未定义。
- counter:一个未初始化的双字变量。
.const
段用于定义只读常量数据。这些数据在程序执行期间是只读的,不能被修改。.const
段通常用于存储不会改变的常量值,例如字符串常量、数学常量等。
.const pi real4 3.1415927 e real4 2.7182818
- pi:一个初始化为
3.1415927
的单精度浮点数常量。 - e:一个初始化为
2.7182818
的单精度浮点数常量。
段名称 | 说明 | 初始化状态 | 可修改性 | 占用物理空间 |
---|---|---|---|---|
.data | 用于定义已初始化的数据。 | 已初始化 | 可修改 | 占用 |
.data? | 用于定义未初始化的数据。 | 未初始化 | 可修改 | 不占用 |
.const | 用于定义只读常量数据。 | 已初始化 | 只读 | 占用 |
1.2.3 代码段(.code)
.code 段是代码段,所有的指令都必须写在代码段中
1.2.4 堆栈段(可忽略)
在程序中不必定义堆栈段,系统会自动分配堆栈空间(所以上面代码忽略了)
二、调用 API
2.1 API 是什么
Win32 API 核心由三个主要的子系统 API 组成,它们是:
Kernel (内核):负责管理系统的底层操作,包括内存管理、进程和线程管理、文件系统操作和硬件设备交互。它提供了操作系统的核心功能,允许应用程序与硬件和操作系统资源进行交互。
User (用户界面):负责管理用户界面元素和用户交互,包括窗口、菜单、按钮等图形用户界面 (GUI) 元素。它提供了创建和管理窗口、处理消息、绘图和与用户交互的功能。
GDI (图形设备接口):负责图形输出和处理,提供绘图功能和图形设备管理。它允许应用程序在屏幕上绘制图形元素,如线条、矩形、位图和文本,支持与打印机等图形输出设备的交互。
Kernel (内核)
内核部分的 API 提供了许多底层功能,使得应用程序能够与操作系统核心交互。这些功能包括但不限于:
- 进程和线程管理:创建、同步和终止进程和线程。
- 内存管理:分配和释放内存、内存映射文件和共享内存。
- 文件系统操作:创建、读取、写入和删除文件和目录,管理文件属性和安全性。
- 设备管理:与各种硬件设备(如磁盘驱动器、键盘、鼠标等)进行交互。
User (用户界面)
用户界面部分的 API 提供了与 GUI 元素和用户交互相关的功能。这些功能包括:
- 窗口管理:创建、显示、隐藏和销毁窗口,管理窗口属性和状态。
- 消息处理:接收和处理系统和用户生成的消息,通过消息循环管理消息队列。
- 对话框和控件:创建和管理标准对话框和控件,如按钮、文本框、列表框等。
- 用户输入处理:处理来自键盘、鼠标等输入设备的用户输入。
GDI (图形设备接口)
GDI 部分的 API 提供了图形绘制和图形设备管理的功能。这些功能包括:
- 基本绘图操作:绘制点、线、矩形、椭圆、多边形等基本图形元素。
- 位图操作:加载、显示和操作位图图像。
- 文本输出:在设备上下文中绘制文本,设置字体和文本属性。
- 设备上下文管理:创建和管理设备上下文,设置和恢复绘图属性。
2.2 调用 API
Win32 API 是用堆栈来传递参数的,调用者把参数一个个压入堆栈,DLL 中的函数程序再从堆栈中取出参数处理
在 Win32 API 中,消息框(MessageBox)函数用于显示一个包含消息和按钮的对话框,向用户提供信息或询问用户。消息框函数的完整形式是 MessageBox
或 MessageBoxEx
。
int MessageBox( HWND hWnd, // 父窗口句柄 LPCSTR lpText, // 消息内容 LPCSTR lpCaption, // 标题 UINT uType // 类型 );
还有最后一句说明
Library: Use User32.lib
参数说明
hWnd:父窗口的句柄。如果消息框是顶层窗口,则此参数可以为 NULL。
lpText:显示在消息框中的文本内容。
lpCaption:消息框的标题。
uType:消息框的类型和按钮样式,可以是以下值的组合:
按钮选项:
MB_OK
:一个“确定”按钮。MB_OKCANCEL
:一个“确定”按钮和一个“取消”按钮。MB_YESNO
:一个“是”按钮和一个“否”按钮。MB_YESNOCANCEL
:一个“是”按钮、“否”按钮和一个“取消”按钮。MB_RETRYCANCEL
:一个“重试”按钮和一个“取消”按钮。MB_ABORTRETRYIGNORE
:一个“中止”按钮、“重试”按钮和一个“忽略”按钮。
图标选项:
MB_ICONEXCLAMATION
或MB_ICONWARNING
:一个感叹号图标。MB_ICONINFORMATION
或MB_ICONASTERISK
:一个信息图标。MB_ICONQUESTION
:一个问号图标。MB_ICONSTOP
或MB_ICONERROR
或MB_ICONHAND
:一个错误图标。
默认按钮:
MB_DEFBUTTON1
:第一个按钮是默认按钮。MB_DEFBUTTON2
:第二个按钮是默认按钮。MB_DEFBUTTON3
:第三个按钮是默认按钮。
返回值
返回用户点击的按钮。可能的返回值包括:
IDOK
:用户点击了“确定”按钮。IDCANCEL
:用户点击了“取消”按钮。IDYES
:用户点击了“是”按钮。IDNO
:用户点击了“否”按钮。IDRETRY
:用户点击了“重试”按钮。IDABORT
:用户点击了“中止”按钮。IDIGNORE
:用户点击了“忽略”按钮。
上面的声明用汇编的格式来表达就是:
MessageBox Proto hWnd:dword lpText:dword lpCaption:dword uType:dword
在汇编中调用 MessageBox 函数的方法是:
push hWnd push lpText push lpCaption push uType call MessageBox
2.2.1 使用 invoke 语句
invoke function_name, arg1, arg2, ..., argN
- function_name:要调用的函数的名称。
- arg1, arg2, ..., argN:传递给函数的参数。
之前的 HelloWord 案例是
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
2.2.2 API 的返回值(存放在 eax 中)
有的 API 函数有返回值,如 MessageBox 定义的返回值是 int 类型的数,返回值的类型对汇编程序来说也只有 dword 一种类型,它永远放在 eax 中。如果要返回的内容不是一个 eax 所能容纳的,Win32 API 采用的方法一般是 eax 中返回一个指向返回数据的指针,或者在调用参数中提供一个缓冲区地址,干脆把数据直接返回到缓冲区中去。
2.2.3 函数的声明
在调用 API 函数的时候,函数原型也必须预先声明,否则,编译器会不认这个函数
函数名 proto [距离] [语言] [参数1:数据类型,[参数2]:数据类型,……
Win32 中只有一个平坦的段,无所谓距离,所以在定义时是忽略的
语言如果忽略,则默认使用 .model 定义的类型
参数名称也可以忽略,仅仅为了可读性而设置的
MessageBox Proto :dword,:dword,:dword,:dword
在 Win32 环境中,和字符串相关的 API 共有两类,分别对应两个字符集:一类是处理 ANSI 字符集的,另一类是处理 Unicode 字符集的。前一类函数名字的尾部带一个 “A” 字符,处理 Unicode 的则带一个 “W” 字符
MessageBox 和显示字符串有关,同样它有两个版本,严格地说,系统中有两个定义:
MessageBoxA Proto hWnd:dword lpText:dword lpCaption:dword uType:dword MessageBoxW Proto hWnd:dword lpText:dword lpCaption:dword uType:dword
这才是最终版本,必须选择一类字符,前面的 MessageBox 默认是以 A 类处理
2.2.4 include 语句
include filename
- filename:要包含的文件的名称。通常,这些文件的扩展名是
.inc
。
常见的 include
文件
在 Win32 汇编中,通常会包含一些标准的头文件,这些文件包含了常用的 Windows API 函数、常量和结构定义。以下是一些常见的 include
文件:
- windows.inc:包含 Windows API 函数和常量的定义。
- kernel32.inc:包含 Kernel32.dll 中的函数和常量的定义。
- user32.inc:包含 User32.dll 中的函数和常量的定义。
- masm32.inc:包含 MASM32 库中的函数和宏的定义。
2.2.5 includelib 语句
includelib library_name.lib
- library_name.lib:要链接的库文件的名称。
注意事项
- 在使用
includelib
之前,通常需要先使用include
将相应的头文件包含进来,以便汇编器能够识别和理解相应的函数和常量。 - 如果您使用的是特定于平台的库文件(如 Windows API 的库文件),请确保在正确的环境中链接相应的库文件。例如,在使用 MASM 编译器时,通常需要链接
kernel32.lib
和user32.lib
。
三、标号、变量和数据结构
在MASM中标号和变量的命名规范是相同的,它们是:
(1)可以用字母、数字、下划线及符号@、$ 和 ?
(2)第一个符号不能是数字
(3)长度不能超过 240个字符
(4)不能使用指令名等关键字
(5)在作用域内必须是唯一的
3.1 标号
3.1.1 标号的定义
start: ; 程序的入口点标号 ; ...
3.1.2 MASM 中的 @@
在 MASM(Microsoft Macro Assembler)中,@@
是一个特殊的符号,用于生成唯一的局部标号。它通常用于帮助编写和管理复杂的宏定义或重复代码段。
生成唯一的局部标号:
@@
可以用作局部标号的一部分,帮助区分不同的代码段或处理不同的数据。
.data array dd 1, 2, 3, 4, 5 .code start: mov ecx, 5 L1 @@: ; 使用 @@ 生成唯一标号 mov eax, [array + ecx * 4] ; 处理数据 loop L1 @B ; @B 表示回到上一次使用 @@ 生成的标号处
当用 @@ 做标号时,可以用 @F 和 @B 来引用它,@F 表示本条指令后的第一个 @@ 标号,@B 表示本条指令前的第一个 @@ 标号,程序中可以有多个 @@ 标号,但 @B 和 @F 只寻找匹配最近的一个。
3.2 局部变量
3.2.1 局部变量的定义(local)
在宏定义中,可以使用 local
指令声明局部变量,以便在宏展开时为特定的数据或计算提供临时存储空间。局部变量在宏展开时被分配和使用,其生命周期与宏展开的时间段相同。
MyMacro MACRO param1:DWORD, param2:DWORD LOCAL localVar1:DWORD LOCAL localVar2:DWORD ; 使用局部变量 mov localVar1, param1 add localVar1, 10 mov localVar2, param2 sub localVar2, 5 ; 使用局部变量 add localVar1, localVar2 ENDM
3.3 数据结构(STRUCT)
; 定义一个数据结构 MyStruct STRUCT dwValue1 DWORD ? dwValue2 DWORD ? szText DB 256 DUP (?) ; 字符串,256字节 MyStruct ENDS ; 定义一个变量,使用上面的数据结构 .data myData MyStruct <1, 2, "Hello"> .code start: ; 访问结构成员 mov eax, myData.dwValue1 mov ebx, myData.dwValue2 mov edx, OFFSET myData.szText ; 使用结构成员 mov eax, [edx] ; 访问字符串地址 ; ...
在上面的示例中:
MyStruct STRUCT
定义了一个名为MyStruct
的数据结构,包含了dwValue1
、dwValue2
和szText
三个成员。其中,dwValue1
和dwValue2
是双字节整数(DWORD),szText
是一个256字节的字符串(256 DUP (?) 表示重复256次问号填充)。MyStruct ENDS
表示数据结构的结束。.data
部分用于定义数据段,其中myData
是一个使用MyStruct
结构初始化的变量。在
.code
部分,可以通过偏移量和结构成员的名称来访问和操作myData
结构中的各个成员。例如,使用mov eax, myData.dwValue1
可以将dwValue1
的值加载到eax
寄存器中。
这种方式使得在汇编语言中能够方便地组织和管理复杂的数据结构,有助于提高代码的可读性和可维护性。
3.4 变量的使用(重点)
3.4.1 使用 ptr 以不同的类型访问变量
MASM 中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度检验,仅使用变量的地址。使用的方法是:
类型 ptr 变量名
阅读下面代码:
.data bTest1 db 12H wTest2 dw 1234H dwTest3 dd 12345678H .code mov al,bTest1 mov ax,word ptr bTest1 mov eax,dword ptr bTest1
.data 段中的变量是按顺序从低地址往高地址排列的
对于超过一个字节的数据,80386处理器的数据排列方式是低位数据在低地址
所以 wTest2 的 1234h 在内存中的排列是 34h12h
因为 34h是低位。同样,dwTest3 在内存中以 78h 56h 34h 12h 从低地址往高地址存放
执行 mov ax, word ptr bTest1,因为超过了长度范围,所以取到了 wTest2 的低字节
这个例子说明了汇编中用 ptr 强制覆盖变量长度的时候,实质上只用了变量的地址,编译器并不会考虑定界的问题,程序员在使用的时候必须对内存中的数据排列有个全局概念,以免越界存取到意料之外的数据。
3.4.2 变量扩展命令(movzx)
想把 bTest1 的一个字节扩展到一个字或一个双字再放到 ax 或 eax 中,高位保持 0 而不是越界存取到其他的变量,可以用 80386 的扩展指令来实现。80386处理器提供的 movzx 指令可以实现这个功能,例如:
movzx ax,bTest1 movzx eax,bTest1 movzx eax,cl movzx eax,ax
例 1 把单字节变量 bTest1 的值扩展到 16 位放入 ax 中。
例 2 把单字节变量 bTest1 的值扩展到 32 位放入 eax 中。
例 3 把 cl 中的 8 位值扩展到 32 位放入 eax 中。
例 4 把 ax 中的 16 位值扩展到 32 位放入 eax 中。
3.4.3 变量的尺寸和数量(sizeof lengthof)
在源程序中用到变量的尺寸和数量的时候,可以用 sizeof 和 lengthof 伪指令来实现
sizeof 变量名、数据类型或数据结构名变量名、数据类型或数据结构名 lengthof 变量名、数据类型或数据结构名变量名、数据类型或数据结构名
sizeof
伪指令
sizeof
伪指令用于计算指定变量或数据结构的字节大小。它返回的是分配给该变量的内存大小。
.data myByte db 10h ; 定义一个字节变量 myWord dw 1234h ; 定义一个字变量 myDword dd 12345678h ; 定义一个双字变量 myArray db 10 DUP (?) ; 定义一个包含10个字节的数组 .code start: mov eax, sizeof myByte ; eax = 1,因为 myByte 是一个字节 mov ebx, sizeof myWord ; ebx = 2,因为 myWord 是一个字(2字节) mov ecx, sizeof myDword ; ecx = 4,因为 myDword 是一个双字(4字节) mov edx, sizeof myArray ; edx = 10,因为 myArray 是一个包含10个字节的数组 ; 继续程序...
lengthof
伪指令
lengthof
伪指令用于计算数组中元素的个数。它返回的是数组的元素数量,而不是数组占用的字节
.data myArray db 10 DUP (?) ; 定义一个包含10个字节的数组 .code start: mov eax, lengthof myArray ; eax = 10,因为 myArray 包含10个元素 ; 继续程序...
3.4.4 获取变量地址(offset lea addr)
对于全局变量,它的地址在编译的时候已经由编译器确定了,它的用法大家都不陌生:
mov 寄存器,offset 变量名
对于局部变量,它是用 ebp 来做指针操作的,有一条指令用来取指针的地址,就是 lea 指令
LEA destination, source
destination
: 目标寄存器,用于存储计算出的有效地址。source
: 内存操作数,用于指定要计算的地址。
假设我们有一个数组,每个元素占用 4 个字节:
.data array DWORD 10 DUP(0) ; 定义一个包含 10 个双字(4 字节)的数组 .code start: mov esi, 2 ; 数组索引,假设我们要访问 array[2] lea eax, array[esi*4] ; 计算 array[2] 的地址,并存储到 EAX 寄存器中 ; 现在 EAX 包含 array[2] 的地址
假设我们有一个结构体:
MyStruct STRUCT field1 DWORD ? field2 DWORD ? MyStruct ENDS .data myVar MyStruct <1, 2> ; 定义一个 MyStruct 类型的变量 .code start: lea ebx, myVar.field2 ; 计算 myVar.field2 的地址,并存储到 EBX 寄存器中 ; 现在 EBX 包含 myVar.field2 的地址
如果要在 invoke 伪指令的参数中用到一个局部变量的地址,该怎么办呢?
参数中是不可能写入 lea 指令的,用 ofset 又是不对的。MASM 对此有一个专用的伪操作符 addr
ADDR variable
示例 1: 使用 ADDR
传递字符串地址
.data myString db "Hello, World!", 0 .code start: INVOKE MessageBox, NULL, ADDR myString, ADDR myString, MB_OK ; MessageBox 是一个标准的 Win32 API 函数 ; ADDR myString 用于传递 myString 的地址 INVOKE ExitProcess, 0 end start
四、使用子程序
4.1 子程序的定义(proc endp)
子程序一般包括以下部分:
- 子程序的定义(入口点)。
- 保存调用者的环境(通常是保存寄存器的内容)。
- 执行子程序的任务。
- 恢复调用者的环境。
- 返回到调用者。
在 MASM 中定义子程序
myProcedure PROC ; 保存调用者的环境 push ax push bx ; 执行子程序的任务 mov ax, 1234h mov bx, 5678h ; 恢复调用者的环境 pop bx pop ax ; 返回调用者 ret myProcedure ENDP
五、高级语法
5.1 条件测试语句
操作符 | 含义 | 适用情况 |
---|---|---|
JE/JZ | 等于 / 零 | 比较结果为零 |
JNE/JNZ | 不等于 / 非零 | 比较结果不为零 |
JA/JNBE | 高于(无符号数) | 不小于等于 |
JAE/JNB | 高于或等于(无符号数) | 不小于 |
JB/JNAE | 低于(无符号数) | 不大于等于 |
JBE/JNA | 低于或等于(无符号数) | 不大于 |
JG/JNLE | 大于(有符号数) | 不小于等于 |
JGE/JNL | 大于或等于(有符号数) | 不小于 |
JL/JNGE | 小于(有符号数) | 不大于等于 |
JLE/JNG | 小于或等于(有符号数) | 不大于 |
JC | 有进位 | CF = 1 |
JNC | 无进位 | CF = 0 |
JO | 溢出 | OF = 1 |
JNO | 无溢出 | OF = 0 |
JS | 负数 | SF = 1 |
JNS | 非负数 | SF = 0 |
JP/JPE | 偶数 | PF = 1 |
JNP/JPO | 奇数 | PF = 0 |
JZ | 零 | ZF = 1 |
JNZ | 非零 | ZF = 0 |
两个以上的表达式可以用逻辑运算符连接:
(表达式 1) 逻辑运算符 (表达式 2) 逻辑运算符 (表达式 3)
CARRY? 表示 Carry 位是否置位 OVERFLOW? 表示 Overflow 位是否置位 PARITY? 表示Parity 位是否置位 SIGN? 表示 sign 位是否置位表示 ZERO? 表示 zero 位是否置位
要测试 eax 等于 ebx 同时 Zero 位置位,条件表达式可以写为:
eax==ebx) && ZERO?
5.2 分支语句(.if .elseif .else .endif)
.if 条件表达式1 表达式1为“真"时执行的指令 [.elseif 条件表达式2] 表达式2为"真"时执行的指令 [.elseif 条件表达式3] 表达式3为"真"时执行的指令 [.else] 所有表达式为"否"时执行的指令 .endif
5.3 循环语句(.while .break .continue .until .untilcxz .endw .repeat)
.while 条件测试表达式 指令 [.break [.if 退出条件]] [.continue] .endw
或者
.repeat 指令 [.break [.if 退出条件]] [.continue] .until 条件测试表达式 (或.untilcxz [条件测试表达式])