04_自由录入显示系统(小钱版)[汇编语言][2011-11-15]

一. 实验准备

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
; ***************************************************************************************************************


程序运行载图:
刚刚程序开始时的界面:

选择2号功能:

按下c号键,以改变字体颜色:

接下“f"键,改变文章背景:

再按“f"键和"c"键:

再按“f"键和"c"键:


按下“r"键,再选择1号功能,自己录入文章。



最后,按q 退出界面。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值