《80X86汇编语言程序设计教程》五 简单运用程序设计

时间:2022-08-28 01:25:47

1、  8086有5种串操作指令,每种有两条,一条适用于字节单元串,一种适用于字单元串。DS:SI指向源串,ES:DI指向目的串。串操作自动修改指针,方向(SI与DI指针是增加还是减少)由DF控制,复位为递增方式,而置位为递减方式。某种意义上来说,这里的字符串指的是串,是一段连续的数据单元,而不限定于字符串。

  1)  字符串装载指令

    lodsb          ;装入字节(相当于 mov  al,[si]; inc  si或dec  si)

    lodsw          ;装入字(相当于 mov  ax,[si]; add  si,2或sub  si,2)

    把字符串中的一个字符装入累加器,并且根据DF位移动指针。

    可使用指令“lods  oprd”取代上面两条指令,其中oprd为定义的串标号,在使用它时,须先设置SI(如mov  si,offset string)---即lods不影响si的值(注意,以下串处理基本需要自行设置ES:DI与DS:SI),再进行装载(lods  string),如果串被定义为字节(db),则解释器采用lodsb指令,如果串被定义为字类型(dw),则采用lodsw(使用DS:SI)。

  2)  字符串存储指令

    stosb           ;存储字节(相当于 mov  es:[di],al; inc  di或dec  di)

    stows          ;存储字(相当于 mov  es,[di],ax; add  di,2或sub  di,2)

    把累加器的值存入到字符串,并移动指针,规则同上。不影响标志位。同样,可用指令“stos  oprd”取代两条指令,其规则也同上(使用ES:DI)。

  3)  字符串传送指令

    movsb        ;字节传送

    movsw       ;字传送

    将源串中的串传送到目的串,移动指针,不影响标志位。该指令与字符串比较指令(cmpsb与cmpsw)属于特殊情况的指令,因为它们的两个操作数均在存储器中。同样,可用“movs oprd1,oprd2”取代上面两条指令,两个操作数类型必须一样,规则同上(使用ES:DI与DS:SI)。

  4)  字符串扫描指令

    scasb          ;串字节扫描

    scasw                   ;串字扫描

    将累加器al或者ax中的内容与目的串DI指向的一个字节或者字的数据进行CMP,并移动指针。同样,可用“scas oprd”取代上面两条指令,规则同上(使用ES:DI)。

  5)  字符串比较指令

    cmpsb        ;串字节比较

    cmpsw       ;串字比较

    将源串SI指向的单元与目的串DI指向的单元进行CMP,移动指针。同样,用“cmps  oprd1,oprd2”取代如上两条指令,规则同上(使用ES:DI与DS:SI)。

 

 

2、  重复前缀

  重复前缀加在串操作指令之前,达到重复执行其后串操作指令的效果。

  1)  重复前缀REP

    每次重复前先判断CX的值,如果为0就结束重复,否则CX的值减1,继续重复。注意:它类似于loop指令,区别是loop先将CX的值减1,再判断是否为0,虽然如此,但它们的循环次数是一样的,只不过loop是先循环再判断(do while)而REP是先判断再循环(while)。同样的,CX减1操作不影响标志位。REP经常用在movs与stos之前,使用前需先装入cx。

  2)  重复前缀REPZ/REPE

    这两个是同一个指令的两个助记符,重复机制同上,只不过重复结束条件CX = 0或串操作指令使零标志ZF为0(注意:串比较之前ZF标志位不影响入口)。不影响标志位。用于movs和stos之前和REP指令效果一样(须置ZF位),而通常是用于cmps,实现在相同时继续比较。

  3)  重复前缀REPNZ/REPNE

    同REPZ/REPE,不同的是重复结束条件为CX = 0或串操作指令使标志ZF = 1(注意点同上)。不影响标志位。它主要用在scas之前,表示不等时继续扫描,直到搜索到字符或者串结束。

  4)  说明

    重复前缀执行执行时可能被中断,它识别中断,但是,如果它前面还有其它前缀(段超越前缀或锁前缀)的话,中断返回时其它的前缀就不再有效,因为CPU在中断时只能“记住”一个前缀,即字符串操作指令前的重复前缀。如果必须使用多个前缀,可以开关中断。

 

 

3、  串处理的一些用例测试

 

  1 dseg    segment
2 string0 db "Hello 80X86!",0,0ah,0dh,'$'
3 string1 db "Hello 80X86!",0
4 string2   db "Hello 80x86!",0
5 string3 db "0x8",0
6 string4   db "0X8",0
7 re_e   db "string1 = string2",0ah,0dh,'$'
8 re_l   db "string1 < string2",0ah,0dh,'$'
9 re_g   db "string1 > string2",0ah,0dh,'$'
10 n_y db "Find string",0ah,0dh,'$'
11 n_n db "Not find string",0ah,0dh,'$'
12 dseg ends
13
14 fceg segment
15 ;子过程名: STRLWR
16 ;功 能: 把字符串中的大写字母转换为小写字母(串以0结尾)
17 ;入口参数: DS:SI = 字符串首地址的段值:偏移
18 ;出口参数: 无
19 ; (1)把串中的大写字母转换为小写字母,不是字母的字符不变
20 STRLWR proc far
21 push   si
22 push   ax
23 cld ;清DF,使为递增方式
24 jmp short STRLWR2
25 STRLWR1:
26 sub al,'A'
27 cmp al,'Z'-'A' ;判断字符是否为大写字母
28 ja STRLWR2 ;不是则转
29 add al,'a' ;否则转小写
30 mov [si-1],al ;经过下面的loasb指针已经后移
31 STRLWR2:
32 lodsb ;载入串到al,并移动指针
33 and al,al ;如果为0,则到达串尾
34 jnz STRLWR1
35 pop ax
36 pop si
37 ret
38 STRLWR endp
39
40
41 ;子过程名: STRCMP
42 ;功 能: 比较两字符串的大小
43 ;入口参数: DS:SI = 字符串1首地址的段值:偏移
44 ; ES:DI = 字符串2首地址的段值:偏移
45 ;出口参数: AX = 0表示两字符串相等;AX > 0表示第一个字符串较大;AX < 0表示地址一个字符串较小
46 ; (1)字符串以0结尾
47 STRCMP proc far
48 cld
49 push   di
50 xor ax,ax ;先测试一个串长度
51 mov cx,0ffffh
52 repnz  scasb
53 not cx ;cx为串长度
54 pop di
55 repz   cmpsb ;两个串比较(包括结束符)
56 mov al,[si-1]
57 mov bl,es:[di - 1]
58 xor bh,bh
59 sub ax,bx
60 ret
61 STRCMP endp
62
63
64 ;子过程名: STRSTR
65 ;功 能: 判断串2是否为串1的子串
66 ;入口参数: 堆栈传递
67 ; 第一个参数:串1偏移
68 ; 第二个参数:串1段值
69 ; 第三个参数:串2偏移
70 ; 第四个参数:串2段值
71 ;出口参数: DX : AX 返回指向字符串2在字符串1中首次出现的指针
72 ; (1)影响寄存器:DX,AX
73 ; (2)设串以0结束
74 STRSTR proc far
75 push    bp
76 mov bp,sp
77 push    ds
78 push    es
79 push    bx
80 push   cx
81 push   si
82 push   di
83
84 les bx,[bp + 10] ;串2偏移送bx,串2段值送es
85 cmp byte ptr es:[bx],0 ;判串2是否为空串
86 jnz STRSTR1 ;非空串转,否则返回str1指针
87 mov dx,[bp + 8] ;串1段值
88 mov ax,[bp + 6] ;串1指针
89 jmp STRSTR6
90
91 STRSTR1:
92 cld
93 les di,[bp + 6] ;串1偏移送di,串1段值送es
94 push   es
95 mov bx,di ;串1偏移送bx
96 xor ax,ax
97 mov cx,0ffffh
98 repnz scasb ;侧串1长度(包含结束符)
99 not cx
100 mov dx,cx ;串1长度放入dx
101
102 les di,[bp + 10] ;串2偏移送bx,串2段值送es
103 push   es
104 mov bp,di ;串2偏移送bp
105 xor ax,ax
106 mov cx,0ffffh
107 repnz scasb
108 not cx
109 dec cx ;cx为串2长度,不包含结束符
110 pop ds ;串2段值送ds,此时ds:bp指向串2
111 pop es ;串1段值送es,此时es:bx指向串1
112 STRSTR2:
113 mov si,bp ;ds:si指向串2
114 lodsb ;取串2第一个字符
115 mov di,bx
116 xchg   cx,dx ;使cx = 串1长度,dx = 串2长度
117 repnz scasb ;在串1中搜索串2第一个字符
118 mov bx,di
119 jnz STRSTR3 ;找到?
120 cmp cx,dx ;找到,串1剩下字符数比串2长?
121 jnb STRSTR4 ;是,转
122 STRSTR3: ;找不到处理:DX:AX = 0000:0000
123 xor ax,ax
124 xor dx,dx
125 jmp STRSTR6
126 STRSTR4:
127 xchg cx,dx ;cx = 串2长度,dx = 串1长度
128 mov ax,cx
129 dec cx
130 repz cmpsb ;判断串1中是否有串2后的其他字符
131 mov cx,ax
132 jnz STRSTR2 ;没有,继续找
133 STRSTR5:
134 mov ax,bx ;找到,准备返回值
135 dec ax
136 mov dx,es
137 STRSTR6:
138
139 pop di
140 pop si
141 pop cx
142 pop bx
143 pop es
144 pop ds
145 pop bp
146 ret 8
147 STRSTR endp
148
149 fceg ends
150
151 cseg segment
152 assume cs:cseg,ds:dseg
153 start:
154 mov ax,dseg
155 mov ds,ax
156 ;-------------------------------------
157 ;测试STRLWR
158 mov si,offset string0
159 call STRLWR
160
161 mov dx,offset string0
162 mov ah,09h
163 int 21h
164
165 ;-------------------------------------
166 ;测试STRCMP
167 mov ax,dseg
168 mov ds,ax
169 mov es,ax
170 mov si,offset string1
171 mov di,offset string2
172 call STRCMP
173 cmp ax,0
174 je r_z
175 jg r_g
176 mov dx,offset re_l
177 jmp r
178 r_z:
179 mov dx,offset re_e
180 jmp r
181 r_g:
182 mov dx,offset re_g
183 jmp r
184 r:
185 mov ah,09h
186 int 21h
187 ;-------------------------------------
188 ;测试STRSTR
189 mov bx,dseg ;串2段值
190 push   bx
191 mov ax,offset string4
192 push   ax ;串2偏移
193 push   bx ;串1段值
194 mov ax,offset string2
195 push   ax ;串1偏移
196 call   STRSTR
197
198 or ax,dx
199 jz n
200 mov dx,offset n_y
201 jmp show
202 n:
203 mov dx,offset n_n
204 show:
205 mov ah,09h
206 int 21h
207
208 mov ah,0ah
209 int 21h
210
211 mov ah,4ch
212 int 21h
213 cseg ends
214 end start

 

输出结果:

 

 《80X86汇编语言程序设计教程》五 简单运用程序设计

五(3)串处理用例测试

 

 

4、  十进制数计算是基于8421BCD码,分两种:压缩(组合)BCD码和非压缩(未组合)BCD码。前者一个字节表示2个BCD码,而后者1个字节的低4位表示一个BCD码,而高字节无定义。数字字ASCII码是一种非压缩的BCD码。在对BCD码进行算术运算时有如下调整指令:

  1)  压缩BCD码加法调整指令:DAA

    对AL(由2个压缩BCD码相加所得)进行调整,产生一个压缩BCD码。方法如下:

      AL低4位 >= A 或 AF = 1,则AL = (AL) + 6,且AF置1

      高4位 >= A 或 CF = 1,则AL = (AL) + 60H,且CF置1

    影响标志位AF、CF、PF、SF和ZF,不影响OF

  2)  压缩BCD码减法调整指令:DAS

    对AL(由2个压缩BCD码相减所得)进行调整,产生一个压缩BCD码。方法如下:

      AL低4位 >= A 或 AF = 1,则AL = (AL) - 6,且AF置1

      AL高4位 >= A 或 CF = 1,则AL = (AL) - 60H,且CF置1

    影响标志位AF、CF、PF、SF和ZF,不影响OF

  3)  非压缩BCD码加法调整指令:AAA

    对AL(由2个非压缩BCD码相加所得)进行调整,产生一个非压缩BCD码。方法如下:

      AL低4位在0~9之间,且AF = 0,则第三条

      AL低4位 >= A 或 AF = 1,则AL = (AL) + 6,AH = (AH) + 1,且AF置1

      清除AL高4位

      AF位送CF位

    影响标志位AF、CF。

  4)  非压缩BCD码减法调整指令:DAS

    对AL(由2个非压缩BCD码相减所得)进行调整,产生一个非压缩BCD码。方法如下:

      AL低4位在0~9之间,且AF = 0,则第三条

      AL低4位 >= A 或 AF = 1,则AL = (AL) - 6,AH = (AH) - 1,且AF置1

      清除AL高4位

      AF位送CF位

      影响标志位AF、CF。

  5) 非压缩BCD码乘法调整指令:AAM

    对AL(由2个非压缩BCD码相乘所得)进行调整,产生一个非压缩BCD码。方法:把AL中的值除以10,商放在AH中,余数放在AL中。影响SF、ZF和PF。

  6)  非压缩BCD码除法调整指令:AAD

    把AH(高位十进制数)及AL中的两位非压缩BCD码,调整为二进制数,放在AL,用在除法指令之前。调整方法:AL = AH * 10 + (AL);AH = 0。影响SF、ZF和PF

 

 

5、  BCD码调整的一些用例测试

 

  1 dseg    segment
2 feedline db 0ah,0dh,24h
3 expr label byte
4 num1 db "1445345123499",0
5 adds db '+ '
6 num2 db   "22442534",0
7 equs db ' = '
8 result db 30 dup('$')
9 dseg ends
10
11 fceg segment
12 ;子过程名: HTOASC
13 ;功 能: 把1位十六进制数转换为对应十六进制数码的ASCII码
14 ;入口参数: AL低4位 = 要转换的十六进制数
15 ;出口参数: AL含对应ASCII码
16 ; (1)转换后A~F为大写字母
17 HTOASC proc far
18 and al,0fh
19 add al,90h
20 daa
21 adc al,40h
22 daa
23 ret
24 HTOASC endp
25
26 ;子过程名: DADD
27 ;功 能: 将2个用十进制数码的ASCII码串表示的数进行相加,得到结果为十进制数码的ASCII码串
28 ;入口参数: DS:DI = 加数num1,设以0结束
29 ; DS:SI = 加数num2,设以0结束
30 ; DS:BX = 结果result,假设缓冲区足够长
31 ;出口参数: DS:BX保存了相加结果,串以'$'结束
32 DADD proc far
33 push   es
34 mov ax,ds
35 mov es,ax
36
37 push   di
38 push   si
39 ;求得num1的长度(包括结束符)
40 mov cx,0ffffh
41 xor ax,ax
42 repnz scasb ;使用到es:di
43 not cx
44 mov dx,cx ;使用dx暂存num1长度
45
46 ;求得num2的长度(包括结束符)
47 mov di,si
48 mov cx,0ffffh
49 repnz scasb
50 not cx
51 pop si
52 pop di
53
54 ;调整di,si指针使其指向串尾(十进制数的个位)
55 add di,dx
56 sub di,2
57 add si,cx
58 sub si,2
59
60 ;调整bx指针使其指向串尾(十进制数个数)
61 ;将较短的数值复制到结果缓存中
62 cmp cx,dx ;选择两个操作数较长的一个长度
63 ja DADD1 ;长的存入cx,短的存入dx
64 xchg cx,dx ;num1较长,要移动的是num2
65 jmp DADD2
66 DADD1: ;num2较长
67 xchg si,di ;要移动的是num1
68 DADD2: ;此时,si存入的是短数字的尾指针,di存入的是长数字尾指针
69 push cx
70 DADD3: ;将结果全部赋值0
71 mov byte ptr[bx],0
72 inc bx
73 loop DADD3
74 mov byte ptr[bx + 1],'$' ;写串结束符
75 pop cx
76 dec cx
77 dec bx
78
79 push   bx
80 push   cx
81 mov cx,dx
82 dec cx
83 DADD4: ;开始复制数据
84 mov al,byte ptr[si]
85 mov byte ptr[bx],al
86 dec bx
87 dec si
88 loop   DADD4
89 pop cx
90 pop bx
91 ;循环求和
92 xor ax,ax
93 DADD5:
94 add al,[di] ;ASCII码为非压缩BCD码
95 aaa ;非压缩BCD码调整指令
96 add al,[bx]
97 aaa
98 add al,30h ;转ASCII码
99 mov byte ptr [bx],al ;写值
100 mov al,ah
101 xor ah,ah
102 dec di
103 dec bx
104 loop DADD5
105
106 add al,30h
107 mov byte ptr [bx],al ;写值
108
109 pop es
110 ret
111 DADD endp
112
113 fceg ends
114
115 cseg segment
116 assume cs:cseg,ds:dseg
117 start:
118 mov ax,dseg
119 mov ds,ax
120 ;-------------------------------------
121 ;测试STRSTR
122 mov al,03h ;测试3h
123 call   HTOASC
124 mov dl,al
125 mov ah,02h
126 int 21h
127 mov dx,offset feedline
128 mov ah,09h
129 int 21h
130
131 mov al,0eh ;测试eh
132 call   HTOASC
133 mov dl,al
134 mov ah,02h
135 int 21h
136 mov dx,offset feedline
137 mov ah,09h
138 int 21h
139 ;-------------------------------------
140 ;测试STRSTR
141 int 3
142 mov di,offset num1
143 mov si,offset num2
144 mov bx,offset result
145 call   DADD
146 mov dx,offset expr ;算术表达式输出
147 mov ah,09h
148 int 21h
149
150 mov ah,0ah
151 int 21h
152
153 mov ah,4ch
154 int 21h
155 cseg ends
156 end start

 

输出结果:

 

 《80X86汇编语言程序设计教程》五 简单运用程序设计

五(4)BCD码调整用例测试

 

 

6、  DOS程序段前缀和特殊情况处理程序

  其实以下两部分内容都是具体和DOS挂钩的,现在学它们没什么用处,简单了解下就好。所谓段前缀(PSP)是DOS加载一个外部命令或运用程序时,在程序段之前设置的一个具有256字节的信息区:

 

偏移

内容及意义

偏移

内容及意义

00H

程序终止退出命令INT 21H

16H

保留

02H

可用内存空间高端段值

2CH

环境块段值

04H

保留

2EH

保留

05H

DOS系统功能远调用指令

5CH

格式化的未打开文件控制块1

0AH

程序结束处理中断向量原内容

6CH

格式化的未打开文件控制块2

0EH

CTRL+C键处理中断向量原内容

80H

命令行参数长度(字节数)

12H

严重错误处理中断向量原内容

81H

命令行参数

 

  DOS在把控制权转给运用程序前,DS与ES均指向PSP,这给运用程序访问这张信息表提供了途径。下面是几个运用的简单原理:

  1)  终止程序另外一条途径(00H)

    PSP与程序代码段不处于同一个段,所以不能直接调用,但是可以使用远程过程返回指令RET,将IP指向PSP的00H处执行,来实现终止程序,功能类似INT 4CH。大体过程:

 

1 MAIN  proc far
2 START: ;开始标号
3   push   ds ;PSP段值
4   xor ax,ax
5   push ax ;00H偏移
6;其它内容
7   ret ;远过程调用返回,跳到PSP的00H处执行INT 21H
8 MAIN endp
9 end START

 

  2) 取得命令行参数(80H和81H)

    思路:程序开始出将80H号字节内容读出即为参数个数,设置到CX,置ES为数据段段值(其中包含存储命令行的缓冲区,最长127个字节),将DI设置为buffer偏移,DS已经指向PSP,不用管(或者使用DOS的62H号功能调用,获取PSP段地址到bx,然后再移动到DS),将SI设置为81H,使用REP MOVSB即可取出命令行参数到缓冲区。

  3)  CTRL+C与CTRL+BREAK键处理

    a)修改DOS中断服务23H:按下它们时,DOS将会产生INT 23H号中断,如果要过滤它们,一个思路是修改中断向量表23H,使指向新的中断服务程序,而我们只要在编写这个中断服务程序时直接返回即可,但是,DOS界面上还是会有按下它们相应的字符产生。

    b)修改CTRL+BREAK软中断(1BH):键盘中断处理程序(9H号)发现它们时,将产生1BH号中断,并在约定内存单元设置标志位(DOS的23H号中断基于该标志位),如果我们提供新的1BH号中断的服务程序,使其不写入,那么DOS将无法发现它们,也不会发成23H号中断。

    c)修改键盘管理程序(16H):修改键盘管理程序可以直接屏蔽掉它们,使过滤它们按键的存在,而上述两点根本就无法再感知。

 

 

7、  TSR程序设计举例

  TSR意为结束并驻留,使用DOS调用的31H功能号。TSR程序装入内存并初次运行后,大部分驻留内存,在某条件下又被重新激活运行。通常驻留程序分为两个部分,一个是初始化部分,这部分的写法比较固定;另一个是驻留内存部分,是要被重新激活以后去执行的部分,取决于具体的工作内容。下面是几个简单的驻留程序,它捕获热键CTRL+F8,每次按下热键就在屏幕指定位置显示固定信息。由于是在同一个位置显示信息,所以要在显示以后清除输入清屏指令“cls”,再按CTRL+F8才能看到效果。代码如下:

 

 1 BUFF_HEAD = 1AH                        ;键盘缓存区头指针保存单元偏移
2 BUFF_TAIL = 1CH ;键盘缓存区尾指针保存单元偏移
3 BUFF_START = 1EH   ;键盘缓存区开始偏移
4 BUFF_END = 3EH ;键盘缓存区结束偏移
5 CTRL_F8 = 6500H ;激活键扫描码
6 ROW = 3 ;行号
7 COLUMN = 0 ;列号
8 PAGEN = 0 ;显示页号
9
10 cseg segment
11 assume cs:cseg,ds:cseg
12 ;数据区
13 OLD9H dd ? ;原9号中断向量保存单元
14 MESS db "hello!" ;显示信息
15 MESSLEN equ $-MESS
16 ;新键盘中断服务程序
17 NEW9H:
18 pushf
19 call    cs:OLD9H ;调用原中断处理程序
20
21 sti ;开中断
22 push   ds ;保护现场
23 push    ax
24 push    bx
25
26 mov ax,40h
27 mov ds,ax
28 mov bx,ds:[BUFF_HEAD]
29 cmp bx,ds:[BUFF_TAIL] ;键盘缓冲区为空?
30 jz IOVER ;是则中断返回
31 mov ax,ds:[bx] ;否则取所按按键
32 cmp ax,CTRL_F8 ;是否为激活键?
33 je YES ;是则转
34 IOVER:
35 pop bx ;中断返回处理
36 pop ax ;恢复现场
37 pop ds
38 IRET
39 YES: ;按下激活键处理程序
40 inc bx
41 inc bx ;调整键盘缓冲区头指针(取走按键)
42 cmp bx,BUFF_END ;指针到缓冲区末尾?
43 jnz YES1 ;否,转
44 mov bx,BUFF_START ;是,指向头
45 YES1:
46 mov ds:[BUFF_HEAD],bx ;保存
47 push   cx ;再保护部分现场
48 push   dx
49 push   bp
50 push   es
51 mov ax,cs
52 mov es,ax ;数据的同代码段
53 mov bp,offset MESS
54 mov cx,MESSLEN
55 mov dh,ROW
56 mov dl,COLUMN
57 mov bh,PAGEN
58 mov bl,07h
59 mov al,0 ;显示后不移动光标,串中不包含属性
60 mov ah,13h
61 int 10h
62 pop es ;恢复部分现场
63 pop bp
64 pop dx
65 pop cx
66 jmp IOVER ;中断返回
67 ;---------------------------------------------------
68 ;初始化代码
69 INIT:
70 push   cs
71 pop ds
72 mov ax,3509h ;35H号功能调用,取09号中断向量
73 int 21h
74 mov word ptr OLD9H,bx
75 mov word ptr OLD9H + 2,es ;保存
76
77 mov dx,offset NEW9H ;设置新的9号中断向量
78 mov ax,2509h
79 int 21h
80
81 ;计算驻留节数
82 mov dx,offset INIT + 15 ;加上15是考虑字节数不是16倍的情况
83 mov cl,4
84 shr dx,cl ;转换为节数
85 add dx,10h ;加上PSP的节数
86 mov al,0
87 mov ah,31h ;驻留并退出
88 int 21h
89 cseg ends
90 end INIT