《汇编语言(第四版)》 . 王爽著 . 清华大学出版社 . 2019
实验 6 实践课程中的程序
实验任务(1)
将课程中所有讲解过的程序上机调试,用 Debug 跟踪其执行过程,并在过程中进一步理解所讲内容。
程序 1
在汇编程序中,用 ‘…’ 的方式指名数据是以字符的形式给出的,编译器将把它们转化为相对应的 ASCII 码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | assume cs:code,ds:data
  data segment       db 'unIX'       db 'foRK' data ends
  code segment
  start:      mov al,'a'             mov bl,'b'             mov ax,4c00h             int 21h
  code ends
  end start
   | 
 
用 Debug 跟踪程序运行
ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII 第一次以规范标准的类型发表是在 1967 年,最后一次更新则是在 1986 年,到目前为止共定义了 128 个字符
ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。标准 ASCII 码也叫基础 ASCII 码,使用 7 位二进制数(剩下的 1 位二进制为 0)来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符
常用的 ASCII 码
0x30 ~ 0x39 : 字符 1 ~ 9
0x41 ~ 0x5A : 大写字母 A ~ Z
0x61 ~ 0x7A : 小写字母 a ~ z
程序 2
在 codesg 中填写代码,将 datasg 中的第一个字符串转化为大写,第二个字符串转化为小写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | assume cs:codesg,ds:datasg
  datasg segment       db 'BaSiC'       db 'iNfOrMaTiOn' datasg ends
  codesg segment
  start:             ?
  codesg ends
  end start
   | 
 
通过查 ASCII 码标准表,我们可以很轻易地看出来,小写字母的 ASCII 码值比大写字母的 ASCII 码值大 20H,那么如果我们能够判断当前字母是大写还是小写,就可以按这个规律进行大小写之间的改变
可惜的是,到这个实验为止,我们还没有接触到能够对字母的大小写进行判断的指令,那么我们必须找到新的规律
从 ASCII 码的二进制形式着手,我们发现,大写字母与其小写字母的二进制表示的唯一区别是第 5 位不相同,例如,小写字母 a 为 0110 0001,大写字母 A 为 0100 0001,那么如果有一个字母,将它的第 5 位置 1 即为小写,置 0 即为大写,这样就避免了判断
注意,将某一位置 0 或置 1 可以使用 and 与 or 指令
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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'BaSiC'       db 'iNfOrMaTiOn' datasg ends
  codesg segment
  start:      mov ax,datasg                        mov ds,ax                            mov bx,0                             mov cx,5                
      s:      mov al,[bx]             and al,11011111B                     mov [bx],al             inc bx             loop s
              mov bx,5                             mov cx,11               
     s0:      mov al,[bx]             or al,00100000B                      mov [bx],al             inc bx             loop s0
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用 Debug 跟踪程序运行
程序 3
用 [bx+idata] 来指明内存单元,这种寻址方式被称为寄存器相对寻址,它为高级语言实现数组提供了便利机制
用 Debug 查看内存,结果如下
2000:1000   BE 00 06 00 00 00...
写出下面的程序执行后,ax、bx、cx 中的内容
1 2 3 4 5 6
   | mov ax, 2000H mov ds,ax mov bx,1000H mov ax,[bx] mov cx,[bx+1] mov cx,[bx+2]
   | 
 
AX 中为 00BEH,BX 中为 1000H,CX 中为 0606H
注意,数据寄存器的大小为一个字,传送进去的数据也应为一个字大小,并且注意是小端序
用 Debug 跟踪程序运行
程序 4
使用 [bx+idata] 的方式进行数组的处理,在 codesg 中填写代码,将 datasg 中的第一个字符串转化为大写,第二个字符串转化为小写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | assume cs:codesg,ds:datasg
  datasg segment       db 'BaSiC'       db 'MinIX' datasg ends
  codesg segment
  start:                   ?
  codesg ends
  end start
   | 
 
为了简化程序,将两个循环合并成一个循环,可以用 [0+bx] 和 [5+bx] 的方式在同一个循环中定位这两个字符串中的字符,0 和 5 给定两个字符串的起始偏移地址,bx 给定从起始偏移地址开始的相对地址
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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'BaSiC'       db 'MinIX' datasg ends
  codesg segment
  start:      mov ax,datasg                        mov ds,ax             mov bx,0             mov cx,5            s:      mov al,[bx]                          and al,11011111b             mov [bx],al                          mov al,[5+bx]                        or al,00100000b             mov [5+bx],al             inc bx             loop s
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用 Debug 跟踪程序运行
程序 5
si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,但 si 和 di 不能够分成两个 8 位寄存器来使用
用 si 和 di 实现将字符串 ‘welcome to masm!’ 复制到它后面的数据区中。
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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'welcome to masm!'       db '................' datasg ends
  codesg segment
  start:      mov ax,datasg             mov ds,ax             mov si,0             mov di,16
              mov cx,8     s:      mov ax,[si]             mov [di],ax             add si,2             add di,2             loop s                          mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
变址寄存器:SI(source index)、DI(destination index)
用 ds:si 指向要复制的源字符串,用 ds:di 指向复制的目的空间,用循环来完成复制,一次循环复制 2 个字节
用 Debug 跟踪程序运行
程序 6
用更少的代码,实现问题 7.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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'welcome to masm!'       db '................' datasg ends
  codesg segment
  start:      mov ax,datasg             mov ds,ax             mov si,0             mov cx,8
      s:      mov ax,0[si]             mov 16[si],ax             add si,2             loop s
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
si 与 di 是存在一定的关系的,可以用 si+16 表示 di,这样可以省去一个寄存器
程序 7
用 Debug 查看内存,结果如下
2000:1000   BE 00 06 00 00 00...
写出下面的程序执行后,ax、bx、cx 中的内容
1 2 3 4 5 6 7 8 9 10
   | mov ax,2000H mov ds,ax mov bx,1000H mov si,0 mov ax,[bx+si] inc si mov cx,[bx+si] inc si mov di,si add cx,[bx+di]
   | 
 
AX 中为 00BEH,BX 中为 1000H,CX 中为 0606H
可参考程序 3
用 Debug 跟踪程序运行
程序 8
[bx+si+idata] 与 [bx+di+idata] 的含义相似,表示一个偏移地址为 (bx)+(si)+idata 或 (bx)+(di)+idata 的内存单元,这种寻址方式被称为相对基址变址寻址
用 Debug 查看内存,结果如下
2000:1000   BE 00 06 00 6A 22...
写出下面的程序执行后,ax、bx、cx 中的内容
1 2 3 4 5 6 7 8 9 10
   | mov ax,2000H mov ds,ax mov bx,1000H mov si,0 mov ax,[bx+2+si] inc si mov cx,[bx+2+si] inc si mov di,si add bx,[bx+2+di]
   | 
 
AX 中为 0006H,BX 中为 226AH,CX 中为 6A00H
用 Debug 跟踪程序运行
总结比较前面用到的寻址方式
[idata] 用一个常量来表示地址,可用于直接定位一个内存单元,称为直接寻址 
[bx] 用一个变量来表示内存地址,可用于间接定位一个内存单元,称为寄存器间接寻址 
[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元,称为寄存器相对寻址 
[bx+si] 用两个变量表示地址,称为基址变址寻址 
[bx+si+idata] 用两个变量和一个常量表示地址,称为相对基址变址寻址 
程序 9
编程,将 datasg 段中每个单词的头一个字母改为大写字母
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
   | assume cs:codesg,ds:datasg
  datasg segment       db '1. file         '       db '2. edit         '       db '3. search       '       db '4. view         '       db '5. options      '       db '6. help         ' datasg ends
  codesg segment
  start:      mov ax,datasg             mov ds,ax             mov bx,0             mov cx,6     s:      mov al,[bx+3]             and al,11011111b             mov [bx+3],al             add bx,16             loop s
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用 bx 定位每行的起始地址,用 3 定位要修改的列,每行的起始地址相差 16,用 [bx+idata] 的方式来对目标单元进行寻址
用 Debug 跟踪程序运行
程序 10
编程,将 datasg 段中每个单词改为大写字母。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | assume cs:codesg,ds:datasg
  datasg segment       db 'ibm             '       db 'dec             '       db 'dos             '       db 'vax             ' datasg ends
  codesg segment
  start:                   ?
  codesg ends
  end start
   | 
 
用 bx 定位每行的起始地址,用 si 定位要修改的列,每行的起始地址相差 16,用 [bx+si] 的方式来对目标单元进行寻址
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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'ibm             '       db 'dec             '       db 'dos             '       db 'vax             ' datasg ends
  codesg segment
  start:      mov ax,datasg             mov ds,ax             mov bx,0
              mov cx,4
     s0:      mov si,0             mov cx,3
      s:      mov al,[bx+si]                       and al,11011111b             mov [bx+si],al             inc si             loop s
              add bx,16             loop s0
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
在上面的程序中,我们进行了二重循环,却只使用了一个循环计数器,造成在进行内层循环的时候,覆盖了外层循环的循环计数值,所以程序是有问题的
我们应该在每次开始内层循环的时候,将外层循环的 cx 中的数值保存起来,在执行外层循环的 loop 指令前,再恢复外层循环的 cx 数值
比如,用寄存器 dx 来临时保存 cx 中的数值
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
   | assume cs:codesg,ds:datasg
  datasg segment       db 'ibm             '       db 'dec             '       db 'dos             '       db 'vax             ' datasg ends
  codesg segment
  start:      mov ax,datasg             mov ds,ax             mov bx,0
              mov cx,4
     s0:      mov dx,cx                            mov si,0             mov cx,3                
      s:      mov al,[bx+si]             and al,11011111b             mov [bx+si],al             inc si             loop s                          add bx,16             mov cx,dx                            loop s0                 
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用别的寄存器暂存 cx 中的值只是权宜之计,CPU 中寄存器的数量有限,且大都有各自的任务要做,而程序中经常会出现暂存数据的需求
1 2 3 4 5 6 7
   | datasg segment       db 'ibm             '       db 'dec             '       db 'dos             '       db 'vax             '       dw 0                           datasg ends
   | 
 
可以考虑将暂存的数据放到内存单元中,需要的时候,从内存单元恢复,但如果需要同时保存多个数据的时候,就得记下数据放到了哪个内存单元中,这样会使得程序混乱
一般来说,在需要暂存数据的时候,我们都应该使用栈
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
   | assume cs:codesg,ds:datasg,ss:stacksg
  datasg segment       db 'ibm             '       db 'dec             '       db 'dos             '       db 'vax             ' datasg ends
  stacksg segment                            dw 0,0,0,0,0,0,0,0 stacksg ends
  codesg segment
  start:      mov ax,stacksg             mov ss,ax             mov sp,16             mov ax,datasg             mov ds,ax             mov bx,0
              mov cx,4
     s0:      push cx                              mov si,0             mov cx,3                
      s:      mov al,[bx+si]             and al,11011111b             mov [bx+si],al             inc si             loop s                          add bx,16             pop cx                               loop s0                 
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用 Debug 跟踪程序运行
实验任务(2)
编程,将 datasg 段中每个单词的前 4 个字母改为大写字母。
用二重循环 + 相对基址变址寻址解决
用 bx 定位每行的起始地址,用 si 定位要修改的列,用 [bx+3+si] 的方式来对目标单元进行寻址
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
   | assume cs:codesg,ds:datasg,ss:stacksg
  stacksg segment       dw 0,0,0,0,0,0,0,0 stacksg ends
  datasg segment       db '1. display      '       db '2. brows        '       db '3. replace      '       db '4. modify       ' datasg ends 
  codesg segment
  start:      mov ax,stacksg             mov ss,ax             mov sp,16               
              mov ax,datasg             mov ds,ax               
              mov bx,0             mov cx,4                
     s1:      push cx                              mov si,0             mov cx,4                
     s2:      mov al,[bx+3+si]             and al,11011111b             mov [bx+3+si],al                     inc si             loop s2
              add bx,16                            pop cx                               loop s1
              mov ax,4c00h             int 21h
  codesg ends
  end start
   | 
 
用 Debug 跟踪程序运行
汇编语言实验合集
汇编语言实验合集
实验 1 查看 CPU 和内存,用机器指令和汇编指令编程
实验 2 用机器指令和汇编指令编程
实验 3 编程、编译、连接、跟踪
实验 4 [bx] 和 loop 的使用
实验 5 编写、调试具有多个段的程序
实验 6 实践课程中的程序
实验 7 寻址方式在结构化数据访问中的应用
实验 8 分析一个奇怪的程序
实验 9 根据材料编程
实验 10 编写子程序
课程设计 1
实验 11 编写子程序
实验 12 编写 0 号中断的处理程序
实验 13 编写、应用中断例程
实验 14 访问 CMOS RAM
实验 15 安装新的 int9 中断例程
实验 16 编写包含多个功能子程序的中断例程
实验 17 编写包含多个功能子程序的中断例程
课程设计 2