Windows 32 汇编笔记(二):使用 MASM

avatar
作者
筋斗云
阅读量:5

一、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
内存模型平台说明
tinyDOS代码和数据都在一个段内,适用于非常小的程序。
smallDOS代码和数据分别在单独的段内,适用于小型程序。
mediumDOS多个代码段和一个数据段,适用于中型程序。
compactDOS一个代码段和多个数据段,适用于中型程序。
largeDOS多个代码段和多个数据段,适用于大型程序。
hugeDOS类似 large,但数据段可以跨越多个段。
flatWindows/Linux所有代码和数据在一个平坦的 32 位或 64 位地址空间中,适用于现代编程。
调用约定平台说明
C跨平台参数从右到左压入堆栈,由调用者清理堆栈。常用于 C 语言编译器。
PASCAL跨平台参数从左到右压入堆栈,由被调用者清理堆栈。常用于 Pascal 语言编译器。
STDCALLWindows参数从右到左压入堆栈,由被调用者清理堆栈。常用于 Windows API。
FORTRAN跨平台参数从左到右压入堆栈,不清理堆栈。常用于 Fortran 语言编译器。
SYSCALLWindows/Linux用于系统调用,参数传递方式和堆栈管理由操作系统定义。
FASTCALLWindows/Linux部分参数通过寄存器传递,剩余参数从右到左压入堆栈。

1.1.3 option 语句

option 语句用于设置汇编器的选项,控制代码生成和编译行为

option option_name:option_value 

常见的 option 选项

  1. casemap:控制标识符的大小写映射。
  2. dotname:允许标识符中使用点号(.)。
  3. emulator:启用或禁用仿真器选项。
  4. language:设置调用约定和命名约定。

casemap 选项

casemap 选项用于控制汇编器是否区分标识符的大小写。常见的值有 noneallnotpublic

  • none:区分大小写。
  • all:不区分大小写。
  • notpublic:公共标识符不区分大小写,其他标识符区分大小写。
option casemap:none 

dotname 选项

dotname 选项允许在标识符中使用点号(.)。

option dotname

emulator 选项

emulator 选项用于启用或禁用仿真器选项,主要用于调试。

option emulator:on

language 选项

language 选项设置调用约定和命名约定。常见的值有 CPASCALSTDCALL 等。

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 组成,它们是:

  1. Kernel (内核):负责管理系统的底层操作,包括内存管理、进程和线程管理、文件系统操作和硬件设备交互。它提供了操作系统的核心功能,允许应用程序与硬件和操作系统资源进行交互。

  2. User (用户界面):负责管理用户界面元素和用户交互,包括窗口、菜单、按钮等图形用户界面 (GUI) 元素。它提供了创建和管理窗口、处理消息、绘图和与用户交互的功能。

  3. GDI (图形设备接口):负责图形输出和处理,提供绘图功能和图形设备管理。它允许应用程序在屏幕上绘制图形元素,如线条、矩形、位图和文本,支持与打印机等图形输出设备的交互。

Kernel (内核)

内核部分的 API 提供了许多底层功能,使得应用程序能够与操作系统核心交互。这些功能包括但不限于:

  • 进程和线程管理:创建、同步和终止进程和线程。
  • 内存管理:分配和释放内存、内存映射文件和共享内存。
  • 文件系统操作:创建、读取、写入和删除文件和目录,管理文件属性和安全性。
  • 设备管理:与各种硬件设备(如磁盘驱动器、键盘、鼠标等)进行交互。

User (用户界面)

用户界面部分的 API 提供了与 GUI 元素和用户交互相关的功能。这些功能包括:

  • 窗口管理:创建、显示、隐藏和销毁窗口,管理窗口属性和状态。
  • 消息处理:接收和处理系统和用户生成的消息,通过消息循环管理消息队列。
  • 对话框和控件:创建和管理标准对话框和控件,如按钮、文本框、列表框等。
  • 用户输入处理:处理来自键盘、鼠标等输入设备的用户输入。

GDI (图形设备接口)

GDI 部分的 API 提供了图形绘制和图形设备管理的功能。这些功能包括:

  • 基本绘图操作:绘制点、线、矩形、椭圆、多边形等基本图形元素。
  • 位图操作:加载、显示和操作位图图像。
  • 文本输出:在设备上下文中绘制文本,设置字体和文本属性。
  • 设备上下文管理:创建和管理设备上下文,设置和恢复绘图属性。

2.2 调用 API

Win32 API 是用堆栈来传递参数的,调用者把参数一个个压入堆栈,DLL 中的函数程序再从堆栈中取出参数处理 

在 Win32 API 中,消息框(MessageBox)函数用于显示一个包含消息和按钮的对话框,向用户提供信息或询问用户。消息框函数的完整形式是 MessageBoxMessageBoxEx。 

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_ICONEXCLAMATIONMB_ICONWARNING:一个感叹号图标。
      • MB_ICONINFORMATIONMB_ICONASTERISK:一个信息图标。
      • MB_ICONQUESTION:一个问号图标。
      • MB_ICONSTOPMB_ICONERRORMB_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.libuser32.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 的数据结构,包含了 dwValue1dwValue2szText 三个成员。其中,dwValue1dwValue2 是双字节整数(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)

子程序一般包括以下部分:

  1. 子程序的定义(入口点)。
  2. 保存调用者的环境(通常是保存寄存器的内容)。
  3. 执行子程序的任务。
  4. 恢复调用者的环境。
  5. 返回到调用者。

在 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
JZZF = 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 [条件测试表达式])

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!