小白都能看懂的实验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
- 一条汇编语句都会对应一个机器码,机器码分为两个部分
- 汇编指令对应前两个十六进制数
- 操作数对应之后的几个十六进制数
- 对于loop而言,操作数是循环开始的地址与loop指令下一条指令的地址之差,且以补码的形式呈现
- 因此可以得到公式
- 循环开始的地址 = loop指令下一条指令的地址 + 地址之差
- 一条汇编语句都会对应一个机器码,机器码分为两个部分
- 根据公式,需要解出跳转后标号s1其后指令的偏移地址,需要知道两个信息
- loop指令下一条指令的地址
- loop指令机器码的操作数
- 此题中
- loop指令下一条指令的地址为001B,转为十进制即为27
- loop指令机器码的操作数为F2(补码),即11110010(补码),原码为10001110,转为十进制为-14
- 因此循环开始的地址为27 + (-14) = 13, 即为000D
- 以上就是CPU计算s1后指令所在位置的方法
- 观察Loop指令所对应的机器码E2F2
-
- 回答问题②
- 与问题①类似,先观察反汇编的结果
![]()
- 计算两个数
- loop指令下一条指令的位置,为(0039)16=(57)10
- loop指令机器码的操作数,为F0,即11110000,原码为10010000,也就是-16
- 57-16=41, (41)10=(29)16
- 计算完毕,与反汇编结果相同
- 以上就是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]中的指令,分为两个阶段
- 将下一条指令所在地址的段内偏移地址压入栈中
- 将pc的值设置为ds:[0]中的数据
- line25: 将栈顶元素弹出,并存入ax中
- 由于刚才line24有入栈操作,因此此时弹出的应为刚才压入的内容,即s1所在地址
- line27: 调用ds:[2],ds[3],ds:[4],ds:[5]中对应的指令,分为两个阶段
- 将下一条指令的段地址地址和偏移地址依次压入栈中
- 将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寄存其中的数值
- 由于cs:ip指向了当前所执行的代码的地址所以
- 首先需要知道call指令的作用
- 实践验证
- 首先查看反汇编结果
- 查看call指令的地址,为001d
- 将程序运行至call word ptr ds:[0]指令运行完毕
- ax的值确实是ip的值0021
![]()
- 将程序运行至call dword ptr ds:[2]指令运行完毕
- bx的值确实是ip的值
- cx的值确实是cs的值
![]()
- 查看call指令的地址,为001d
- 验证完毕,此题结束
- 首先查看反汇编结果
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中的字符输出到屏幕上
- 在执行时,会先查看寄存器AH的值,根据值的不同进行不同的操作
- 指令int 21h是一条具有多功能的指令
- 有关除法指令
- div指令为除法操作指令
- 指令格式
- div 寄存器
- 指令操作过程(以div bx为例)
- 被除数放在寄存器AX中
- 除数存放在指令div后跟着的寄存器中
- 商存放在AL中
- 余数放在AH中
- 数字转为对应ASCII码的方法
- 十进制数字字符对应的ASCII码 = 十进制数码值 + 30H
- 有关在屏幕上输出字符的内存原理
- 有了以上信息,开始构思本题
- 题目重述
- 内存中存放了7个两位十进制数,需要将他们打印在屏幕上
- 思路
- 内存中存放的是数值,需要我们打印在屏幕上
- 所以需要将这7个数逐个逐位转为ASCII码
- 以99为例,对一个数操作的步骤如下
- 取出个位数字--9
- 将9转为其对应的ASCII码(公式上面有)
- 打印输出
- 取出十位数字--9
- 将9转为其对应的ASCII码'
- 打印输出
- 对以上步骤,应进行以下操作(PrintNumber)
- 进行99/10的操作.(得到的余数默认被存放在了AH中)
- 对AH进行"+30H"的操作,利用公式计算出余数对应的ASCII码值
- 将计算得到的ASCII码值存入到dl中,以供输出
- 利用int 21进行输出
- 重复1,2,3操作,输出十位数
- 完成一个数字的输出
- 打印空格比较简单,不再赘述
- 内存中存放的是数值,需要我们打印在屏幕上
- 代码实现
-
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
- 每一行为80个字符,因为此空间内一个字符占用两个字节,所以一行一共占用了160个字节
- 内存地址空间中有一块特殊的区域,向其中写入内容将立刻显示在屏幕上
- 有了以上信息,开始构思本题的代码
- 题目重述
- 在屏幕最上方显示黑底红字字符串,在屏幕最下方显示黑底绿字字符串
- 思路
- 显示彩色的字符串,需要对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
- 程序设计思路如下
- 总体由三个循环构成
- 循环使得屏幕变为蓝底
- 循环使得最后一行为横线(减号)
- 循环使得最后一行中间显示学号
- 总体由三个循环构成
- 有了思路,程序设计非常简单
- 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- 运行结果如下
- 基础知识部分与实验任务4相同,此题难点在于计算学号字符串开始的位置
- 心得体会已在博客中详细给出,谢谢!










浙公网安备 33010602011771号