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

实验目的

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

实验准备

复习王爽《汇编语言》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调试:

  1. 理解运算符offset、伪指令equ、预定义符号$的灵活使用。
    通过line5、line8,以及数据项的数据属性(字节、字、双字,等),可以方便计算出连续数据项
    的个数,而无需人工计数。
    注*: 符号常量len1, len2不占用数据段内存空间
  2. 回答问题
    ① 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得以继续循环再次打印横杠。

posted @ 2021-11-29 21:07  【金陵彭于晏】  阅读(156)  评论(3)    收藏  举报