实验3 转移指令跳转原理及其简单应用编程

一、实验目的

  1. 理解和掌握转移指令的跳转原理
  2. 掌握使用call和ret指令实现子程序的方法,理解和掌握其参数传递方式
  3. 理解和掌握80×25彩色字符模式显示原理
  4. 综合应用寻址方式和汇编指令完成简单应用编程

二、实验结论

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个'-' ,通过循环写入;

运行结果:

三、实验结论

  1. loop指令通过位移量实现跳转,即下条指令的起始地址+位移量(补码加法)得到跳转的偏移地址;
  2. call指令进行段内转移前只将ip压栈;进行段间转移前将cs、ip依次压栈;
  3. 十六进制转十进制用div 0ah实现,每次的余数为低位,商为高位;
  4. int 21h指令会根据ah中的值有不同的作用,常用的有ah为2时该指令输出dl中的字符;
  5. 对div指令的实现有了更深的理解(详见
posted @ 2021-11-29 16:25  z_pluto  阅读(56)  评论(2编辑  收藏  举报