实验3 转移指令跳转原理及其简单应用编程
一、实验目的
- 理解和掌握转移指令的跳转原理
- 掌握使用call和ret指令实现子程序的方法,理解和掌握其参数传递方式
- 理解和掌握80×25彩色字符模式显示原理
- 综合应用寻址方式和汇编指令完成简单应用编程
二、实验结论
1. 实验任务1
task1.asm源码:
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
问题①
通过debug反汇编可以看到,跳转的位移量为F2h(16进制有符号数补码),转换为十进制为-14;
loop指令是段内转移,只修改IP;loop在指令中给出位移量,通过位移量实现跳转,且位移量的范围是-128~127;
本例中跳转的目的地址由loop下条指令的起始地址+偏移量得到,即0D=1B+F2(十六进制补码)或13=27+(-14)(十进制)
问题②
debug反汇编看到跳转的位移量为F0h,转换为十进制为-16;
本例中loop跳转的偏移地址同样由下条指令起始地址与偏移量相加得到,即29=39+F0(十六进制补码)或41=57+(-16)(十进制)
问题③
可以看到loop指令通过偏移量得到了标号处的偏移地址;
2. 实验任务2
task2.asm源码:
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
问题①
代码分析:
line16和line17 分别将s1处偏移地址存入ds:[0]和将s2处偏移地址存入ds:[2];
line24 call指令将ds:[0]处的字数据作为跳转的偏移地址,在跳转之前将当前的IP即line25的偏移地址压栈;
line25 pop指令弹出的即是line25指令的偏移地址,即(ax)=指令(pop ax)的偏移地址
line27 同理,call将ds:[2]处的双字分别作为跳转的段地址和偏移地址,跳转前将当前的即line28的CS、IP依次压栈;
line28 pop指令先弹出后入栈的IP,即(bx)=指令(pop bx)的偏移地址
line29 pop后弹出先入栈的CS,即(cx)=指令(pop bx)的段地址
问题②
反汇编:
g命令执行到0028:
可以看到,ax=0021(pop ax的偏移地址),bx=0026(pop bx的偏移地址),cx=076C(pop bx的段地址)
,和问题①的结论一致。
3. 实验任务3
task3.asm源码:
assume cs:code, ds:data
data segment
x db 99,72,85,63,89,97,55
len equ $ - x ;len = 7
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov bl, 0ah ;除数为10
mov cx, len ;7个数,循环7次
s:
mov al, [si] ;将十六进制数移入ax作为被除数
mov ah, 0
call printNumber ;进入转十进制子程序
call printSpace ;进入打印空格子长徐
inc si ;下一个数
loop s
jmp exit ;循环结束,退出程序
printNumber: ;子程序,十六进制转十进制并输出
div bl ;除法
mov dl, ah ;商放入dl,准备输出
mov dh, al
add dl, 30h ;转换成对应数字的ASCII码
mov ah, 2
int 21h ;输出商
mov dl, dh
add dl, 30h
int 21h ;输出余数
ret
printSpace: ;子程序,输出一个空格
mov dl, ' '
int 21h ;输出空格
ret
exit: ;退出程序
mov ah, 4ch
int 21h
code ends
end start
核心思想:
目标1字节十六进制转换成十进制输出;
用16位数除以0ah,得到的商和余数分别为十进制数的十位和个位;
将数据段中的每个十六进制数(8位)移入al,0移入ah,ax即为16位被除数;除数0ah移入bl,通过div bl得到商和余数;
得到的商和余数加上30h得到对应数字的ASCII码,通过int 21h输出;
对于每个16进制数,循环以上步骤
运行结果:
对于div指令的思考
网上的资料和王爽的书上是这样说的:
被除数:如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
但是通过实验发现照这样使用可能出错;
例如,被除数为16位FFFFh,除数为8位0Fh,商FFFh应该是要保存在AL中,但AL只有8位,商超过了8位,会出现divide error错误;
查看Intel白皮书中的伪代码发现,进行除法时会判断商是否超过寄存器可保存的最大值,如被除数16位时会判断商是否超过FFh,超过则提示divide error错误;
总结:使用div指令时可能会出现商过大导致的错误,建议先预估下商的位数再选择被除数存放的方式。
4. 实验任务4
task4.asm源码:
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 si, 0 ;数据段指针,指向try
mov cx, len ;几个字符循环几次
mov bl, 02h ;颜色
mov bh, 0 ;行位置
mov ax, 0b800h
mov es, ax ;显示缓冲区段地址
mov di, 0 ;显示缓冲区指针
mov al, 0a0h ;每行80个字符,每个字符2字节,每行160字节,用来确定行首
call printStr ;调用子程序
mov si, 0
mov di, 0
mov bl, 04h
mov bh, 18h
mov ax, 0b800h
mov es, ax
mov cx, len
mov al, 0a0h
call printStr
jmp exit
printStr:
mul bh ;确定行首在显示缓冲区的位置
add di, ax
s: mov al, [si]
mov es:[di], al ;移入字符ASCII码
mov al, bl
mov es:[di+1], al ;移入属性(颜色)
inc si ;try指针+1,指向下个字符
add di, 2 ;每个显示的字符占2字节,显示缓冲区指针+2
loop s
ret
exit:
mov ah, 4ch
int 21h
code ends
end start
运行结果:
核心思想:
将try的各个字符与设置的颜色依次交替放入显示缓冲区,形式如:'t' 02 'r' 02 'y' 02
分别用si和di保存数据和显示缓冲区的当前位置;
通过 a0h×行 确定行首位置,再依次将字符ASCII码、颜色移入显示缓冲区
80×24彩色字符模式:
80×25彩色字符模式显示缓冲区结构中,显示器可显示25行,每行80个字符;
内存地址空间中,B8000H~BFFFFH共32KB的空间,为80×25彩色字符模式的显示缓冲区,向这个地址空间写入数据,写入的内容立即出现在显示器上;
一行中,一个字符占两个字节的存储空间(一个字),低位字节存储字符的ASCII码,高位字节存储字符的属性。一行由80个字符,占160字节;
属性字节的格式:
7 6 5 4 3 2 1 0 BL R G B I R G B 闪烁 背景色 高亮 前景色 可以按位设置属性字节,从而搭配出各种不同的前景色和背景色。
——《汇编语言》王爽
5. 实验任务5
task5.asm源码:
assume cs:code, ds:data
data segment
stu_no db '201983290041'
len = $ - stu_no
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, 0b800h
mov es, ax
mov bl, 017h ;字符的属性,蓝底白字
mov si, 0 ;指向数据段的数据
mov di, 0 ;指向显示缓冲区位置
mov al, 050h ;每行80个字符
mov bh, 18h ;0~23行只修改属性,不修改ASCII码
mul bh
mov cx, ax ;只修改背景色的部分
s: mov es:[di+1], bl
add di, 2
loop s
mov cx, 022h ;最后一行前半部分的--
s1: mov byte ptr es:[di], '-'
mov es:[di+1], bl
add di, 2
loop s1
mov cx, len ;最后一行的学号
s2: mov al, [si]
mov es:[di], al
mov es:[di+1], bl
inc si
add di, 2
loop s2
mov cx, 023h ;学号后面的--
s3: mov byte ptr es:[di], '-'
mov es:[di+1], bl
add di, 2
loop s3
code ends
end start
核心思想:
0~23行只修改字符的属性为蓝底白字,共循环24行*80个字符次;
最后一行为34个'-' + 12位学号 + 35个'-' ,通过循环写入;
运行结果:
三、实验结论
- loop指令通过位移量实现跳转,即下条指令的起始地址+位移量(补码加法)得到跳转的偏移地址;
- call指令进行段内转移前只将ip压栈;进行段间转移前将cs、ip依次压栈;
- 十六进制转十进制用div 0ah实现,每次的余数为低位,商为高位;
- int 21h指令会根据ah中的值有不同的作用,常用的有ah为2时该指令输出dl中的字符;
- 对div指令的实现有了更深的理解(详见)