简介
允许进行决策的程序设计语言使用一种称为条件分支的技术能够在运行时改变控制流程。高级语言中的 IF 语句、SWITCH 语句或条件循环语句都有内建的特定的分支逻辑。汇编语言也提供了逻辑所需的工具。通过本章学习,我们将看到高级的条件分支语句是如何翻译成底层的实现代码的。
处理硬件设备的程序必须能够操控数据中的单个数据位,应该能够测试、清除和设置单个数据位。数据加密和压缩也依赖于位操作。
本章试图解答的一些问题:
如何使用第1章中介绍的布尔运算(AND,OR和NOT)?
在汇编语言中如何写一条IF语句?
编译器是如何将嵌套的 IF 语句翻译成机器语言的?
如何设置和清除二进制数字中的单个位?
如何对数据进行简单的二进制加密?
在布尔表达式中的有符号数和无符号数有什么区别?
什么是无线状态机?
GOTO语句真是有害的吗?
布尔和比较指令
IA-32指令集中包含 AND,OR,XOR,NOT,TEST和BTop指令,实现了字节、字和双字的布尔运算。
CPU 的状态标志
简单回顾一下布尔指令影响的标志位:
零标志操作的结果等于 0 时置位。
进位标志在指令执行产生的结果(视为无符号整数)对目的操作数而言(或太小)而无法容纳时的置位。
符号标志是目的操作数最高位的一份副本,如果目的操作数为负数则设置该标志,如果是正数则清零(0是整数)
溢出标志在指令产生的有符号结果,无效时置位
在指令目的操作数的低字节中,为1的数据位的数量是偶数时设置奇偶标志。
AND 指令
应用案例:
OR 指令
XOR 指令
NOT 指令
TEST 指令
CMP 指令
设置而和清除标志位
条件跳转
条件结构
执行条件语句包括两步骤:
1、使用CMP、AND、SUB之类的指令修改 CPU 标志。
2、使用体哦阿健跳转指令测试标志值并导致向新地址的分支转移。
条件跳转(Jcond)指令
条件跳转指令的类型
条件跳转指令可分为以下4类:
基于特定的标志值的
根据两个操作数是否相等,或根据(E)CX的值的。
基于无符号操作数的比较结果的。
基于有符号操作数的比较结果的。
基于特定 CPU 标志值:零标志、进位标志、溢出标志、就标志和符号标志的跳转指令。
基于恒等性比较的跳转指令
基于无符号数比较的跳转指令
基于有符号数比较的跳转指令
条件跳转指令范围
在 16 位实模式下,条件跳转使用单个有符号字节(相对偏移地址)定位跳转的目标地址,目标地址被限制在 -128~+127 个字节的范围内。
如下图所示,JZ指令的硬编码是 74 03,相对偏移是 03。紧跟 JZ 之后的指令的地址是 0002,因此 CPU 把 0002 和偏移 03 相加,得到偏移 0005
下面的例子演示了向后跳转(负数偏移),跳转指令之后指令的偏移地址是 0005,因此 0005 要和 0FBh(-5)相加,得到偏移地址 0000
16位模式下的长跳转:
16 位模式程序中的跳转目标地址如果超出了 -128~+127 的范围就会报错:“relative jump out of range”。
解决方法:先用 “有条件(JCC)跳转指令” 跳转到 无条件(jmp)跳转指令,最后再由 “无条件跳转指令” 跳到 “目标地址”。逆向过程中会经常遇到这种写法。
32位模式下的跳转
如果跳转的目标地址距离当前地址超出了 -128~+127个字节 的范围,MASM 就会为跳转指令生成 32 为的有符号相对偏移地址。如下所示,标号 L2 距离当前的地址是 130 个字节,因此跳转指令的地址域是 32 为的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ;-------------------------------- ;程序名:JCC.asm ;功能:演示32位 JCC 跳转指令。 ;作者:9unk ;编写时间:2022-12-5 ;-------------------------------- INCLUDE Irvine32.inc .code main PROC jz L1 jz L2 nop nop nop L1: db 127 DUP (0) L2: exit main ENDP END main
使用 32 位偏移地址的跳转指令的操作码为两个字节长,操作码为 0F84h
条件跳转的应用
测试状态位
取三个数中的最小值
下面的指令比较 V1,V2 和 V3 三个无符号变量的值,并把其中最小者送入 AX 寄存器:
数组查找
ArrayScan.asm 程序查找 16 位整数数组中的第一个非零值,如果找到一个匹配项就显示该值,否则显示一条该值无法找到的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ;----------------------------------------------------------- ;程序名:ArrayScan.asm ;功能:查找 16 位整数数组中的第一个非零值,如果找到一个匹配项就显示该值,否则显示一条该值无法找到的信息。 ;作者:9unk ;编写时间:2022-12-5 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data ;intArray SWORD 0,0,0,0,1,20,35,-12,66,4,0 intArray SWORD 0,0,0,0 noneMsg BYTE "A non-zero value was not found",0 .code main PROC MOV ebx,OFFSET intArray MOV ecx,LENGTHOF intArray L1: CMP WORD PTR [ebx],0 JNZ found ADD ebx,2 LOOP L1 jmp notFound found: MOVSX eax,WORD PTR [ebx] CALL WriteInt JMP quit notFound: MOV edx,OFFSET noneMsg CALL WriteString quit: CALL Crlf exit main ENDP END main
字符串加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 ;----------------------------------------------------------- ;程序名:Encrypt.asm ;功能:使用 XOR 指令对字符串加密 ;作者:9unk ;编写时间:2022-12-6 ;----------------------------------------------------------- TITLE Encryption Program INCLUDE Irvine32.inc KEY = 239 BUFMAX = 128 .data sPrompt BYTE "Enter the plain text: ",0 sEncrypt BYTE "Cipher text: ",0 sDecrypt BYTE "Decrypted: ",0 buffer BYTE BUFMAX+1 DUP(0) bufSize DWORD ? .code main PROC CALL InputTheString ;输入明文 CALL TranslateBuffer ;加密缓冲区 MOV edx,OFFSET sEncrypt ;显示加密信息 CALL DisplayMessage CALL TranslateBuffer ;解密缓冲区 MOV edx,OFFSET sDecrypt ;显示解密信息 CALL DisplayMessage exit main ENDP ;------------------------------ InputTheString PROC ;提示用户输入字符串 ;入口参数:无 ;出口参数:无 ;------------------------------ PUSHAD MOV edx,OFFSET sPrompt ;显示提示信息 CALL WriteString MOV ecx,BUFMAX MOV edx,OFFSET buffer ;指向缓冲区 CALL ReadString ;输入字符串 MOV bufSize,eax ;保存字符串长度 CALL Crlf POPAD RET InputTheString ENDP ;-------------------------------- DisplayMessage PROC ;显示加解密字符串 ;入口参数:EDX = 字符串首地址 ;出口参数:无 ;-------------------------------- PUSHAD CALL WriteString MOV edx,OFFSET buffer CALL WriteString CALL Crlf CALL Crlf POPAD RET DisplayMessage ENDP ;----------------------------------- TranslateBuffer PROC ;通过对每个字符串进行异或来加密字符串 ;入口参数:无 ;出口参数:无 ;----------------------------------- PUSHAD MOV ecx,bufSize MOV esi,0 L1: XOR buffer[esi],KEY INC esi LOOP L1 POPAD RET TranslateBuffer ENDP END main
位测试指令
BT、BTC、BTR和BTS指令统称为位测试指令,这些指令很重要,因为它们可以在单条原子指令内可执行多个步骤。位测试指令对多线程程序非常有用,对多线程程序而言,在不被其他线程中断的危险的情况下对重要标志位进行测试、清除、设置或求反是非常重要的。
条件循环指令
LOOPZ 和 LOOPE 指令
LOOPZ 指令(为零则循环)允许在零标志置位并且 ECX 中的无符号值大于 0 时循环,目标标号距 LOOPZ 后下一条指令的距离应该在 -128~+127 字节范围内,指令格式是:
LOOPZ 目的地址
LOOPE 指令(相等则循环)与 LOOPZ 指令是等价的,因为二者的操作码是相同的。LOOPZ 和 LOOPE 指令的执行逻辑如下:
ECX = ECX-1
如果 ECX > 0 并且 ZF = 1,跳转到目的地址
注意: 运行于实地址模式下的程序使用 CX 作为 LOOPZ 指令的默认循环器,如果想强制使用 ECX 作为循环计数器,应使用 LOOPZD 指令。只有 Loopz 比较特殊,其他条件循环指令都是用 ecx 作为计数器。
LOOPNZ 和 LOOPNE 指令
LOOPNZ 指令(不为零则循环),它在 ECX 中的无符号值大于 0 并且零标志复位的状态下进行循环,指令个格式如下:
LOOPNZ 目的地址
LOOPNE 指令(不相等则循环)与 LOOPNZ 指令是等价的,因为二者的机器码相同。LOOPNZ 和 LOOPNE 指令的执行逻辑如下:
ECX=ECX-1
如果 ECX > 0 并且 ZF = 0,跳转到目的地址
注意: 这里的执行逻辑,书中写错了。
条件结构
IF 块结构语句
1 2 3 4 if(表达式) 语句序列1 else 语句序列2
复合表达式
逻辑 AND 运算符
1 2 3 4 if (al>bl) AND (bl>cl){ x=1 }
短路求值:短路求值就是优先判断最重要的条件,如果该条件不成立,那么其他的判断也不需要考虑了。例如这个例子:如果第一个表达式为假(表示整个表达式不成立),则第二个表达式则无需判断。
1 2 3 4 5 6 7 8 9 10 cmp al,bl ja L1 jmp next L1: cmp bl,cl ja L2 jmp next L2: mov x,1 next:
使用 jbe 代替 ja 优化代码:
1 2 3 4 5 6 cmp al,bl jbe next cmp bl,cl jbe next mov x,1 next:
可以发现,把判断反过来写能优化代码。这也就是汇编与C语言的判断都是相反的原因。
逻辑 OR 运算符
当符合表达式中的多个表达式使用逻辑OR运算符连接的时候,只要任何一个表达式为真则复合表达式为真。以下伪代码为例:
1 2 if(al>bl) OR (bl>cl) X = 1
汇编语言实现
1 2 3 4 5 6 7 cmp al,bl ja L1 cmp bl,cl jbe next L1: mov X,1 next:
WHILE 循环
WHILE 结构在执行一块指令前,首先测试谈条件,只要条件为真,就重复执行语句块。下面的循环是用 C++ 写的。
1 2 3 4 5 while (val1 < val2){ val1++; val2--; }
汇编语言实现
1 2 3 4 5 6 7 8 9 10 11 mov eax,val1 @@while: cmp eax,val2 jb next jmp endwhile next: inc eax dec val2 jmp @@while endwhile: mov val1,eax
使用 jge 代替 jb
1 2 3 4 5 6 7 8 9 10 mov eax,val1 @@while: cmp eax,val2 jge endwhile next: inc eax dec val2 jmp @@while endwhile: mov val1,eax
案例:嵌套循环中的 IF 语句
高级语言经常使用表达式嵌套的控制结构。下面的 C++ 例子中,IF语句被嵌套在一个 WHILE 循环中。代码的功能是计算数组中大于变量 sample 所有数组元素之和。
1 2 3 4 5 6 7 8 9 10 11 12 13 int array []={10 ,60 ,20 ,33 ,72 ,89 ,45 ,65 ,72 ,18 };int sample = 50 ;int ArraySize = sizeof array / sizeof sample;int index = 0 ;int sum = 0 ;while (index < ArraySize){ if (array [index] > sample) { sum += array [index]; } index++; }
汇编代码:根据流程图生成汇编代码的最简单的方法就是分别为每个图形实现代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ;----------------------------------------------------------- ;程序名:Flowchart.asm ;功能:计算数组中大于变量 sample 所有数组元素之和。 ;作者:9unk ;编写时间:2023-1-3 ;----------------------------------------------------------- INCLUDE Irvine32.inc sample = 50 .data array dd 10,60,20,33,72,89,45,65,72,18 ArraySize dd ($-array)/TYPE array index dd 0 sum dd 0 .code main PROC mov esi,index mov eax,sum @@while: cmp esi,ArraySize jge endwhile cmp array[esi*4],sample jbe next add eax,array[esi*4] next: inc esi jmp @@while endwhile: mov index,esi mov sum,eax call Dumpregs exit main ENDP END main
以表格驱动的分支选择
这个就是我们在 8086 汇编中学习过的查表法。表格驱动的分支选择是使用表格查找法来替代多路选择结构的一种方法。要使用该方法,必须创建一个包含查找值和过程偏移的表格,程序使用循环来搜索该表格,当需要大量的比较时,这种方法是最方便的。
案例:在下面的例子程序中,用户键盘输入一个字符,程序使用一个循环将该字符同表中的每个项相比较,对于找到的匹配项,调用相应的子程序,打印不同的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 ;----------------------------------------------------------- ;程序名:ProcTble.asm ;功能:定义一个存储子程序地址的表格,用户键盘输入一个字符,程序使用一个循环将该字符同表中的每个项相比较, ;对于找到的匹配项,调用相应的子程序,打印不同的字符串。 ;作者:9unk ;编写时间:2023-1-3 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data CaseTable BYTE 'A' DWORD Process_A BYTE 'B' DWORD Process_B BYTE 'C' DWORD Process_C BYTE 'D' DWORD Process_D EntrySize = TYPE CaseTable NumberOfEntries = ($ - CaseTable)/EntrySize prompt BYTE "Press capotal A,B,C,or D: ",0 msgA BYTE "Process_A",0 msgB BYTE "Process_B",0 msgC BYTE "Process_C",0 msgD BYTE "Process_D",0 .code main PROC mov edx,OFFSET prompt call Writestring call ReadChar mov ebx,OFFSET CaseTable mov ecx,NumberOfEntries L1: cmp al,[ebx] jne L2 call NEAR PTR [ebx+1] call Writestring call crlf jmp L3 L2: add ebx,EntrySize loop L1 L3: exit main ENDP Process_A PROC mov edx,OFFSET msgA ret Process_A ENDP Process_B PROC mov edx,OFFSET msgB ret Process_B ENDP Process_C PROC mov edx,OFFSET msgC ret Process_C ENDP Process_D PROC mov edx,OFFSET msgD ret Process_D ENDP END main
应用:有限状态机
有限状态机(FSM)是依据输入改变事物的当前状态的机器或程序。有限状态机类似于流程图,主要用于分析事物的状态切换,让程序更加简洁易懂,方便后续添加其他功能。在计算机中,有限状态机被应用于建模应用行为,硬件电路系统设置,软件工程,编译器,网络协议和计算与语言的研究。
状态机中有几个术语:state(状态) 、transition(转移) 、action(动作) 、transition condition(转移条件)
state(状态) :将一个系统的功能进行分离,可以得到很多种状态,当然这些状态是有限的。例如:门禁闸机可以划分为开启状态、关闭状态;电扇可以划分为开、关、一档、二档、三档等状态。
transition(转移) :一个状态接收一个输入执行了某些动作到达了另外一个状态的过程。transition(转移)就是在定义状态机的转移流程。
transition condition(转移条件) :也叫做Event(事件),在某一状态下,只有达到了transition condition(转移条件),才会按照状态机的转移流程转移到下一状态,并执行相应的动作。
action(动作):在状态机的运转过程中会有很多种动作。如:进入动作(entry action)[在进入状态时进行]、退出动作(exit action)[在退出状态时进行]、转移动作[在进行特定转移时进行]。
例1:
如下图,就定义了一个只有opened 和closed两种状态的状态机。当系统处于opened状态,在收到输入“关闭事件”,达到了状态机转移条件,系统就转移到了closed状态,并执行相应的动作,此例有一个进入动作(entry action),进入closed状态,会执行close door动作。
例2:
如下图是玩家用 “上方向键"和"下方向键” 控制游戏角色的状态机。其中分别由 “站立”、“下蹲”、“跳跃”、"下斩"四种状态。玩家可以输入相应的方向键控制游戏角色的动作。
输入字符串的验证
读取输入流的程序必须执行一定的错误检查步骤以验证输入。例如,程序设计语言的编译器可使用有限状态机扫描源程序,并把单词和符号转换成关键字、算数运算符和标识符等记号。
使用有限状态机检查输入字符串的有效性时,通常逐个读取字符。有限状态机使用下面的方法检测非法输入,如果出现下面任意一种情况,就可以认为是检测到了非法输入:
下一个输入字符与从当前状态出发的任何一种转换都不能匹配。
输入已经结束,而当前状态不是终结状态。
字符串的例子:依据下面两条规则检查输入字符串的有效性:
字符串必须以字母 x 开始,以字母 z 结束。
在第一个和最后一个字符之间,可以有 0 个或多个字符,但字符必须在范围 {‘a’…‘y’}之内。
有符号整数验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 ;----------------------------------------------------------- ;程序名:Finite.asm ;功能:分析有符号整数的有限状态机。 ;作者:9unk ;编写时间:2023-1-11 ;----------------------------------------------------------- INCLUDE Irvine32.inc ENTER_KEY = 13 .data InvalidInputMsg BYTE "Invalid input",13,10,0 .code main PROC call Clrscr StateA: call Getnext ;读取下一个字符传送到AL cmp al,'+' ;判断第一个字符是否为 '+'号 或 '-'号 je StateB ;如果是,转换到状态B cmp al,'-' je StateB call IsDigit ;如果 AL 中包含一个数字,ZF = 1 jz StateC ;转换到状态C call crlf call DisplayErrorMsg ;如果是无效输出,打印状态报错信息 jmp Quit StateB: call Getnext ;读下一个字符传入 AL call IsDigit ;如果 AL 中包含一个数字,则 ZF=1 jz StateC call crlf call DisplayErrorMsg jmp Quit StateC: call Getnext ;继续接收下一个字符,如果是数字则 ZF=1 call IsDigit ;发现是无效输入 jz StateC cmp al,ENTER_KEY ;判断是否为回车键,不是就报错,是就正常退出 je Quit call crlf call DisplayErrorMsg jmp Quit Quit: call crlf exit ;------------------------------------------ Getnext PROC ; ; Reads a character from standard input. ; Receives: nothing ; Returns:AL contains the character ;------------------------------------------ call ReadChar call WriteChar ret Getnext ENDP ;------------------------ DisplayErrorMsg PROC ; Display an error message indicating that ; the input stream contatins illegal input ; Receives: nothing. ; Returns:nothing ;------------------------ push edx mov edx,OFFSET InvalidInputMsg call Writestring pop edx ret DisplayErrorMsg ENDP main ENDP END main
决策伪指令
MASM 的决策伪指令(.IF,.ELSE,.ELSEIF,.ENDIF)使得在编写涉及到多路分支逻辑的代码更加容易。编译器会为这些伪指令自动生成 CMP 和 条件跳转指令,其格式与高级语言类似:
1 2 3 4 5 6 7 .IF 条件1 语句块1 .ELSEIF 条件2 语句块2 .ELSE 语句块3 .ENDIF
其中的条件是一个布尔表达式,使用的运算符与 C++/Java 中的布尔运算相同。
案例:
1 2 3 4 5 6 7 8 9 eax > 10000h val1 <= 100 val2 == eax val3 != ebx ;复合条件 (eax > 0) && (eax > 10000h) (val1 <= 100) || (val2 <= 100) (val2 != ebx) && !CARRY?
有符号比较和无符号比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ;----------------------------------------------------------- ;程序名:Signum.asm ;功能:演示使用决策伪指令,探究符号比较和无符号数比较 ;作者:9unk ;编写时间:2023-1-12 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data val1 DWORD 5 val2 SDWORD -1 result DWORD ? .code start: mov eax,6 ;无符号数比较 .IF eax > val1 mov result,1 .ENDIF ;有符号数比较 .IF eax > val2 mov result,1 .ENDIF ;寄存器比较 mov ebx,val2 .IF eax > ebx mov result,1 .ENDIF END start exit
计算机时无法判断寄存器的值是有符号数还是无符号数,所以汇编器默认是使用无符号数比较指令 JBE 来实现。
复合表达式
许多复杂的逻辑“或”和逻辑“与”运算符,在使用 .IF 伪指令时,逻辑 “或” 使用 “||”符号:
1 2 3 .IF expression1 || expression2 statements .ENDIF
逻辑 “与” 使用 “&&” 符号:
1 2 3 .IF expression1 && expression2 statements .ENDIF
例1:设置光标位置
下面的 SetCursorPosition 过程对两个输入参数 DH 和 DL 进行范围检查,其中 Y(DH) 坐标必须在 0~24 之间,X(DL) 坐标必须在 0~79 之间。如果其中的任何一个值超出范围,屏幕上将显示一条错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ;----------------------------------------------------------- ;程序名:SetCur.asm ;功能:设计 SetCursorPosition 子程序,Y(DH) 坐标在 0~24 之间,X(DL) 坐标在 0~79 之间。 ;如果查出范围,就在屏幕上显示错误信息。 ;作者:9unk ;编写时间:2023-1-12 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data BadXCoordMsg BYTE "X-Coordinate out of range!",0Dh,0Ah,0 BadYCoordMsg BYTE "Y-Coordinate out of range!",0Dh,0Ah,0 .code main PROC mov dh,25 mov dl,70 call SetCursorPosition exit main ENDP ;----------------------------------------------------- SetCursorPosition PROC ;功能:设置光标位置,并检查光标所在的范围 ;入口参数:DL = X坐标,DH = Y坐标 ;返回值:无 ;----------------------------------------------------- .IF (DL < 0) || (DL > 79) mov edx,OFFSET BadXCoordMsg call WriteString jmp quit .ENDIF .IF (DH < 0) || (DH > 24) mov edx,OFFSET BadYCoordMsg call WriteString jmp quit .ENDIF call Gotoxy quit: ret SetCursorPosition ENDP END main
例2:大学课程注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ;----------------------------------------------------------- ;程序名:Regist.asm ;功能:假设某大学生想注册一门课程,而要注册这门课程需要学生的平均成绩(0~400范围内),其次需要注册这门课程的学分。 ;作者:9unk ;编写时间:2023-1-15 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data TRUE = 1 FALSE = 0 gradeAverage WORD 275 credits WORD 12 OKToRegister BYTE ? .code start: mov OKToRegister,FALSE .IF gradeAverage > 350 mov OKToRegister,TRUE .ELSEIF (gradeAverage > 250) && (credits <= 16) mov OKToRegister,TRUE .ELSEIF (credits <= 12) mov OKToRegister,TRUE .ENDIF exit END start
.REPEAT 和 .WHILE 伪指令
除使用 CMP 和条件条件转指令编写循环外,还可使用 .REPEAT 和 .WHILE 伪指令也可编写循环。.REPEAT 伪指令需要配合 .UNTIL 使用。程序首先执行 .REPEAT 到 .UNTIL 之间的循环体(条件为假时执行循环,条件为真时终止循环)。
格式如下:
1 2 3 .REPEAT 语句块 .UNTIL 测试条件
.WHILE 伪指令首先测试条件(条件为真时执行循环,条件为假时终止循环),然后再执行循环体。.WHILE 伪指令以 .ENDW 作为结束
格式如下:
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ;----------------------------------------------------------- ;程序名:While.asm ;功能:演示 .WHILE 和 .REPEAT 伪指令 ;作者:9unk ;编写时间:2023-1-15 ;----------------------------------------------------------- INCLUDE Irvine32.inc .code start: ;使用 .WHILE 伪指令显示 1~10 之间的值 mov eax,0 .WHILE eax < 10 inc eax call WriteDec call Crlf .ENDW ;使用伪指令 .REPEAT 伪指令显示 11~20 之间的值。 .REPEAT inc eax call WriteDec call Crlf .UNTIL eax == 20 exit END start
例2:
如下伪代码编写成汇编代码
1 2 3 4 5 6 7 8 while(op1 < op2) { op1++; if(op2 == op3) X = 2; else X = 3; }
汇编语言实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ;----------------------------------------------------------- ;程序名:While2.asm ;功能:演示 .WHILE 和 .IF 伪指令配合使用 ;作者:9unk ;编写时间:2023-1-15 ;----------------------------------------------------------- INCLUDE Irvine32.inc test1 EQU 1 .data op1 DD 0 op2 DD 3 op3 DD 5 X DD 0 .code start: mov eax,op1 mov ebx,op2 mov ecx,op3 ;cmp比较只能是 “寄存器与寄存器”、“变量与寄存器”之间的比较。 ;其他的写法都是错误的。 .WHILE eax < op2 inc eax .IF op2 == ecx mov X,2 .ELSE mov X,3 .ENDIF .ENDW exit END start
编程练习
使用 LOOPZ 的 ArrayScan 程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ;----------------------------------------------------------- ;程序名:lx6-1.asm ;功能:使用 LOOPZ 的 ArrayScan 程序 ;作者:9unk ;编写时间:2023-1-18 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data ;intArray SWORD 0,0,0,0,1,20,35,-12,66,4,0 intArray SWORD 0,0,0,0 noneMsg BYTE "A non-zero value was not found",0 .code main PROC MOV ebx,OFFSET intArray MOV ecx,LENGTHOF intArray MOV esi,0 L1: CMP WORD PTR [ebx+esi],0 JNZ found inc esi inc esi LOOPZ L1 jmp notFound found: MOVSX eax,WORD PTR [ebx] CALL WriteInt JMP quit notFound: MOV edx,OFFSET noneMsg CALL WriteString quit: CALL Crlf exit main ENDP END main
循环的实现
以汇编语言实现下面的C++代码,要求使用 .IF 和 .WHILE 伪指令。假设所有变量都是 32 位有符号整数:
1 2 3 4 5 6 7 8 9 10 11 12 13 int array[] = {10,60,20,33,72,89,45,65,72,18}; int sample = 50; int ArraySize = sizeof array / sizeof sample; int index = 0; int sum = 0; while(index < ArraySize) { if(array[index] <= sample) { sum += array[index]; } index++; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ;----------------------------------------------------------- ;程序名:lx6-2.asm ;功能:用汇编实现C++代码 ;作者:9unk ;编写时间:2022-12-5 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data array SDWORD 10,60,20,33,72,89,45,65,72,18 ArraySize DD ($-array)/TYPE array sample EQU 50 index SDWORD 0 sum SDWORD 0 .code main PROC mov esi,index mov eax,sum .WHILE (esi < ArraySize) .IF (array[esi*4] <= sample) add eax,array[esi*4] .ENDIF inc esi .ENDW mov index,esi mov sum,eax call Dumpregs exit main ENDP end main
测验分数的评级(1)
以下表作为参考,写一个程序要求用户输入一个0~100之间的测验分数,程序应当显示对应的字母等级:
分数范围
字母等级
90~100
A
80~89
B
70~79
C
60~69
D
0~59
F
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ;----------------------------------------------------------- ;程序名:lx6-3.asm ;功能:写一个程序要求用户输入一个0~100之间的测验分数,程序应当显示对应的字母等级 ;作者:9unk ;编写时间:2023-1-20 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data msg db '请输入0~100之间的测验分数:',0 .code main PROC mov edx,offset msg call WriteString call ReadInt .IF(eax<=59) mov al,'F' call WriteChar jmp stop .ELSEIF(eax>=60 && eax<=69) mov al,'D' call WriteChar jmp stop .ELSEIF(eax>=70 && eax<=79) mov al,'C' call WriteChar jmp stop .ELSEIF(eax>=80 && eax<=89) mov al,'B' call WriteChar jmp stop .ELSEIF(eax>=90 && eax<=100) mov al,'A' call WriteChar jmp stop .ENDIF stop: exit main ENDP END main
测验分数评级(2)
以上一道习题的解决方案作为起点,在程序中增加以下特性:
程序在循环中运行以便可输入多个测验分数。
累加输入测验分数的次数。
对用户的输入执行范围检车:如果测验分数小于0或大于100 则显示一条错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ;----------------------------------------------------------- ;程序名:lx6-4.asm ;功能:写一个程序要求用户输入一个0~100之间的测验分数,程序应当显示对应的字母等级。 ;作者:9unk ;优化:增加循环、分数判断报错功能。 ;编写时间:2023-1-30 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data msg db '请输入0~100之间的测验分数:',0 msgerr db '无效测验分数',0 .code main PROC mov edx,offset msg call WriteString call ReadInt .WHILE(eax>=0 && eax <= 100) .IF(eax<=59) mov al,'F' call WriteChar jmp stop .ELSEIF(eax>=60 && eax<=69) mov al,'D' call WriteChar jmp stop .ELSEIF(eax>=70 && eax<=79) mov al,'C' call WriteChar jmp stop .ELSEIF(eax>=80 && eax<=89) mov al,'B' call WriteChar jmp stop .ELSEIF(eax>=90 && eax<=100) mov al,'A' call WriteChar jmp stop .ENDIF .ENDW mov edx,offset msgerr call WriteString call Crlf jmp main stop: exit main ENDP END main
大学课程注册(1)
对 credits 的值进行范围检查:不能小于 1 或大于 30,如果发现了无效数值则显示一条相应的错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ;----------------------------------------------------------- ;程序名:lx6-5.asm ;功能:假设某大学生想注册一门课程,而要注册这门课程需要学生的平均成绩(0~400范围内),其次需要注册这门课程的学分。 ;作者:9unk ;优化:检测 credits 值,不能小于1,或大于30 ;编写时间:2023-1-30 ;----------------------------------------------------------- INCLUDE Irvine32.inc .data TRUE = 1 FALSE = 0 gradeAverage WORD 275 credits WORD 32 OKToRegister BYTE ? ErrMsg db "Please enter credits between 1 and 30",0 .code start: mov OKToRegister,FALSE movzx eax,credits mov edx,OFFSET ErrMsg call Check_Score cmp eax,-1 jz stop .IF gradeAverage > 350 mov OKToRegister,TRUE .ELSEIF (gradeAverage > 250) && (credits <= 16) mov OKToRegister,TRUE .ELSEIF (credits <= 12) mov OKToRegister,TRUE .ENDIF stop: exit ;------------------------------------------- Check_Score PROC ;功能:检查 credits 值是否在范围内, ;如果不在范围内,就报错并ret返回程序,如果在范围内就 ret 返回程序。 ;入口参数:eax=credits,edx=错误字符串的偏移地址 ;出口参数:错误:eax=-1,正常:eax=0 ;------------------------------------------ .IF(eax>=1)&&(eax<=30) mov eax,0 .ELSE call WriteString mov eax,-1 .ENDIF ret Check_Score ENDP END start
大学课程注册表(2)
以前面的练习作为起点,写一个完成以下功能的完整程序:
(1)从用户处输入 gradeAverage 和 credits,如果输入的这两个值中的任意一个为 0,则终止程序。
(2)对 credits 和 gradeAverage 进行范围检查。credits 必须在 0~30 之间,后者必须在 0~400 之间,如果任何值超出范围则显示相应的错误信息。
(3)决定该学生是否可以注册并显示一条合适的信息
(4)重复步骤1 到 步骤 3 直到用户决定退出程序为止。
布尔计算器(1)
编写一个程序来实现简单的布尔计算器的功能,操作对象是 32 位整数。显示一个菜单允许用户从下表中选择:
(1)x AND y
(2)x OR y
(3)NOT x
(4)x XOR y
(5)Exit program
用户做出选择时,调用一个过程显示要执行操作的名字。
布尔计算器(2)
继续前面的练习,实现下面的过程:
AND_op:提示用户输入两个十六进制整数,对它们进行 AND 擦欧总并以十六进制数显示结果。
OR_op:提示用户输入两个十六进制整数,对它们进行 OR 操作并以十六进制数显示结果。
NOT_op:提示用户输入一个十六进制整数,对它进行 NOT 操作并以十六进制数显示结果。
XOR_op:提示用户输入两个十六进制整数,对它们进行异或操作并以十六进制数显示结果。
加权概率
写一个程序从三种颜色中随机选择一种并在屏幕上以该颜色显示文本。使用循环显示 20 行文本,每行文本的颜色都是随机选择的。选择每一种颜色的概率如下:白色=30%,蓝色=10%,绿色=60%
打印斐波那契数
写一个程序计算斐波那契数列{1,1,2,3,5,8,13,…},在溢出标志位时终止。显示这些斐波那契数,每行一个。
消息加密
按下面的要求修改加密程序(Encrypt.asm):允许用户输入一个人包含多个字符的密钥,对密钥和明文中的对应字符进行异或操作,以实现用户输入一个包含多个字符的密钥,对密钥和明文中的对应字符进行异或操作,以实现对消息进行加密和解密。如果明文比较长,请重复使用密钥。
加权概念
写一个过程接收 0~100 之间的一个整数 N,在写该过程被调用的时候,应该有 N/100 的概率清除零标志。写一个程序要求用户输入一个 0~100 之间的概率值,调用前面的过程 30 次,每次调用时向该过程传递用户输入的概率值并在过程返回后显示零标志值。
参考
https://www.zhihu.com/question/31634405/answer/2581244793
https://zhuanlan.zhihu.com/p/22976065/