汇编课程实验三 转移指令跳转原理及其简单应用编程
实验目的
- 理解和掌握转移指令的跳转原理
- 掌握使用call和ret指令实现子程序的方法,理解和掌握其参数传递方式
- 理解和掌握80×25彩色字符模式显示原理
- 综合应用寻址方式和汇编指令完成简单应用编程
实验准备
复习王爽《汇编语言》9-10章:
转移指令的跳转原理
汇编指令jmp, loop, jcxz, call, ret, retf的用法
实验内容
实验任务1
assume cs:code, ds:data
data segment
x db 1, 9, 3
len1 equ $ - x ; 符号常量, $指下一个数据项的偏移地址,这个示例中,是3
y dw 1, 9, 3
len2 equ $ - y ; 符号常量, $指下一个数据项的偏移地址,这个示例中,是9
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, offset x ; 取符号x对应的偏移地址0 -> si
mov cx, len1 ; 从符号x开始的连续字节数据项个数 -> cx
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 ; 取符号y对应的偏移地址3 -> si
mov cx, len2/2 ; 从符号y开始的连续字数据项个数 -> cx
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
对源程序进行汇编、链接,得到可执行程序task1.exe,运行后,结合运行结果和注释,及必要的debug调试:
- 理解运算符offset、伪指令equ、预定义符号$的灵活使用。
通过line5、line8,以及数据项的数据属性(字节、字、双字,等),可以方便计算出连续数据项
的个数,而无需人工计数。
注*: 符号常量len1, len2不占用数据段内存空间 - 回答问题
① line27, 汇编指令 loop s1 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s1其后指令的偏移地址的。
② line44,汇编指令 loop s2 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s2其后指令的偏移地址的。
③ 附上上述分析时,在debug中进行调试观察的反汇编截图
- 执行结果

- 问题一:

LOOP指令的机器码为E2F2,E2是循环指令机器码,F2代表位移的补码表示,转换后为十进制的-14,当前指令地址0019h,即位移25,s1标号开始的位移为13,两者差值并不为14,我们翻阅书本得到结论,loop指令的位移是相对下一条指令而言的,001Bh-000Dh=14。
- 问题二:

同问题一我们可以得到位移量为F0 = -16,我们注意到这两个标号内指令数一致,那为什么两次loop的位移不同呢?原因很简单,我们观察到add指令从0034h到0037h占用了3个字节,inc指令从0018h到0019h占用1个字节,指令长度差异多出的2个字节即是原因。
实验任务2
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指令的跳转原理,先从理论上分析,程序执行到退出(line31)之前,寄存器(ax) = ? 寄存器(bx) = ? 寄存器(cx) = ?
② 对源程序进行汇编、链接,得到可执行程序task2.exe。使用debug调试,观察、验证调试结果与理论分析结果是否一致。
- 问题一
(ax)= offset s1
(bx) = offset s2
(cx) = cs = code
call word ptr ds:[0] 将ip入栈,当前ip指向offset s1,进入到s1执行完pop ax后,(ax)=offset s1call dword ptr ds:[2]先将cs入栈,再将ip入栈,所以(bx)同上=offset s2,(cx)=cs,又当前段地址为code,所以(cx)=code
- 问题二



可以看到与理论预期一致
实验任务3
针对8086CPU,已知逻辑段定义如下:
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $- x
data ends
编写8086汇编源程序task3.asm,在屏幕上以十进制形式输出data段中这一组连续的数据,数据和数据之间以空格间隔。
要求:
- 编写子程序printNumber
- 功能:以十进制形式输出一个两位数
- 入口参数:寄存器ax(待输出的数据 --> ax)
- 出口参数:无
- 编写子程序printSpace
- 功能:打印一个空格
- 入口参数:无
- 出口参数:无
在主体代码中,综合应用寻址方式和循环,调用printNumber和printSpace, 实现题目要求。正确编写后,预期测试结果如下:

- 实验代码:
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 byte ptr ds:[len],10
mov cx,len
mov bx,0
s: mov al,ds:[bx]
mov ah,0
inc bx
call printNumber
call printSpace
loop s
mov ah,4ch
int 21h
printNumber:
div byte ptr ds:[len]
mov dx,ax
mov ah,2
or dl,30h
int 21h
mov ah,2
mov dl,dh
or dl,30h
int 21h
ret
printSpace:
mov dl,' '
mov ah,2
int 21h
ret
code ends
end start
len是data段到存放数据的x的位移,即数据长度,将ds:[len] 设为10,以便后续做除法分离个位和十位用。标号s遍历data段内所有数据,每次遍历call一次printNumber和printSpace。在printNumber内对数据做除法,分别保留十位与个位分两次打印,完成目标。

实验任务4
针对8086CPU,已知逻辑段定义如下:
data segment
str db 'try'
len equ $ - str
data ends
编写8086汇编源程序task4.asm,在屏幕上以指定颜色、指定行,在屏幕上输出字符串。
要求:
- 编写子程序printStr
- 功能:在指定行、以指定颜色,在屏幕上显示字符串
- 入口参数
- 字符串首字符地址 --> ds:si(其中,字符串所在段的段地址—> ds, 字符串起始地址的偏移地址—> si)
- 字符串长度 --> cx
- 字符串颜色 --> bl
- 指定行 --> bh (取值:0 ~24)
- 出口参数:无
- 在主体代码中,两次调用printStr,使得在屏幕最上方以黑底绿字显示字符串,在屏幕最下方以黑底红色显示字符串
程序正确编写后,预期输出效果如下:

- 实验代码
assume ds:data, cs:code
data segment
str db 'try'
len equ $ - str
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov cx,len
mov bh,0
mov bl,00000010B;绿色
call printStr
mov si,0
mov cx,len
mov bh,24
mov bl,00000100B;红色
call printStr
mov ax,4c00h
int 21h
printStr:
mov ax,0b800h;设置ax为显存段地址
mov es,ax
mov al,bh
mov bh,0a0h
mul bh
mov bp,ax
s:
mov ah,ds:[si]
mov es:[bp],ah
inc bp
mov es:[bp],bl
inc bp
inc si
loop s
ret
code ends
end start
分两次调用printStr,每次调用前将打印行号和颜色信息存入对应寄存器,在printStr中对显存进行一些配置,顺序向下执行标号s,s负责遍历字符串,并将对应字符信息和颜色信息存入显存,循环结束后执行ret指令回到call的IP地址。

实验任务5
针对8086CPU,已知逻辑段定义如下:
data segment
stu_no db '20498329042'
len = $ - stu_no
data ends
在80×25彩色字符模式下,在屏幕最后一行正中间显示学号。要求输出窗口蓝底,学号和两侧折线,以白色前景色显示。
程序正确编写后,预期输出效果如下:

- 实验代码
assume ds:data, cs:code
data segment
stu_no db "201983290391"
len = $ - stu_no
data ends
code segment
start:
mov ax,0b800h
mov es,ax
mov bp,1
mov cx,8000h
pre:
mov byte ptr es:[bp],00011111B
add bp,2
loop first
mov ax,data
mov ds,ax
mov ax,0050h
sub ax,len
mov bh,02h
div bh
mov bl,al
mov bh,0
mov ax,0b800h
mov es,ax
mov bp,0F00h
mov cx,bx
s:
mov byte ptr es:[bp],'-'
inc bp
inc bp
loop s
mov cx,len
mov di,0
s1:
mov al,ds:[di]
mov es:[bp],al
inc bp
inc bp
inc di
loop s1
mov cx,bx
s2:
mov byte ptr es:[bp],'-'
inc bp
inc bp
loop s2;
mov ax,4c00h
int 21h
code ends
end start
第一个思路就是测试得到dos窗口一行的长度,用len = $ - stu_no 表示学号长度,行长减len除以2就是横杠的长度,分三段打印,按序执行即可,不需要进行跳转。使用跳转的思路是在打印横杠的标号s的loop结束时重新赋值cx,利用call指令跳转到打印学号的标号,用ret指令返回s,再刷新cx使得s得以继续循环再次打印横杠。


浙公网安备 33010602011771号