小白都能看懂的实验3 转移指令跳转原理及其简单应用编程

四 实验总结

1.实验任务1

  • 程序的源代码如下

    • assume cs:code, ds:data
      
      data segment
          x db 1, 9, 3
          len1 equ $ - x
      
          y dw 1, 9, 3
          len2 equ $ - y
      data ends
      
      code segment
      start:
          mov ax, data
          mov ds, ax
      
          mov si, offset x
          mov cx, len1
          mov ah, 2
       s1:mov dl, [si]
          or dl, 30h
          int 21h
      
          mov dl, ' '
          int 21h
      
          inc si
          loop s1
      
          mov ah, 2
          mov dl, 0ah
          int 21h
      
          mov si, offset y
          mov cx, len2/2
          mov ah, 2
       s2:mov dx, [si]
          or dl, 30h
          int 21h
      
          mov dl, ' '
          int 21h
      
          add si, 2
          loop s2
      
          mov ah, 4ch
          int 21h
      code ends
      end start
  • 运行截图如下
    •  
  • 回答问题①
    • line27, 汇编指令 loop s1 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明
      是如何计算得到跳转后标号s1其后指令的偏移地址的。

       

    • 反汇编结果如下
    • 跳转的位移量是14
    • 计算得到跳转后标号s1其后指令的偏移地址的方法如下
      • 观察Loop指令所对应的机器码E2F2
        • 一条汇编语句都会对应一个机器码,机器码分为两个部分
          1. 汇编指令对应前两个十六进制数
          2. 操作数对应之后的几个十六进制数
        • 对于loop而言,操作数是循环开始的地址与loop指令下一条指令的地址之差,且以补码的形式呈现
          • 因此可以得到公式
          • 循环开始的地址 = loop指令下一条指令的地址 + 地址之差
      • 根据公式,需要解出跳转后标号s1其后指令的偏移地址,需要知道两个信息
        1. loop指令下一条指令的地址
        2. loop指令机器码的操作数
      • 此题中
        1. loop指令下一条指令的地址为001B,转为十进制即为27
        2. loop指令机器码的操作数为F2(补码),即11110010(补码),原码为10001110,转为十进制为-14
        3. 因此循环开始的地址为27 + (-14) = 13, 即为000D
      • 以上就是CPU计算s1后指令所在位置的方法
  • 回答问题②
    • 与问题①类似,先观察反汇编的结果
    • 计算两个数
      1. loop指令下一条指令的位置,为(0039)16=(57)10
      2. loop指令机器码的操作数,为F0,即11110000,原码为10010000,也就是-16
      3. 57-16=41, (41)10=(29)16
      4. 计算完毕,与反汇编结果相同
    • 以上就是CPU计算s2后指令位置的过程 

2.实验任务2

  • 程序源代码如下
  •  
     1     assume cs:code, ds:data
     2 
     3     data segment
     4         dw 200h, 0h, 230h, 0h
     5     data ends
     6 
     7     stack segment
     8         db 16 dup(0)
     9     stack ends
    10 
    11     code segment
    12     start:  
    13         mov ax, data
    14         mov ds, ax
    15 
    16         mov word ptr ds:[0], offset s1
    17         mov word ptr ds:[2], offset s2
    18         mov ds:[4], cs
    19 
    20         mov ax, stack
    21         mov ss, ax
    22         mov sp, 16
    23 
    24         call word ptr ds:[0]
    25     s1: pop ax
    26 
    27         call dword ptr ds:[2]
    28     s2: pop bx
    29         pop cx
    30 
    31         mov ah, 4ch
    32         int 21h
    33     code ends
    34     end start
  • 问题:根据call指令的跳转原理,从理论上分析,程序执行到退出(line31)之前,寄存器ax=?, bx=?, cx=?
  • 思考过程如下
    • 首先需要知道call指令的作用
      • 将程序下一条指令的所在地址压入栈中(即把程序计数器pc中的地址压入栈中)
      • 执行call后面标号所在地址的指令(将pc中的值改为call后面的地址)
        • 在此题中将ds:[2]中的数据放入了pc
      • call word ptr a的作用
        • 将下一条指令所在地址压入栈中
        • jump到后面的标号a的所在地址
      • call dword ptr a的作用
        • 将下一条指令的段地址和段内偏移依次压入栈中
        • jump到后面标号a的所在地址
    • 其次需要读懂程序
      • data segment和stack segment定义了一系列数据
      • code segment中写了需要执行的程序
        • line13-14: 将data segment的地址值送入ds中
        • line16: 将代码段s1的地址送入ds:[0]以及ds:[1]中,供以后调用
        • line17: 将代码段s2的地址送入ds:[2]以及ds:[3]中,供以后调用
        • line18: 将cs的地址送入ds:[4]中
        • line20-22: 设置栈底和栈顶指针
          • line20-21: 将stack segment的地址值送入ss中,表示栈底的地址为stack segment指向的地址
          • line22: 将16送入sp中,表示栈顶元素的位置为ss:sp
        • line24: 调用ds:[0]和ds:[1]中的指令,分为两个阶段
          1. 将下一条指令所在地址的段内偏移地址压入栈中
          2. 将pc的值设置为ds:[0]中的数据
        • line25: 将栈顶元素弹出,并存入ax中
          • 由于刚才line24有入栈操作,因此此时弹出的应为刚才压入的内容,即s1所在地址
        • line27: 调用ds:[2],ds[3],ds:[4],ds:[5]中对应的指令,分为两个阶段
          1. 将下一条指令的段地址地址和偏移地址依次压入栈中
          2. 将pc的值设置为ds:[2]中的数据
        • line28:将栈顶元素弹出并存入bx中
          • 由于刚才line27将s2代码段所在地址的段地址和段内偏移依次压入栈中,所以此时栈顶为s2的偏移地址
        • line29:将栈顶元素弹出并存入cx中
          • 此时栈顶元素为s2的段地址
    • 读懂程序的同时,知道了该题的答案
      • 由于cs:ip指向了当前所执行的代码的地址所以 
        • ax中存放着s1代码段的地址中的段内偏移地址部分,即执行call word ptr ds:[0]时的ip寄存器中的数值
        • bx中存放着s2代码段的地址中的段内偏移地址部分,即执行call dword ptr ds:[2]时的ip寄存器中的数值
        • cx中存放着s2代码段的地址中的段地址部分,即执行call dword ptr ds:[2]时的cs寄存其中的数值
  • 实践验证
    • 首先查看反汇编结果
      • 查看call指令的地址,为001d
      • 将程序运行至call word ptr ds:[0]指令运行完毕
        • ax的值确实是ip的值0021
      • 将程序运行至call dword ptr ds:[2]指令运行完毕
        • bx的值确实是ip的值
        • cx的值确实是cs的值
    • 验证完毕,此题结束

3.实验任务3

  • 在开始实验时,需要知道以下信息
    • 有关在屏幕上输出字符的内存原理
      • 数值以二进制的形式存放在计算机内
        • 如数值12666,存放在计算机中为0011000101111010B(317AH)
        • 计算机可以理解这一串数据
      • 打印在屏幕上的我们看到的数字,是以ASCII码呈现的
        • 如数值12666,若想他们能在屏幕上显示,在计算机内的存储应该是31H,32H,36H,36H,36H
      • 若想在屏幕上看到数值,应该将计算机内的二进制数转为其对应的ASCII码
      • ASCII码只定义了0-9对应的表示方式
        • 所以在处理数值时,应一位一位处理
        • 如数值12666,应现取个位(除以十取余),将之转为字符6,再处理1266,以此类推
    • 有关在屏幕上输出字符的汇编指令
      • 指令int 21h是一条具有多功能的指令
        • 在执行时,会先查看寄存器AH的值,根据值的不同进行不同的操作
          • AH=02H时,将DL中的字符输出到屏幕上
    • 有关除法指令
      • div指令为除法操作指令
      • 指令格式
        • div 寄存器
      • 指令操作过程(以div bx为例)
        • 被除数放在寄存器AX中
        • 除数存放在指令div后跟着的寄存器中
        • 商存放在AL中
        • 余数放在AH中
    • 数字转为对应ASCII码的方法
      • 十进制数字字符对应的ASCII码 = 十进制数码值 + 30H
  • 有了以上信息,开始构思本题
    • 题目重述
      • 内存中存放了7个两位十进制数,需要将他们打印在屏幕上
    • 思路
      • 内存中存放的是数值,需要我们打印在屏幕上
        • 所以需要将这7个数逐个逐位转为ASCII码
        • 以99为例,对一个数操作的步骤如下
          1. 取出个位数字--9
          2. 将9转为其对应的ASCII码(公式上面有)
          3. 打印输出
          4. 取出十位数字--9
          5. 将9转为其对应的ASCII码'
          6. 打印输出
        • 对以上步骤,应进行以下操作(PrintNumber)
          1. 进行99/10的操作.(得到的余数默认被存放在了AH中)
          2. 对AH进行"+30H"的操作,利用公式计算出余数对应的ASCII码值
          3. 将计算得到的ASCII码值存入到dl中,以供输出
          4. 利用int 21进行输出
          5. 重复1,2,3操作,输出十位数
          6. 完成一个数字的输出
      • 打印空格比较简单,不再赘述
    • 代码实现
      •  
         1 assume cs:code, ds:data
         2 
         3 data segment
         4     x db 99, 72, 85, 63, 89, 97, 55
         5     len equ $- x    ;将当前位置与x位置之差存入len中,len=7
         6 data ends
         7 
         8 code segment
         9 start:
        10     mov ax, data    ;将x的开始地址存入si中
        11     mov ds, ax
        12     mov si, offset x
        13     mov cx,len
        14 
        15 s1: call printNumber
        16     call printSpace
        17     inc si
        18     loop s1
        19     
        20     mov ah, 4ch
        21     int 21h
        22 printNumber:
        23     mov ah,00
        24     mov al, ds:[si] ;将si地址对应的数值放入ax中,充当被除数.供除法使用
        25     mov bl, 10  ;将10放入bx中,充当除数,供除法使用
        26     div bl      ;除法
        27     or al, 30h  ;将商(原数值的十位部分)转为对应的ASCII码值
        28     mov dl, al  ;将商的数值对应的ASCII值放入dl中,以供输出
        29 
        30     or ah, 30h  ;将余数(原数值的个位部分)转为对应的ASCII码值
        31     mov bh,ah   ;将余数(原数值的个位部分)转为对应的ASCII码值存放在bh中,因为下方需要修改ah的值,为int 21h做准备,所以得先放到别的地方去
        32 
        33     mov ah,2
        34     int 21h     ;输出原数值的十位
        35 
        36     mov dl, bh
        37     mov ah,2
        38     int 21h     ;输出个位
        39 
        40     ret         ;返回调用该函数的地方
        41 
        42 printSpace:
        43     mov dl,' '  ;将空字符移入dl中,以供输出
        44     mov ah,2
        45     int 21h
        46     ret
        47 
        48     
        49 code ends
        50 end start

         

    • 运行结果如下
  • 4.实验任务4

    •  在实验开始前,我们需要知道以下信息
      • 内存地址空间中有一块特殊的区域,向其中写入内容将立刻显示在屏幕上
        • 该区域的地址范围为B8000H-BFFFFH
        • 该区域名称为80*25彩色字符模式显示缓冲区
        • 一般以B800:0000的形式表示
      • 80*25彩色字符模式显示缓冲区的特点如下
        • 一个字符占用两个字节的空间(一般一个字符占用一个字节)
        • 所占用的一个字节中,高八位为色彩信息,低八位为ASCII码
          • 根据以上特点,可以写出
            • 红底绿字,高八位为01000010B
            • 黑底白字,高八位为00000111B
            • 黑底红字,高八位为00000100B
            • 黑底绿字,高八位为00000010B
        • 行信息
          • 每一行为80个字符,因为此空间内一个字符占用两个字节,所以一行一共占用了160个字节
            • 第一行的偏移地址为000-009F
            • 第二行的偏移地址为0A0-13F
            • 以此类推,最后一行的偏移地址为F00-F9F
    • 有了以上信息,开始构思本题的代码
      • 题目重述
        • 在屏幕最上方显示黑底红字字符串,在屏幕最下方显示黑底绿字字符串
      • 思路
        • 显示彩色的字符串,需要对B8000-BFFFF写入数据
        • 在最上方显示字符串,需要从B800:0000开始写入数据
        • 在最下方显示字符串,需要从B800:0F00开始写入数据
        • 写入的数据应该是一个ASCII码+一个显示信息,循环输入
      • 有了思路,代码非常好写,代码如下
      •  1 assume cs:code, ds:data
         2 data segment
         3     str db 'try'
         4     len equ $- str
         5 data ends
         6 
         7 code segment
         8 start:
         9     mov ax,data
        10     mov ds,ax
        11 
        12     mov si,offset str
        13     mov cx, len
        14     mov bl, 00000010B   ;存颜色信息---黑底绿色
        15     mov bp, 00          ;存第一行的位置
        16     mov ax, 0B800H      ;80*25彩色字符显示区域的段地址
        17     mov es, ax
        18 
        19 s1: call printStr
        20     loop s1
        21 
        22     mov bl,00000100B    ;存颜色信息---黑底红色
        23     mov bp,0F00H         ;存最后一行行的位置
        24     mov cx,len          ;设置循环次数
        25     mov si,offset str
        26 
        27 s2: call printStr
        28     loop S2
        29 
        30     mov ah, 4ch
        31     int 21h
        32 
        33 
        34 printStr:
        35     mov al, ds:[si]
        36     mov es:[bp],al
        37     inc bp
        38     inc si
        39     mov es:[bp],bl
        40     inc bp
        41 
        42     ret
        43 
        44 
        45 code ends
        46 end start
      • 运行结果如下
  • 5.实验任务5

    • 基础知识部分与实验任务4相同,此题难点在于计算学号字符串开始的位置
      • 由于一行可以显示80个字符,我们的学号是有12个字符
      • 要想学号显示在中间左边需要空出(80-12)/2=34个字符
      • 这34个字符在80*25彩色字符显示空间中每个字符占用2个字节
      • 所以占用了68个字节,也就是44H个字节
      • 最后一行开始的偏移地址为0F00H,所以学号开始的偏移地址为0F44H
    • 程序设计思路如下
      • 总体由三个循环构成
        1. 循环使得屏幕变为蓝底
        2. 循环使得最后一行为横线(减号)
        3. 循环使得最后一行中间显示学号
    • 有了思路,程序设计非常简单
      • s1对应第一个循环
      • s2对应第二个循环
      • s3对应第三个循环,代码如下

      •  1 assume ds:data, cs:code
         2 data segment
         3     stu_no db '201983290203'
         4     len equ $ - stu_no
         5 data ends
         6 
         7 code segment
         8 start:
         9     mov ax,data
        10     mov ds,ax
        11 
        12     mov si,offset stu_no
        13 
        14     mov cx,2000
        15 
        16     mov bl,00010000B
        17     mov ax,0B800H
        18     mov es,ax
        19     mov bp,00
        20 
        21 s1: inc bp
        22     mov es:[bp],bl
        23     inc bp
        24     loop s1         ;s1循环将界面设置为蓝色
        25 
        26     mov bp,0F00H
        27     mov bl,00010111B
        28     mov cx,80
        29 
        30 s2: mov ax, 45      ;横线/减号的ASCII码为45
        31     mov es:[bp], ax
        32     inc bp
        33     mov es:[bp],bl
        34     inc bp
        35     loop s2
        36 
        37     mov ax,data
        38     mov ds,ax
        39     mov si,offset stu_no
        40     mov bp,0F44H
        41     mov cx, len
        42 
        43 s3: mov ax, ds:[si]
        44     mov es:[bp],ax
        45     inc si
        46     inc bp
        47     mov es:[bp],bl
        48     inc bp
        49     loop s3
        50 
        51 code ends
        52 end start

         

      • 运行结果如下
  • 心得体会已在博客中详细给出,谢谢!
posted @ 2021-12-02 19:31  Nekasu  阅读(226)  评论(1)    收藏  举报