80386汇编-条件处理
9unk Lv5

简介

允许进行决策的程序设计语言使用一种称为条件分支的技术能够在运行时改变控制流程。高级语言中的 IF 语句、SWITCH 语句或条件循环语句都有内建的特定的分支逻辑。汇编语言也提供了逻辑所需的工具。通过本章学习,我们将看到高级的条件分支语句是如何翻译成底层的实现代码的。

处理硬件设备的程序必须能够操控数据中的单个数据位,应该能够测试、清除和设置单个数据位。数据加密和压缩也依赖于位操作。

本章试图解答的一些问题:

  • 如何使用第1章中介绍的布尔运算(AND,OR和NOT)?
  • 在汇编语言中如何写一条IF语句?
  • 编译器是如何将嵌套的 IF 语句翻译成机器语言的?
  • 如何设置和清除二进制数字中的单个位?
  • 如何对数据进行简单的二进制加密?
  • 在布尔表达式中的有符号数和无符号数有什么区别?
  • 什么是无线状态机?
  • GOTO语句真是有害的吗?

布尔和比较指令

IA-32指令集中包含 AND,OR,XOR,NOT,TEST和BTop指令,实现了字节、字和双字的布尔运算。
1

CPU 的状态标志

简单回顾一下布尔指令影响的标志位:

  • 零标志操作的结果等于 0 时置位。
  • 进位标志在指令执行产生的结果(视为无符号整数)对目的操作数而言(或太小)而无法容纳时的置位。
  • 符号标志是目的操作数最高位的一份副本,如果目的操作数为负数则设置该标志,如果是正数则清零(0是整数)
  • 溢出标志在指令产生的有符号结果,无效时置位
  • 在指令目的操作数的低字节中,为1的数据位的数量是偶数时设置奇偶标志。

AND 指令

2
应用案例:
3

OR 指令

4
5
6

XOR 指令

7
8

NOT 指令

9

TEST 指令

10
11

CMP 指令

12

设置而和清除标志位

13

条件跳转

条件结构

执行条件语句包括两步骤:
1、使用CMP、AND、SUB之类的指令修改 CPU 标志。
2、使用体哦阿健跳转指令测试标志值并导致向新地址的分支转移。

14

条件跳转(Jcond)指令

15

条件跳转指令的类型

条件跳转指令可分为以下4类:

  • 基于特定的标志值的
  • 根据两个操作数是否相等,或根据(E)CX的值的。
  • 基于无符号操作数的比较结果的。
  • 基于有符号操作数的比较结果的。

基于特定 CPU 标志值:零标志、进位标志、溢出标志、就标志和符号标志的跳转指令。

16

基于恒等性比较的跳转指令

17

基于无符号数比较的跳转指令

18

基于有符号数比较的跳转指令

19

条件跳转指令范围

在 16 位实模式下,条件跳转使用单个有符号字节(相对偏移地址)定位跳转的目标地址,目标地址被限制在 -128~+127 个字节的范围内。

如下图所示,JZ指令的硬编码是 74 03,相对偏移是 03。紧跟 JZ 之后的指令的地址是 0002,因此 CPU 把 0002 和偏移 03 相加,得到偏移 0005
20

下面的例子演示了向后跳转(负数偏移),跳转指令之后指令的偏移地址是 0005,因此 0005 要和 0FBh(-5)相加,得到偏移地址 0000
21

  • 16位模式下的长跳转:
    16 位模式程序中的跳转目标地址如果超出了 -128~+127 的范围就会报错:“relative jump out of range”。
    解决方法:先用 “有条件(JCC)跳转指令” 跳转到 无条件(jmp)跳转指令,最后再由 “无条件跳转指令” 跳到 “目标地址”。逆向过程中会经常遇到这种写法。

22

  • 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

23

使用 32 位偏移地址的跳转指令的操作码为两个字节长,操作码为 0F84h

条件跳转的应用

测试状态位

24

取三个数中的最小值

下面的指令比较 V1,V2 和 V3 三个无符号变量的值,并把其中最小者送入 AX 寄存器:
25
26

数组查找

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指令统称为位测试指令,这些指令很重要,因为它们可以在单条原子指令内可执行多个步骤。位测试指令对多线程程序非常有用,对多线程程序而言,在不被其他线程中断的危险的情况下对重要标志位进行测试、清除、设置或求反是非常重要的。
27
28
29
30
31

条件循环指令

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

32
33
34
35

复合表达式

逻辑 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++;
}

汇编代码:根据流程图生成汇编代码的最简单的方法就是分别为每个图形实现代码。

36

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’}之内。

37

有符号整数验证

38

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 中的布尔运算相同。
39

案例:

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

40

计算机时无法判断寄存器的值是有符号数还是无符号数,所以汇编器默认是使用无符号数比较指令 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
2
3
.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

编程练习

  1. 使用 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
  1. 循环的实现
    以汇编语言实现下面的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. 测验分数的评级(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
  1. 测验分数评级(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. 大学课程注册(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
  1. 大学课程注册表(2)
    以前面的练习作为起点,写一个完成以下功能的完整程序:
  • (1)从用户处输入 gradeAverage 和 credits,如果输入的这两个值中的任意一个为 0,则终止程序。
  • (2)对 credits 和 gradeAverage 进行范围检查。credits 必须在 0~30 之间,后者必须在 0~400 之间,如果任何值超出范围则显示相应的错误信息。
  • (3)决定该学生是否可以注册并显示一条合适的信息
  • (4)重复步骤1 到 步骤 3 直到用户决定退出程序为止。
  1. 布尔计算器(1)
    编写一个程序来实现简单的布尔计算器的功能,操作对象是 32 位整数。显示一个菜单允许用户从下表中选择:
    (1)x AND y
    (2)x OR y
    (3)NOT x
    (4)x XOR y
    (5)Exit program
    用户做出选择时,调用一个过程显示要执行操作的名字。

  2. 布尔计算器(2)
    继续前面的练习,实现下面的过程:

  • AND_op:提示用户输入两个十六进制整数,对它们进行 AND 擦欧总并以十六进制数显示结果。
  • OR_op:提示用户输入两个十六进制整数,对它们进行 OR 操作并以十六进制数显示结果。
  • NOT_op:提示用户输入一个十六进制整数,对它进行 NOT 操作并以十六进制数显示结果。
  • XOR_op:提示用户输入两个十六进制整数,对它们进行异或操作并以十六进制数显示结果。
  1. 加权概率
    写一个程序从三种颜色中随机选择一种并在屏幕上以该颜色显示文本。使用循环显示 20 行文本,每行文本的颜色都是随机选择的。选择每一种颜色的概率如下:白色=30%,蓝色=10%,绿色=60%

  2. 打印斐波那契数
    写一个程序计算斐波那契数列{1,1,2,3,5,8,13,…},在溢出标志位时终止。显示这些斐波那契数,每行一个。

  3. 消息加密
    按下面的要求修改加密程序(Encrypt.asm):允许用户输入一个人包含多个字符的密钥,对密钥和明文中的对应字符进行异或操作,以实现用户输入一个包含多个字符的密钥,对密钥和明文中的对应字符进行异或操作,以实现对消息进行加密和解密。如果明文比较长,请重复使用密钥。

  4. 加权概念
    写一个过程接收 0~100 之间的一个整数 N,在写该过程被调用的时候,应该有 N/100 的概率清除零标志。写一个程序要求用户输入一个 0~100 之间的概率值,调用前面的过程 30 次,每次调用时向该过程传递用户输入的概率值并在过程返回后显示零标志值。

参考

https://www.zhihu.com/question/31634405/answer/2581244793
https://zhuanlan.zhihu.com/p/22976065/

  • 本文标题:80386汇编-条件处理
  • 本文作者:9unk
  • 创建时间:2022-12-02 23:53:00
  • 本文链接:https://9unkk.github.io/2022/12/02/80386-hui-bian-tiao-jian-chu-li/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!