实验3 转移指令跳转原理及其简单应用编程
实验任务1
本任务是帮助我们理解运算符offset、伪指令equ、预定义符号$的灵活使用。
经过反汇编可以得知,offset后接偏移地址,在程序加载的后已经对其进行定位。equ是等于的意思。预定义符号 $不占代码内存。
点击查看代码
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;转换成对应的ASCII码
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反汇编,查看到机器码为E2F2
,其中E2
对应了Loop的机器码,那么可以判断出F2
就是他的位移量,通过上课所学到的知识以及查阅相关资料, F2
是以补码的形式存储的,将其转换为二进制就是1000 1110
,即十进制的-14
。
从CPU角度去看,首先查看loop
后面偏移地址为000D
,在看下一条指令开始地址为001B
,那么由000D-001B=-000E
,换算成十进制就是-14
。
debug反汇编截图:
② line44,汇编指令 loop s2 跳转时,是根据位移量跳转的。分析的过程与上述类似,这里直接给出答案,偏移量是16
,由0029h-0039h
得到。并且观察机器码中F0
,也满足该答案。
实验任务2
本任务旨在对call指令原理的理解。
点击查看代码
assume cs:code, ds:data
data segment
dw 200h, 0h, 230h, 0h
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov word ptr ds:[0], offset s1
mov word ptr ds:[2], offset s2
mov ds:[4], cs
mov ax, stack
mov ss, ax
mov sp, 16
call word ptr ds:[0]
s1: pop ax
call dword ptr ds:[2]
s2: pop bx
pop cx
mov ah, 4ch
int 21h
code ends
end start
在执行到这条指令的时候,源码中由两个call指令,我们逐条分析,首先观察第一条call,这里的call指令是转到s1
处,同时目标偏移量大小为word
,那么把下一条指令的起始地址的ip
压入栈。而与之相对应的是第二个call指令处,目标偏移量是dword
,因此第二个call指令是把下一条指令地址的cs
和ip
依次压入栈中,所有看到s1
和s2
后面的pop
指令不难得到,ax=第一个的ip
、bx=第二的ip
、cx=第二的cs
。其对应内容为下截图:
理论分析完成,用debug调试的结果如下:
符合分析的结果!
实验任务3
要求是将data段中的数据以十进制输出。逻辑段如下:
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $- x
data ends
此外输出单个字符的命令为:
mov ah, 2
mov dl, ×× ; ××是待输出的字符,或其ASCⅡ码值
int 21h
观察可以发现,这几组数都是两位数,当尝试将其直接输出发现,存储在对应内存单元的都是十六进制,不能满足题意,相关截图丢失,可以根据这段内存自己脑补。
那么本题就是需要将这些两位数拆分开来,在把每个字符转换成ASCII码输出,相关代码如下:
点击查看代码
assume cs:code, ds:data
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $- x
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, offset x
mov cx, len
s: call printNumber
call printSpace
loop s
mov ah, 4ch
int 21h
printNumber:
mov ah, 0
mov al, [si]
mov bl, 10
div bl
mov bx, ax
mov ah, 2
mov dl, bl
or dl, 30h
int 21h
mov dl, bh
or dl, 30h
int 21h
inc si
ret
printSpace:
mov ah, 2
mov dl, ' '
int 21h
ret
code ends
end start
此外值得思考的是,是否有方法能将这些数字转换为字符串,这样无论字数的多少,都可以对其进行输出,这也是博主最初的解题思路,但是实现的过程有一些复杂,在查阅了部分资料之后就转换了方法。希望在今后深入学习后能掌握相关方法。
实验任务4
将如下数据段以要求的形式输出在屏幕上
data segment
str db 'try'
len equ $ - str
data ends
要求是分别在左上角和左下角以绿色和红色输出这段字符。
分析:首先可以根据要求知道需要编写相关子程序,入口参数有字符串首地址,字符串长度,字符串长度,字符串颜色,指定行。
我们逐一解决,字符串的首地址可以用offset
将str
存储在si
寄存器中,字符串长度也很简单,根据定义知道就是len
的大小,指定行根据范围可以知道,首行bh=0
,尾行bh=24
,最后确定其颜色,根据书本上所学到的:
那么黑底绿字应该是00000010b
,即十进制中的2
,黑底红色就是00000100b
,即十进制中的4
.
剩下的问题就是如何定位了,下面代码将展示:
assume cs:code, ds:data
data segment
str db 'try'
len equ $ - str
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, 0b800h
mov es, ax;显存位置
mov cx, len
mov si, offset str
mov bh, 0
mov bl, 2
call printStr
mov cx, len
mov si, offset str
mov bh, 24
mov bl, 4
call printStr
mov ah, 4ch
int 21h
; 子程序: printStr
; 功能: 在指定行、以指定颜色,在屏幕上显示字符串
; 入口参数:
; 字符串首字符地址 --> ds:si (其中,字符串所在段的段地址—> ds, 字符串起始地址的偏移地址—> si)
; 字符串长度 --> cx
; 字符串颜色 --> bl
; 指定行 --> bh (取值:0 ~ 24)
printStr:
mov al, bh;计算起始地址放在di中
mov dl, 0a0h
mul dl
mov di, ax
mov ah, bl
s: mov al, [si]
mov es:[di], ax
add di, 2
inc si
loop s
ret
code ends
end start
根据代码可以知道,这里是直接将对应行的地址计算出来,用到了mul
指令,已知有25行,每行有80个字符,输出的形式是以对应字符+属性
,所有在ah
中存属性,al
中存内容,在将ax
存放在对应显存位置即可。
最终效果如下:
符合题意,主要考察的就是对各种地址的合理运用,并且在题目已经对一些数据进行要求的情况下,如何写好程序。
实验任务5
在80×25彩色字符模式下,在屏幕最后一行正中间显示学号。要求输出窗口蓝底,学号和两侧折线,以
白色前景色显示。
逻辑段为:
data segment
stu_no db '201983290487'
len = $ - stu_no
data ends
本题可以将任务分为两部分,首先将整个页面设置为蓝底白字,再在最后一行输出要求的格式。相关代码如下:
assume cs:code, ds:data
data segment
stu_no db '201983290487'
len = $ - stu_no
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, 0b800h
mov es, ax
mov bh, 0
mov al, bh;计算起始地址放在di中
mov dl, 0a0h
mul dl
mov di, ax
mov ax, 24;计算需要重复多少次可以将屏幕变成蓝底白字
mov dl, 80
mul dl
mov cx, ax
s1: mov al, ' '
mov ah, 17h;蓝底白字
mov es:[di], ax
add di, 2
loop s1
mov si, offset stu_no
mov bh, 24
mov al, bh;计算起始地址放在di中
mov dl, 0a0h
mul dl
mov di, ax
call printStr
mov ax, 4c00h
int 21h
printStr:
call printline
mov cx, len
s2: mov ah, 17h;蓝底白字
mov al, [si]
mov es:[di], ax
inc si
add di, 2
loop s2
call printline
ret
printline:
mov cx, 34;(80-12)/2
s3: mov ah, 17h
mov al, '-'
mov es:[di], ax
add di, 2
loop s3
ret
code ends
end start
本题与实验任务4有共通之处,只是在任务4的基础上,对子程序的调用以及程序执行的次数等计算上提高了要求。
运行结果如下:
满足要求!
实验总结
本次实验已经对一些相对实际的任务有了要求,也越来越接近实际的编程。同时在写一些题目时也有收获和思考,在处理实验三时,我的第一想法是直接将这些数据输出,发现输出的16进制的ASCII码对应内容,我又在思考能否直接将这些数转换成字符串,这样也便于输出,但是在查阅了相关资料,也是了解到复杂性,偏离了本任务的考察要求,但是在之后的学习,希望能逐渐掌握这些方法。
随着实验的一次次深入,我们的实验任务以及不满足于对于命令的使用或者是对地址的判断,寄存器的使用,而是在此基础上,尝试去实现一些复杂的任务,将我们所学到的知识应用到实际中去,过程中难免遇到一些困难,但是与同学交流或者查阅资料都是解决方法。问题的一次次迎刃而解都是收获。