一. 实验准备
01.实验名称:
自由录入显示系统(Freeentry and display system)
02.实验目的:
1) 完成教学任务
2) 归纳应用前阶段所学的汇编知识,加深对汇编语言的了解
3) 积累项目经验
03.实验环境:
01) 电脑型号:惠普 HP Pavilion g4 Notebook PC 笔记本电脑
02) 操作系统:MicrosoftWindows 7 旗舰版(6.1.7601 Service Pack 1)
03) 处理器: 英特尔 Core i3 M390 @2.67GHz 双核笔记本处理器
04) 主板: 惠普1667(惠普HM55芯片组)
05) 内存: 4GB(尔必达 DDR31333MHz / 金士顿 DDR3 1333MHz)
06) 主硬盘: 希捷 ST9640320AS(640GB/ 5400转/分)
07) 显卡: ATI Radeon HD 6470M (1GB /惠普)
08) 编译调试软件:EMU 8086V4.08
04.实验问题:
01) 如何在屏幕显示字符
02) 如何使用中断调用(系统时间及键盘相关操作)
03) 如何响应键盘输入
04) 如何使文字及屏幕颜色改变
05) 如何返回重设全部设置
05.参考文献:
01) 《汇编语言》(第2版)王爽 著 (清华大学出版社)
02) 《80X86汇编语言程序设计》廖开际 编(华南理工大学出版社)
二. 实验分析
01.程序功能流程图
02.程序结构 -- 各子程序间的关系图
03.程序代码设计说明
001) main过程段设计分析
【程序功能】完成整体的程序结构的设计
【逐步分析】
该过程段代码如下所示,该过程段中还包含了七个标号,其中,start标号是程序的入口,程序从这里开始执行,先将堆栈段定好位,将其定位至自己所定义的堆栈段中,以防发生内存溢出现象,然后开始整个程序的第一步,firststep,显示欢迎选单,让用户了解该系统是用来做什么用的,此时调用的controlshow子程序,入口是bl,当bl = 0的时候,显示欢迎菜单,整个程序的第二步,功能选择菜单,入口为ah = 1的ahfunctions子程序中,可以让用户选择三个按钮,执行相对应的功能,待用户选择完以后,继续下一步的功能,thirdstep,由上次的功能选单,确定了这次的功能入口,还是调用ahfunctions子程序,紧接着的是最后一步提供给用户的功能,入口是ah = 3, 在这次功能执行完以后,程序会根据上一功能返回的字母,执行下一步的操作是什么,直接退出系统或者是再次进入系统,这样一来,整个程序的总体思路就清晰了。
main proc near ; main part of program
start:
mov ax, stack ; 首先设置堆栈地址
mov ss, ax
mov sp, 256 ; 使用temparea1作为堆栈
firststep:
mov bl, 0 ; 显示欢迎菜单
call controlshow
secondstep:
mov ah, 1
call ahfunctions ; 第一次调用functions进入第一个功能选单
thirdstep:
cmp ah, '3' ; 这是第一阶段的跳出功能
je mainend
call ahfunctions ; 由上一次功能选单,确定本次入口,ah为参数(已在第一次调用中确认)
; 文章选择阶段
forthstep:
mov ah, 3
call ahfunctions ; 进入第三阶段,功能选单阶段
fifthstep: ;最后一步,紧接第三阶段,有两个出口,一是r,二是q
cmp ah, 'r'
je firststep
cmp ah, 'q'
je mainend
mainend:
mov ax, 4c00h
int 21h
main endp
002)showcontrol及showcows子程序设计分析
【程序功能】在屏幕上相应的位置显示相应的字符串内容
【逐步分析】
showcontrol子程序主要配合showcow子程序使用,showcontrol子程序负责接收一个入口参数:bl--在data中alladress中的地址,接收完以后,再根据该地址,知道需要显示的位置,设定showcows的入口参数bh—需要显示地址的相对应在屏幕上需要显示的行数,这样一来,就能在正确的位置显示正确的内容了。
screenshowarticle proc near
controlshow:
push bx
AddressShow:
as0:
cmp bl, 0 ; [0]标题的地址
jne as12 ; [8]第一个选单的地址
mov bh, 0
jmp showcow
as12:
cmp bl, 12 ; [12]默认显示的英文文章地址
jne as16
mov bh, 13
jmp showcow
as16: ; 以下三段内容与上面类型相类似,此处省略显示
as20:
as24:
ShowEnd:
pop bx
ret
【逐步分析】
在showcows子程序中,需要将ds:[si]复制到es:[di]中,编写该程序时主要需要思考的问题是:因为要程序调入的是行的地址bh,所以还需要计算出相对应的内存地址,而计算的方法就是将行的地址乘以屏幕一行所显示的字符数80,其中显示一个字符又包含了两个字节,高字节存放字符的ASCII码,低字节存放字符的属性,所以需要用乘法行数乘以160,得出的高位放在dx中,低位放在ax中,再对结果进行处理即可。另外,此处还使用了一个cmp与jmp的结合功能来判断是否继续执行循环,以字符“\”为循环结束标志,当在ds:[si]中发现该字符时即停止循环。
showcow:
; push功能省略显示 ……
mov ax, data ; 设置ds:[si]为复制源
mov ds, ax
mov cl, bl ; 借助cx将bl移至si作为偏移地址
mov ch, 0
mov si, cx
mov si, alladdress[si] ; 在地址表中找到正确的地址
mov ax, 0B800h ; 设置es:[di]为复制目标
mov es, ax
mov bl, bh ; 调用的参数是bh,用bh行数计算出正确的显示缓冲区地址
mov bh, 0
mov ax, bx
mov bx, 160
mul bx
mov bx, ax ; bx存乘法结果的低位
mov di, dx ; di存放高位(80 *bh) * 2; 将ds:[si]的字符复制到es:[di]显示缓冲区处
mov ax, es ; 将高位的地址结果存放至es中,因为es是段地址
add ax, di
mov es, ax
showuntil:
mov al, ds:[si]
mov es:[bx], al
inc si
inc bx ; 显示区域需要递增2
inc bx
mov cl, ds:[si]
cmp cl, '\' ; 当它不为'\'时继续循环
jne showuntil
; pop功能省略显示 ……
jmp ShowEnd ; 因为调用的时候是jmp进来的,所以现在可以直接jmp到调用结束处.
screenshowarticleendp
003)ahfunctions子程序设计分析
【程序功能】为三个主要程序运行阶段提供入口
【逐步分析】
这里使用ah为入口参数,选择ah = 1时,执行第一阶段的功能,这里将第一阶段的功能定义为:在程序功能流程图中的提供给用户的第一次功能选择。选择ah = 2时,执行第二阶段的功能,这里将第二阶段的功能定义为让用户输入文章。选择ah= 3时,执行第三阶段的功能,这里将第三阶段的功能定义为:在程序功能流程图中提供给用户的第二次功能选择。
ahfunctionsall proc near
ahfunctions:
; push功能省略显示 ……
ahfunction1: ; 一阶段功能
cmp ah, 1
jne ahfunction2
call ahfunction1start
jmp ahfunctionsend ; 以下两段内容与上面类型相类似,此处省略显示
ahfunction2:
ahfunction3:
ahfunctionsend:
; pop功能省略显示 ……
ret
ahfunctionsall endp
004)ahfunction1start子程序设计分析
【程序功能】1.从键盘缓冲区中读取一个字符以为其它功能提供入口
2.当入口为’2’的时候,直接执行对应的功能。
【逐步分析】
程序执行的第一块,显示功能菜单,以提供信息给用户,程序第二块,al101,将ah赋值2,返回主函数时为ahfunctions提供接口,程序第三块,al102,可以直接在该程序实现的功能,显示默认的文章,然后调用回到主函数调用ahfunctions子程序,程序第四块,al103,main程序段根据ah提供的接口,判断是否结束程序。
ahfunction1startproc proc near
ahfunction1start:
push bx
push es
mov bx, 0b800h
mov es, bx
mov bh, '\'
mov es:[160*9+47*2], bh
mov ah, 0
int 16h
al101:
cmp al, '1' ; 选择1号功能,则直接调用ahfuncion2
jne al102
mov ah, 2 ; 设置入口参数ah为2,si为中断编号16
mov si, 16 ; 返回该函数,在主函数中作为新地址入口
jmp ahfunction1end
al102:
cmp al, '2'
jne al103
mov bl, 12
call controlshow ; 显示文章
call dae1todae9 ; ds:dae1 -> ds:dae9
mov si, 16
mov ah, 3
jmp ahfunction1end ; 返回该函数
al103:
cmp al, '3'
jne alnone
mov ah, al
jmp ahfunction1end ; 返回该函数
alnone:
jmp ahfunction1start ; 如果都不是这些选项,再次跳到循环处,获取正确的选择number
ahfunction1end:
mov es:[160*11+17*2], al
pop es
pop bx
ret ; 此时调用ret,回到ahfunctionall
005)ahfunction2start子程序设计分析
【程序功能】1.允许用户输入一些字符,程序自动将其全部存放于数据段指定空间。
2.允许用户输入一 些字符,程序自动将其全部显示于屏幕上。
【逐步分析】
实际上该子程序就相当于封装了一个堆栈,只是在堆栈的基础上稍作了一下修改,程序主要思路是先判断用户的输入,如果用户输入的字符在字符范围内,那么就将其显示于屏幕上,并且将其放入堆栈中,放入栈中后,在屏幕中显示出来,再将其从进栈的入口返回,然后循环输入程序,直到遇到结束符“\”,当遇上结束符时,子程序会跳出堆栈的输入循环,进入子程序的跳出准备,在子程序的跳出之前,会处理堆栈中的数据,首先,程序会将栈中的内容复制到data段中,为第三阶段的功能做准备。
ahfunction2startproc proc near
ahfunction2start:
getstr:
; push功能省略显示 ……
getstrs:
mov ah, 0 ; int16的读取键盘缓冲区的功能编号为0
int 16h ; 使用int16中断例程
cmp al, '\' ; 先判断它是否结束符,只以结束符终止该子程序
je charsfinish ; 如果是的话,就直接跳到结束处理
cmp al, 20h
jb nochars ; ASCII码小于20h, 说明不是字符,跳到nochar标号中
mov ah, 0
call charstack ; 字符入栈的调用参数是(ah)=0
mov ah, 2
call charstack ; 显示栈中的字符,入栈后显示字符,调用参数(ah)=2
jmp getstrs ; 循环进入该子程序输入,直至按入"\"退出
nochars:
cmp ah, 0eh ;退格键的扫描码
je backspace
jmp getstrs ; 如果不是退格,则是不支持字符,直接重新输入
backspace:
mov ah, 1
call charstack ; 字符出栈
charsfinish:
mov si, offset savechar ; savechar -> si
mov ax, data
mov ds, ax ; data -> ds, 目的是让di可以指向dae9
mov di, offset dae9 ; dae9 -> di
mov es, ax ; data -> es
mov ax, code
mov ds, ax ; cs -> ds
mov cx, 500
cld ;设置为正方向 fromcs:[savechar] to data:[dae9]
rep movsb ; 开始复制 fromds:[si] to es:[di]
; pop功能省略显示 ……
ret
【逐步分析】
此处为堆栈实现的入口,当ah = 0时,将字符放入栈,当ah = 1时,将字符出栈,当ah = 3时,将显示字符。
charstack:
jmp charstart ; 直接跳到charstart标号处,为了不执行非代码段
; table标号与top标号中的数据
table dw charpush, charpop, charshow
top dw 0
row dw 0
line dw 13 ; 默认为在第13行显示,21行前结束
savechar db 500 dup('\')
charstart: ; 程序开始前,先临时保存需要用到的寄存器
; push功能省略显示 ……
; 比较ah参数是否0~2正确范围内,若不是,则正常跳出子程序
cmp ah, 2
ja sret
mov bl, ah
mov bh, 0
add bx, bx
push ax
mov ax, code
mov ds, ax
pop ax
jmp wordptr ds:table[bx] ; 获取在该子程序需要执行程序的入口地址
charpush:
mov bx, top
cmp bx, 499 ; 控制字符个数,不能超过500个
je sret
push cx
mov cx, code
mov ds, cx
pop cx
mov ds:savechar[bx], al ; 将字符存放至savechar中
inc top
inc row
jmp sret
charpop:
cmp top, 0 ; 考虑无元素的情况
je getstrs
dec top ; top的位置是没有元素的,删除即先将位置减一
mov bx, top
push cx
mov cx, code
mov ds, cx
pop cx
mov byte ptr ds:savechar[bx], '\'
cmp row, 0 ; 考虑元素在首位的情况
jne norpop
mov word ptr ds:row, 80 ; 处理列(在dec的位置再减1) 0~79
dec line ; 处理行
norpop:
dec row
shownochar:
inc top
call plusrowend
dec top
jmp sret
charshow:
cmp row, 80 ; 一行只允许存放80个字符
jne plusrowend
plusrow:
inc line
mov word ptr row, 0
plusrowend:
mov bx, 0b800h
mov es, bx
mov bx, row ; bx存放需要显示的列
add bx, bx
sub bx, 2
mov ax, line ; ax存放需要显示的行
push cx
mov cx, 160
mul cx ; 将ax乘以160得出行列单元
add bx, ax ; 将低地址的相加,得出列单元
mov ax, es
add dx, ax ; 将高地址相加,得出行单元
mov es, dx
push di
mov cx, code
mov ds, cx
pop cx
mov di, top ; 显示指定字符
dec di
mov al, ds:savechar[di]
pop di
cmp al, '\'
jne showonechar ; 这里是pop的时候使用的功能
mov al, ' '
mov byte ptr es:[bx+2], al
ret
showonechar:
mov byte ptr es:[bx], al
jmp sret
sret:
; pop功能省略显示 ……
ret
ahfunction2startproc endp
005)ahfunction3start子程序设计分析
【程序功能】1.用户输入字符c或f能使文章段的字符颜色或者是屏幕颜色改变。
2.将程序返回开始执行的地方,将程序里面的多个部分的内存数据清空。
3.退出系统
【逐步分析】
在这一阶段,还是需要用户输入一个字符,即调用ah =0,int16中断功能,当输入c或f字符能使字符颜色改变,这里程序设计思路主要是:先将原来的字符显示默认属性保存到一个内存空间中,然后,再申请一个内存空间,用于存放新的显示属性,当触发了这个程序的时候,这个程序将会修改属性,不断地循环加1,再将该属性放到显示文章区域的相关位置,修改完成后直接跳回原来的输入中,由用户再次选择需要实现的功能,当选择了重启功能的时候,程序将会将data段里面存放用户输入文章的内存段,还有堆栈段里面的行列数,屏幕的文字及属性,全部返回到刚刚开始程序执行时的状态。当选择了q的时候,程序直接跳回main程序中执行程序结束的中断。
ahfunction3startproc proc near
ahfunction3start:
push bx
push es
mov bl, 16 ; 显示第三阶段的功能选单
call controlshow
jmp short savecol ; 存储字符及背景属性
saveco dw 0 ; 用作字符属性的还原
changecol dw 0 ; 用作字符属性的修改
savecol:
mov ax, 0b800h
mov es, ax
push es:[1] ;获得原系统字符及背景属性
pop saveco
mov ax, saveco
mov byte ptr changecol, al
al300:
mov ah, 0 ; 用户功能选择输入
int 16h
al301: ; 一号功能,改变文字颜色
cmp al, 'c'
jne al302
call changecharcolour ; 改变字符颜色
jmp al300 ;重新选择
al302: ; 二号功能,改变背景颜色
cmp al, 'f' ; 选择1号功能,则直接调用ahfuncion2
jne al303
call changescreencolour ; 改变屏幕颜色
jmp al300 ;重新选择
al303:
; 三号功能,重新启动系统
cmp al, 'r'
jne al304
mov bl, 24
call controlshow ; 清空屏幕(参数为24)
call resetall ; 还原屏幕显示属性
; 清空文章自由储存区
; 清空字符串内的储存区
mov ah, al ;ah为主函数进入ahfunctions子程序的接口
jmp short ahfunction3end
al304: ; 四号功能,退出系统
cmp al, 'q'
jne alnone3
mov ah, al ;ah为主函数进入ahfunctions子程序的接口
jmp ahfunction3end
alnone3:
jmp al300 ; 如果都不是这些选项,再次跳到循环处
; 获取正确的选择number
ahfunction3end:
pop es ; 此时ah可返回的是q或者r
pop bx
jmp fifthstep
; ret ; jmpfifthstep
resetall:
; push 功能省略显示 ……
; 1.还原屏幕显示属性
mov ax, 0B800H
mov es, ax
mov di, 1 ; es:[di]进入显示缓存区区域
mov ax, es:[1] ; 使al为显示属性
mov cx, 2000 ; 将一屏幕的属性清空
clearthewholescreen:
mov ptr byte es:[di], al
inc di
inc di
loop clearthewholescreen
; 2. 清空文章自由储存区
mov ax, data
mov ds, ax
mov di, 0 ; data:dae9[di]
; 3. 清空字符串内的储存区
mov ax, code
mov es, ax
mov si, 0 ; code:savechar[si]
mov cx, 500
clearthearticle:
mov byte ptrds:dae9[di], '\'
mov byte ptres:savechar[si], '\'
inc di
inc si
loop clearthearticle
; 将堆栈空间计算变量清零
mov ax, 0
mov es:top, ax
mov es:row, ax
mov ax, 13
mov ds:line, ax
; pop 功能省略显示 ……
ret
changecharcolour:
; push 功能省略显示 ……
mov ax, changecol
and al, 00000111b ; 提取出文字的属性
cmp al, 7
je reset0
jmp short plus1
reset0:
and changecol, 11111000b ; 将文字属性置零
jmp short addresschar
plus1:
inc changecol ; 改变文字属性
addresschar:
mov ax, 0B800h
mov es, ax
mov di, 1
mov bx, 0
mov ax, data
mov ds, ax
mov ax, changecol
loopcharbegin:
cmp byte ptr ds:dae9[bx], '\'
je loopcharend
mov byte ptr es:[13*160 + di], al
inc di
inc di
inc bx
jmp loopcharbegin
loopcharend:
; pop 功能省略显示 ……
ret
三. 实验过程
01. 逻辑结构问题
1) 现象:
在代码刚刚编写完成时,程序一运行,就无法响应,后来,在调试的时候,不知道从哪里下手,因为整个程序的结构很乱,总是在使用jmp,而call功能则使用得很少,这样的话,就导致破坏了子程序的封装性,让其它程序可以随时进来。
2) 问题:
逻辑结构设计不合理
3)解决方法:
为了免得在调试的时候再出现这样的错误,免得一出错误就要从程序开始的位置重新追踪,还是先将自己的整个程序的思路,结构弄清楚,于是,根据自己写出的程序,列出了各个程序间的大概的关系图,看看哪些地方可以作修改的,先将程序的整个结构改好,再将一个个的子功能完善。下图是在解决问题时,所做出的修改方法,几乎将每个大的子功能都使用call和ret来调用返回,避免程序结构混乱。
02. 程序中的屏幕显示问题
1) 现象:
显示异常,在运行第一部分的功能时,原来构思是显示在屏幕第一行中间的标题,它竟然移位了,显示在屏幕的偏右的位置了,导致全部的字符位都偏了。
2) 问题:
如何调正字符在屏幕显示的位置。
3)解决方法:
追踪程序,发现问题的根源了,下面是showcow子程序里面原来的一段代码,主要实现的功能是要计算出bl行的数据在显示缓冲区中的地址:
; ds[si] -> es:[bx]
mov ax, 0B800h ; 设置es:[di]为复制目标
mov es, ax
mov bl, bh ; 调用的参数是bh,用bh行数计算出正确的显示缓冲区地址
mov bh, 0
mov ax, bx
mul bx, 160 ;<<<<<<<<<<<<<<<ß---------------------此处有错
mov bx, ax ; bx存乘法结果的低位
这段程序的错误原因是错误地使用了乘法指令,刚刚开始的时候错得更离谱,连编译也出错,好吧,现在,该如何修改这段程序呢?即然知道是乘法指令的错误,那么,就从乘法指令的概令入手吧,mul指令,在执行16位数据相乘的情况下,第一位数据放在ax下,第二位数据放在mul指令的后边,也就是说mul后面只跟一个操作数,这是原程序的错误之一,第二个错,程序结果是分别保存在两个寄存器当中的,高位放在dx中,低位放在ax中,也就是说,bx最后应该加上ax之余,es也要加上dx,这样就可以使结果正确了,之前一直想不到的一个方法就是如何将dx的这位高低的数也加在bx当中呢,只是,本来dx是不属于低位的,想加在这里是没有可能的了,后来,终于想通了,转换一下角度就知道,高加高,低加低,下面是改正后的程序部分程序代码。
mov ax, 0B800h ; 设置es:[di]为复制目标
mov es, ax
mov bl, bh ; 调用的参数是bh,用bh行数计算出正确的显示缓冲区地址
mov bh, 0
mov ax, bx
mov bx, 160
mul bx
mov bx, ax ; bx存乘法结果的低位
mov di, dx ; di存放高位(80 * bh) * 2; 将ds:[si]的字符复制到es:[di]显示缓冲区处
mov ax, es ; 将高位的地址结果存放至es中,因为es是段地址
add ax, di
mov es, ax
showuntil:
mov al, ds:[si]
mov es:[bx], al
inc si
inc bx ; 显示区域需要递增2
inc bx
mov cl, ds:[si]
cmp cl, '\' ; 当它不为'\'时继续循环
jne showuntil
03. 未解决问题:function3的ret功能
1) 现象:
实现function3子程序的’q’或者’r’功能时,需要按两次相同的输入才能实现其功能,如果把function3的ret改变jmp的话,就可以只按一次键盘输入就实现功能了。
2) 问题:
为什么要按两次相同的键才能实现那一功能?
3)解决方法:
不知道是否有有心人会留意到,在源代码当中,只有一条指令是被注释上的,这是一个我一直想不通的问题:
ahfunction3end:
pop es ; 此时ah可返回的是q或者r
pop bx
jmp fifthstep
; ret ; jmpfifthstep
为什么使用jmp就正常,而使用ret就不正常了呢?根据正常的逻辑,我首先想到的是自己的push和pop没有对应好,自己的ss:sp是否改动过,然后,我仔细地检查了一次程序,发现完全对应上了,不是这个问题,于是,我单步执行追踪程序,看看问题出在哪,这问题令我很费解,调试结果竟然是,在执行int16的时候,它读取了字符通过cmp功能竟然辨认错误,于是,我在想是不是它不识别这个字符,我将这个字符改为a实现功能,失败,于是,我再想,是不是它没有在读取第一个由用户输入的字符,只是,在执行读取’c’的cmp的功能的时候,竟然正常了,只有在执行这个reset和quit功能的cmp程序的时候才会出现这样的问题,不知道为什么要ret两次,于是,换了jmp,正常返回了,只是,至今想不到解决的方法。我总结一下这里出现的问题现象吧,push,pop已对应,字符成功从键盘读取,cmp失败?这是该程序中未解决的问题。只是,小问题没有影响到整个程序的执行,只有可能是会导致多次重启程序的时候,堆栈满了,然后内存溢出。编译环境也试过换过了,还没能解决,我将这问题定义为未解决之迷!
四. 实验总结
start:
历经三天的时间,从2011.11.12至2011.11.14历经三天的时间,终于把这个815行的汇编小小小项目完成了。
day by day:
这是从我2011.10.20开始学汇编以来写过的最长的一个程序,虽然不是很强大的程序,不过我还是将它称作项目吧,因为也算是自己努力了数天的成果,编写这次的汇编小项目,让我学到的也不少。特别是在调试项目的时候,那是一段最难熬的时间,也是最能吸收知识的时间。
这次项目的时间分配如下,第一天,我苦想主题,第二天,编写程序,第三天,调试程序。
第一天,我苦想主题。这是因为我想不到做些什么,本来想过做一个小小小的文字游戏出来的,不过后来折腾了很久,还是算了,那个游戏的想法,做了出来会被人笑,一个好冷好无聊的小游戏想法,于是,我放弃了这个小游戏了,又在想,干脆把这个小游戏的想法转变一下吧,把文字转变成一些正规一点的内容吧,之后,又萌生了打字游戏的想法,只是,后来,又在想,这个程序没有很好地用到我之前学过的知识啊,而且也很多人做过这类的游戏,不是很想做,于是,又放弃了这个打字游戏的想法,之后,时间紧迫,再想就没有时间做了,于是,果断决定了,做一个能输入的,能界面颜色转变的小程序就行了,因为做这个程序无非也就是想练练手,积累经验罢了,而且这个想法好像也不错,因为自己之前也没有做过这类的程序,好吧,想到主题了,于是,刚刚开始时,还是不知道先做什么好,于是,这时候,开始将自己所想像的程序效果画面在脑海中想象出来,接着是开始由脑海中的一幕幕的画面来设计思路,知道功能以后,开始去想程序如何写了,于是,开始简单地构思一下大概的程序结构,就开始写了起来了,第一天,我将一大部分的存放于data中的字符打在了.asm里面,想出了自己想要的效果的程序,就这样结束了。
第二天,编写程序。因为我这次所写的程序所要实现的功能大多数都是以前写过的,然后现在只是将它们综合在一起,本以为可以很快就结束的,只是,在编写的过程,才发觉,原来自己有不少的知识点都忘了,于是,在编写的过程中,不断地翻书,也没有很好地将整个程序的脉络先列出来再写,算是走一步,算一步吧,就这样,写了一天,终于写出了程序,只是,晕,程序出来了根本不能运行,我将基本的语法错误改掉后,能运行了,只是,大多数的功能都不能实现,只能实现最简单的第一部分的内容,显示欢迎界面。
第三天,调试程序。这过程,实在是有时候不是很好受,对着程序,看了半天,看不出是什么问题,这个时候的调试,主要是逻辑上的问题,有时候是不知道哪个寄存器的内容没有临时将其保存起来,有时候是忘了有没有使用过call命令,有时是将ds和es的地址换转了,有时候还遇到了乘法指令的使用错误问题,编写汇编语言时感觉问起别人来没有高级语言的方便,一看,就要差不多看整个程序了,也没有怎么去问人了,因为某些时候,自己没有再三思考过的知识点,不想去问别人,有时候,越调可是越火,有想过算了,不做了,但是,放弃了又太可惜了,而且,这是自我提升的机会,于是,还是继续慢慢地看下去,总能发现问题的,因为问题是我自己创造出来的,而且,还知道,调试,肯定是需要耐心的,还是根据思路,一步步走下去,仔细再仔细地看看哪里出错吧,就这样,折腾了一天,当一个子程序的功能出来了以后,那是无比的兴奋。
learn:
在这次编程项目的编写过程中,让我对汇编的基础知识有了更好的掌握,就像程序中断mov ah,0 ,int16这个中断功能,总算是熟悉了,而也更深地领悟到了,原来这些中断,很多很多的中断,全都只是相当于高级语言里面的函数调用而己,这就是我刚刚学汇编时一直不了解的汇编的神秘一面,还有,随着自己编写的各个寄存器,各个cmp语句,各个功能的嵌套使用,发觉,好像潜移默化地将其与一些高级语言里面的知识,一些电脑的小知识联系到一起了,原来,屏幕的显示是这样的,原来它的原理是这样的,原来要显示一个字符,还需要先保存,再转换,再显示,原来,以前高级语言里面的return语句是要通过这样返回啊,高级语言里面的局部函数的思想是不是相当于汇编里面在运用子程序前先保存变量,运用完再释放这样的原理了,这样就可以不影响其它程序了。在写汇编的时候会产生的感觉真奇妙,让我知道了一些以前不知道的知识。在写汇编程序时,没能像C++那样,更好地将每个子程序分块,总是几个子程序之间jmp来jmp去,后来,在调试的时候,发觉很有问题,于是,就将自己所写出来的子程序间的关系图画了出来,再根据这个关系图,确定需要改进的地方,逐个击破,然后,再将每个子程序之间的调用关系,层次关系搞得更清楚,进行了一系列的修改,将一幅关系图改好了一点,将一个程序改善了一下,这样一下,脉络就更清晰了,在下次修改的时候也方便修改。写程序,还是分块工作,面向对象的方便啊。一定要汲取经验,前期的准备工作不足的话,会令自己在后期的时候多做很多工作。
target:
这次的程序编写,虽然没有完全实现自己想要的功能,不过,绝大部分已经实现了,只差一个时间显示的没有去做,查阅过相关知识,发觉如果把这个功能也做上的话,别人我不知道,如果对于我的话,可能再要花上三天的时间了,基于时间安排的问题,我还是先不将这个功能补充上去了,因为,即使是学习了这个功能,感觉自己的汇编知识的掌握也是相差不大。这次的汇编小项目的设计,我将其作为总结以前所学知识的一次训练,几乎是35天的王爽的汇编书籍的学习,让我对汇编有了一个小小的了解,也在学习当中知道了自己的方向,我想,我的汇编入门之路就到这里了,也不打算去进阶了,因为我的发展方向不是这个。接下来的日子,是学习数据结构的时候了。
五. 源代码
; ----------------------------------------------------------------------------------------------------- the whole program start -----
assume cs:code,ds:data, ss:stack
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 段名称:stack
; 段功能:存放堆栈的区域
; ----------------------------------------------- statement end -----------
stack segment
temparea1 dw 256 dup (0)
stack ends
; -------------------------------------------------------------------------------------------- program ends ------------
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 段名称:data
; 段功能:存放一些相关数据信息
; ----------------------------------------------- statement end -----------
data segment
alladdress dw offset screentitle, seg screentitle ; [0]标题的地址
dw offset wsc1, seg wsc1 ; [4]说明文档的地址
dw offset cmu1, seg cmu1 ; [8]第一个选单的地址
dw offset dae1, seg dae1 ; [12]默认显示的英文文章地址
dw offset cmu5, seg cmu5 ; [16]选单2的地址
dw offset dae9, seg dae9 ; [20]需要输入的英文文章的地址
dw offset clearscreen, seg clearscreen ; [24]清空屏幕
dw offset intold, seg intold ; [28]int的旧地址
dw offset intnew, seg intnew ; [32] int的新地址
screentitle db ' Free entry and display system '; 80字节屏幕标题
; ------------------ 首先存放需要默认显示的说明文档 ------------
; welcomescreen:
wsc1 db ' Welcome to the "Free entry and display system". In this system, you can real' ; 80字节
wsc2 db 'ize free entry and colour display, If you choice "1. Free entry the articles",' ; 80字节
wsc3 db ' you can input articles in 500 characters, If you choice "2. Use the system def' ; 80字节
wsc4 db 'ault articles", you can enter the interface about you finishing your inputs dire' ; 80字节
wsc5 db 'ctly. After finishing inputs, according the screen tips, this system allow to us' ; 80字节
wsc6 db 'e some functions about changing this screen colour to make your articles have a'; 80字节
wsc7 db ' good looking. If you choice"3. Exit the system". the system will quit normally.' ; 80字节
; ------------------------------ 之后存放选单1 --------------------
; choicemenu:
cmu1 db ' 1. Free entry the articles ' ; 80字节
cmu2 db ' 2. Use the system default articles (enter "|" right slash key to end) ' ; 80字节
cmu3 db ' 3. Exit the system ' ; 80字节
cmu4 db ' -> I choice: _ \' ; 20字节
; ------------------ 然后存放需要默认显示的英文文章 ------------
; defaultarticle:
dae1 db ' Why did the writer complain to the people behind him?Last week I went to the'; 80字节
dae2 db 'theater had a very good seat. The play was very interesting. I did not enjoy it. ' ; 80字节
dae3 db 'A young man and a young woman were sitting behind me. They were talking loudly. ' ; 80字节
dae4 db 'I got very angry. I could not hear the actors. I turned round. I looked at the m' ; 80字节
dae5 db 'an and the woman angrily. They did not pay any attention. In the end, I could no' ; 80字节
dae6 db 't bear it. I turned round again. I can not hear a word! I said angrily. It is' ; 80字符
dae7 db 'none of your business, the young man said rudely. This is a private conversation.' ; 81字节
dae8 db '\' ; 1字节
dae9 db 800 dup('\')
; ------------------------------ 之后存放选单2 --------------------
; choicemenu:
cmu5 db '1. Press c to change the colour of the article words ' ; 80字节
cmu6 db '2. Press f to change the queue’s colour ' ; 80字节
cmu7 db '3. Press r to restart the system ' ; 80字节
cmu8 db '4. Press q to exit the system\' ; 31字节
; ------------------------------ 清屏 --------------------
; clearscreen:
clearscreen db 1999 dup (' '),'\'
; ------------------------------------------------------ 中断向量表中的地址保存 ---------------------------------------
; intN
intold dw 0, 0
intnew dw 0, 0
data ends
; -------------------------------------------------------------------------------------------- program ends ------------
; ***************************************************************************************************************
code segment
main proc near ; main part of program
start:
mov ax, stack ; 首先设置堆栈地址
mov ss, ax
mov sp, 256 ; 使用temparea1作为堆栈
firststep:
mov bl, 0 ; 显示欢迎菜单
call controlshow
secondstep:
mov ah, 1
call ahfunctions ; 第一次调用functions进入第一个功能选单
thirdstep:
cmp ah, '3' ; 这是第一阶段的跳出功能
je mainend
call ahfunctions ; 由上一次功能选单,确定本次入口,ah为参数(已在第一次调用中确认)
; 文章选择阶段
forthstep:
mov ah, 3
call ahfunctions ; 进入第三阶段,功能选单阶段
fifthstep: ; 最后一步,紧接第三阶段,有两个出口,一是r,二是q
cmp ah, 'r'
je firststep
cmp ah, 'q'
je mainend
mainend:
mov ax, 4c00h
int 21h
main endp
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:showcontrol
; 子程序功能:与showcow程序结合使用,通过知道要复制的地址,判断出要复制的行数
; 子程序参数:bl:在data中的地址
; ----------------------------------------------- statement end -----------
screenshowarticle proc near
controlshow:
push bx
AddressShow:
as0:
cmp bl, 0 ; [0]标题的地址
jne as12 ; [8]第一个选单的地址
mov bh, 0
jmp showcow
as12:
cmp bl, 12 ; [12]默认显示的英文文章地址
jne as16
mov bh, 13
jmp showcow
as16:
cmp bl, 16 ; [16]选单2的地址
jne as20
mov bh, 21
jmp showcow
as20:
cmp bl, 20 ; [20]需要输入的英文文章的地址
jne as24
mov bh, 1
jmp showcow
as24: ; [24]清空屏幕
cmp bl, 24
jne ShowEnd
mov bh, 0
jmp showcow
ShowEnd:
pop bx
ret
; -------------------------------------------------------------------------------------------- program ends ------------
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:showcow
; 子程序功能:将指定地址的数据复制到显示缓存区中的指定行
; 子程序参数:bl:在data中的地址, bh:所要复制到的显示区中的行
; ----------------------------------------------- statement end -----------
showcow:
push ax
push ds
push si
push di
push es
push cx
mov ax, data ; 设置ds:[si]为复制源
mov ds, ax
mov cl, bl ; 借助cx将bl移至si作为偏移地址
mov ch, 0
mov si, cx
mov si, alladdress[si] ; 在地址表中找到正确的地址
mov ax, 0B800h ; 设置es:[di]为复制目标
mov es, ax
mov bl, bh ; 调用的参数是bh,用bh行数计算出正确的显示缓冲区地址
mov bh, 0
mov ax, bx
mov bx, 160
mul bx
mov bx, ax ; bx存乘法结果的低位
mov di, dx ; di存放高位(80 * bh) * 2; 将ds:[si]的字符复制到es:[di]显示缓冲区处
mov ax, es ; 将高位的地址结果存放至es中,因为es是段地址
add ax, di
mov es, ax
showuntil:
mov al, ds:[si]
mov es:[bx], al
inc si
inc bx ; 显示区域需要递增2
inc bx
mov cl, ds:[si]
cmp cl, '\' ; 当它不为'\'时继续循环
jne showuntil
pop cx
pop es
pop di
pop si
pop ds
pop ax
jmp ShowEnd
screenshowarticle endp
; -------------------------------------------------------------------------------------------- program ends ------------
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:ahfunctions(该程序与newsint程序连用)
; 子程序功能:为三个阶段的程序提供入口
; 子程序参数:ah: 实现的功能
; ----------------------------------------------- statement end -----------
ahfunctionsall proc near
ahfunctions:
push ds
push es
push si
push di
push bx
ahfunction1: ; 一阶段功能
cmp ah, 1
jne ahfunction2
call ahfunction1start
jmp ahfunctionsend
ahfunction2: ; 二阶段功能
cmp ah, 2
jne ahfunction3
call ahfunction2start
jmp ahfunctionsend
ahfunction3: ; 三阶段功能
cmp ah, 3
jne ahfunctionsend
call ahfunction3start
jmp ahfunctionsend
ahfunctionsend:
pop bx
pop di
pop si
pop es
pop ds
ret
ahfunctionsall endp
; -------------------------------------------------------------------------------------------- program ends ------------
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:ahfunction1start
; 子程序功能:完成第一阶段的功能,从数字1到3中选择功能,程序响应这三个功能,跳到正确的地址
; 子程序参数:ah: 实现的功能
; ----------------------------------------------- statement end -----------
ahfunction1startproc proc near
ahfunction1start:
push bx
push es
mov bx, 0b800h
mov es, bx
mov bh, '\'
mov es:[160*9+47*2], bh
mov ah, 0
int 16h
al101:
cmp al, '1' ; 选择1号功能,则直接调用ahfuncion2
jne al102
mov ah, 2 ; 设置入口参数ah为2,si为中断编号16
mov si, 16 ; 返回该函数,在主函数中作为新地址入口
jmp ahfunction1end
al102:
cmp al, '2'
jne al103
mov bl, 12
call controlshow ; 显示文章
call dae1todae9 ; ds:dae1 -> ds:dae9
mov si, 16
mov ah, 3
jmp ahfunction1end ; 返回该函数
al103:
cmp al, '3'
jne alnone
mov ah, al
jmp ahfunction1end ; 返回该函数
alnone:
jmp ahfunction1start ; 如果都不是这些选项,再次跳到循环处,获取正确的选择number
ahfunction1end:
mov es:[160*11+17*2], al
pop es
pop bx
ret ; 此时调用ret,回到ahfunctionall
; ------------------------------------------------------------------------------------------------------ program end ------------
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:dae1todae9
; 子程序功能:将默认的文章复制到savechar内存区中
; 子程序参数:无
; ----------------------------------------------- statement end -----------
dae1todae9: ; data:dae1 -> data:dae9
push ax
push si
push di
push ds
mov ax, data
mov ds, ax
mov di, 0
mov si, 0
dtd:
cmp byte ptr ds:dae1[si], '\'
je dea1todae9end
mov al, ds:dae1[si]
mov byte ptr ds:dae9[di], al
inc si
inc di
jmp dtd
dea1todae9end:
pop ds
pop di
pop si
pop ax
ret
ahfunction1startproc endp
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:ahfunction2start
; 子程序功能:完成第二阶段的功能,从数字1到3中选择功能,程序响应这三个功能,跳到正确的地址
; 子程序参数:ah: 实现的功能
; ----------------------------------------------- statement end -----------
ahfunction2startproc proc near
ahfunction2start:
getstr:
push bx
push cx
push es
push dx
push si
push di
push ds
; ------------------------------------------------------------------------------ statement start ------------------------------------
; 子程序名称:getstrs && nochars
; 子程序功能:接受一个键盘输入并判断该输入属于哪种类型的输入
; 若为字符,则跳到字符处理的子程序中
; 若为功能键,则判断是退格键还是Enter键,跳到相应的子程序中
; 最后循环本子程序
;------------------------------------------------------------- statement end -----
getstrs:
mov ah, 0 ; int16的读取键盘缓冲区的功能编号为0
int 16h ; 使用int16中断例程
cmp al, '\' ; 先判断它是否结束符,只以结束符终止该子程序
je charsfinish ; 如果是的话,就直接跳到结束处理
cmp al, 20h
jb nochars ; ASCII码小于20h, 说明不是字符,跳到nochar标号中
mov ah, 0
call charstack ; 字符入栈的调用参数是(ah)=0
mov ah, 2
call charstack ; 显示栈中的字符,入栈后显示字符,调用参数(ah)=2
jmp getstrs ; 循环进入该子程序输入,直至按入"\"退出
nochars:
cmp ah, 0eh ; 退格键的扫描码
je backspace
jmp getstrs ; 如果不是退格,则是不支持字符,直接重新输入
; ------------------------------------------------------------------------------------------------------ program end ------------
; ------------------------------------------------------------------------------ statement start ------------------------------------
; 子程序名称:backspace
; 子程序功能:这两个程序分别实现其功能:
; 输入backspace键时,调用charstack 子程序的(参数为1)的字符出栈,清空错误字符的显示。
;------------------------------------------------------------- statement end -----
backspace:
mov ah, 1
call charstack ; 字符出栈
jmp getstrs ; 循环进入该子程序输入,直至按入"\"退出
; ------------------------------------------------------------------------------------------------------ program end ------------
; ------------------------------------------------------------------------------ statement start ------------------------------------
; 子程序名称:charsfinish
; 子程序功能:将堆中保存的内容存入数据区中,并结束阶段二,跳到第三阶段
;------------------------------------------------------------- statement end -----
charsfinish:
mov si, offset savechar ; savechar -> si
mov ax, data
mov ds, ax ; data -> ds, 目的是让di可以指向dae9
mov di, offset dae9 ; dae9 -> di
mov es, ax ; data -> es
mov ax, code
mov ds, ax ; cs -> ds
mov cx, 500
cld ; 设置为正方向 from cs:[savechar] to data:[dae9]
rep movsb ; 开始复制 from ds:[si] to es:[di]
pop ds
pop di
pop si
pop dx
pop es
pop cx
pop bx
ret
; ------------------------------------------------------------------------------------------------------ program end ------------
; ------------------------------------------------------------------------------ statement start ------------------------------------
; 子程序名称:charstack
; 子程序功能:使用汇编实现栈
; 先使用一个table作为各功能的数据标号
; 然后使用一个名为top的数据标号记录元素个数,以实现出入读栈顶的功能
; 最后再使用各子程序实现功能
; 子程序参数:0 charpush, 1:charpop, 2:charshow
;------------------------------------------------------------- statement end -----
charstack:
jmp charstart ; 直接跳到charstart标号处,为了不执行非代码段
; table标号与top标号中的数据
table dw charpush, charpop, charshow
top dw 0
row dw 0
line dw 13 ; 默认为在第13行显示,21行前结束
savechar db 500 dup('\')
charstart: ; 程序开始前,先临时保存需要用到的寄存器
push bx
push dx
push di
push es
push ax
; 比较ah参数是否0~2正确范围内,若不是,则正常跳出子程序
cmp ah, 2
ja sret
mov bl, ah
mov bh, 0
add bx, bx
push ax
mov ax, code
mov ds, ax
pop ax
jmp word ptr ds:table[bx] ; 获取在该子程序需要执行程序的入口地址
charpush:
mov bx, top
cmp bx, 499 ; 控制字符个数,不能超过500个
je sret
push cx
mov cx, code
mov ds, cx
pop cx
mov ds:savechar[bx], al ; 将字符存放至savechar中
inc top
inc row
jmp sret
charpop:
cmp top, 0 ; 考虑无元素的情况
je getstrs
dec top ; top的位置是没有元素的,删除即先将位置减一
mov bx, top
push cx
mov cx, code
mov ds, cx
pop cx
mov byte ptr ds:savechar[bx], '\'
cmp row, 0 ; 考虑元素在首位的情况
jne norpop
mov word ptr ds:row, 80 ; 处理列(在dec的位置再减1) 0~79
dec line ; 处理行
norpop:
dec row
shownochar:
inc top
call plusrowend
dec top
jmp sret
charshow:
cmp row, 80 ; 一行只允许存放80个字符
jne plusrowend
plusrow:
inc line
mov word ptr row, 0
plusrowend:
mov bx, 0b800h
mov es, bx
mov bx, row ; bx存放需要显示的列
add bx, bx
sub bx, 2
mov ax, line ; ax存放需要显示的行
push cx
mov cx, 160
mul cx ; 将ax乘以160得出行列单元
add bx, ax ; 将低地址的相加,得出列单元
mov ax, es
add dx, ax ; 将高地址相加,得出行单元
mov es, dx
push di
mov cx, code
mov ds, cx
pop cx
mov di, top ; 显示指定字符
dec di
mov al, ds:savechar[di]
pop di
cmp al, '\'
jne showonechar ; 这里是pop的时候使用的功能
mov al, ' '
mov byte ptr es:[bx+2], al
ret
showonechar:
mov byte ptr es:[bx], al
jmp sret
sret:
pop ax
pop es
pop di
pop dx
pop bx
ret
ahfunction2startproc endp
; ***************************************************************************************************************
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:ahfunction3start
; 子程序功能:完成第三阶段的功能,从数字1到4中选择功能,程序响应这三个功能,跳到正确的地址
; 子程序参数:ah: 实现的功能
; ----------------------------------------------- statement end -----------
ahfunction3startproc proc near
ahfunction3start:
push bx
push es
mov bl, 16 ; 显示第三阶段的功能选单
call controlshow
jmp short savecol ; 存储字符及背景属性
saveco dw 0 ; 用作字符属性的还原
changecol dw 0 ; 用作字符属性的修改
savecol:
mov ax, 0b800h
mov es, ax
push es:[1] ; 获得原系统字符及背景属性
pop saveco
mov ax, saveco
mov byte ptr changecol, al
al300:
mov ah, 0 ; 用户功能选择输入
int 16h
al301: ; 一号功能,改变文字颜色
cmp al, 'c'
jne al302
call changecharcolour ; 改变字符颜色
jmp al300 ; 重新选择
al302: ; 二号功能,改变背景颜色
cmp al, 'f' ; 选择1号功能,则直接调用ahfuncion2
jne al303
call changescreencolour ; 改变屏幕颜色
jmp al300 ; 重新选择
al303:
; 三号功能,重新启动系统
cmp al, 'r'
jne al304
mov bl, 24
call controlshow ; 清空屏幕(参数为24)
call resetall ; 还原屏幕显示属性
; 清空文章自由储存区
; 清空字符串内的储存区
mov ah, al ; ah为主函数进入ahfunctions子程序的接口
jmp short ahfunction3end
al304: ; 四号功能,退出系统
cmp al, 'q'
jne alnone3
mov ah, al ; ah为主函数进入ahfunctions子程序的接口
jmp ahfunction3end
alnone3:
jmp al300 ; 如果都不是这些选项,再次跳到循环处
; 获取正确的选择number
ahfunction3end:
pop es ; 此时ah可返回的是q或者r
pop bx
jmp fifthstep
; ret ; jmpfifthstep
; ------------------------------------------------------------------------------------------------------ program end ------------
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:resetall
; 子程序功能:1.还原屏幕显示属性 2. 清空文章自由储存区 3. 清空字符串内的储存区
; 子程序参数:无,需用到ahfunction3start里面的saveco标号
; ----------------------------------------------- statement end -----------
resetall:
push ax
push es
push di
push si
push ds
; 1.还原屏幕显示属性
mov ax, 0B800H
mov es, ax
mov di, 1 ; es:[di]进入显示缓存区区域
mov ax, es:[1] ; 使al为显示属性
mov cx, 2000 ; 将一屏幕的属性清空
clearthewholescreen:
mov ptr byte es:[di], al
inc di
inc di
loop clearthewholescreen
; 2. 清空文章自由储存区
mov ax, data
mov ds, ax
mov di, 0 ; data:dae9[di]
; 3. 清空字符串内的储存区
mov ax, code
mov es, ax
mov si, 0 ; code:savechar[si]
mov cx, 500
clearthearticle:
mov byte ptr ds:dae9[di], '\'
mov byte ptr es:savechar[si], '\'
inc di
inc si
loop clearthearticle
; 将堆栈空间计算变量清零
mov ax, 0
mov es:top, ax
mov es:row, ax
mov ax, 13
mov ds:line, ax
pop ds
pop si
pop di
pop es
pop ax
ret
; ------------------------------------------------------------------------------------------------------ program end ------------
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:changecharcolour
; 子程序功能:改变文字区域的字符颜色
; 子程序参数:无,需用到ahfunction3start里面的changecharcol标号
; 附: 改变范围为显示缓冲区的13-21行
; ----------------------------------------------- statement end -----------
changecharcolour:
push ax
push es
push di
push bx
mov ax, changecol
and al, 00000111b ; 提取出文字的属性
cmp al, 7
je reset0
jmp short plus1
reset0:
and changecol, 11111000b ; 将文字属性置零
jmp short addresschar
plus1:
inc changecol ; 改变文字属性
addresschar:
mov ax, 0B800h
mov es, ax
mov di, 1
mov bx, 0
mov ax, data
mov ds, ax
mov ax, changecol
loopcharbegin:
cmp byte ptr ds:dae9[bx], '\'
je loopcharend
mov byte ptr es:[13*160 + di], al
inc di
inc di
inc bx
jmp loopcharbegin
loopcharend:
pop bx
pop di
pop es
pop ax
ret
; ------------------------------------------------------------------------------------------------------ program end ------------
; ----------------------------------------------------------------- statement start ------------------------
; 子程序名称:changescreencolour
; 子程序功能:改变文字区域的屏幕颜色
; 子程序参数:无,需用到ahfunction3start里面的changescrcol标号
; 附: 改变范围为显示缓冲区的13-21行
; ----------------------------------------------- statement end -----------
changescreencolour:
push ax
push es
push di
push ds
push si
mov ax, changecol
and al, 01110000b ; 提取出屏幕的属性
shr al, 4
cmp al, 7
je reset0s
jmp short plus1s
reset0s:
and changecol, 10001111b ; 将屏幕属性还原
jmp addressscr
plus1s:
inc al ; 属性加1后
shl al, 4 ; 还原到原来的位置
and changecol, 10001111b ; 将屏幕属性置零
push bx
mov bx, changecol
mov ah, 0
add bx, ax ; 将属性加入到置零属性中
mov word ptr changecol, bx
pop bx
addressscr:
mov ax, 0B800h
mov es, ax
mov di, 1
mov ax, data
mov ds, ax
mov ax, changecol
mov bx, 0
loopscrbegin:
cmp byte ptr ds:dae9[bx], '\'
je loopscrend
mov byte ptr es:[13*160 + di], al
inc di
inc di
inc bx
jmp loopscrbegin
loopscrend:
pop si
pop ds
pop di
pop es
pop ax
ret
ahfunction3startproc endp
; ***************************************************************************************************************
code ends
end start
; ***************************************************************************************************************