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

1.实验任务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

源程序运行后结果为:
实验3 转移指令跳转原理及其简单应用编程

回答问题

① line27, 汇编指令 loop s1 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s1其后指令的偏移地址的。
实验3 转移指令跳转原理及其简单应用编程

1)反汇编查看机器码,可以看到其机器码为 E2F2
E2表示LOOP
F2是补码形式的位移量,转换为二进制为 11110010
将其转换为原码 为10001110=−14,所以其位移量为 −14
2)CPU根据目标偏移地址减去当前偏移地址得到位移量,当前指令的ip为 0019h ,即 2525−14=11
还需加上上一条指令的长度,上一条指令的偏移地址为11,而上一条指令B402的长度为2字节,所以计算出s1的偏移量为:11+2=13

② line44,汇编指令 loop s2 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s2其后指令的偏移地址的。
实验3 转移指令跳转原理及其简单应用编程

我们先运行至第一个loop处再反汇编,和以上计算方法一样,ip指向0037h,为55,补码F0转换为源码为0001000016偏移地址为55-16=39,而上一条指令B402的长度为2字节,所以计算出s1的偏移量为:39+2=41

③ 附上上述分析时,在debug中进行调试观察的反汇编截图

2.实验任务2

taska.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]
    ;push ip
    ;jmp word ptr ds:[0]
s1: pop ax
    ;(ax) = stack.top() = offset line23 + size(line23) = offset s1
    call dword ptr ds:[2]
    ;push cs
    ;push ip
    ;jmp dword ptr ds:[2]
s2: pop bx
    ;(bx) = stack.top() = offset line28 + size(line28) = offset s2
    pop cx
    ;(bx) = stack.top() = cs = code
    mov ah,4ch
    int 21h
code ends
end start

问题1

call word ptr ds:[0] 短转移, 将下一条指令偏移地址(ip)压入栈, 并转移至 ds:[0]地址即 s1 处, 此后的 pop ax 将该内容出栈给ax;
call dword ptr ds:[2] 段间转移, 将下一条指令基址和偏移地址(cs 和 ip)压入栈, 并转移至 ds:[2] 起始的双字指向的地址即 s2 处, 此后的 pop bx 将ip出栈给ax, pop cx 将 cs 出栈给 cx.
即:
(ax) = offset s1
(bx) = offset s2
(cx) = cs = code

问题2

进行代码调试
实验3 转移指令跳转原理及其简单应用编程
运行到第一次call之后,可见确实执行了s1后的代码,ax的值也确实是s1所标记地址的偏移量
继续向下执行
实验3 转移指令跳转原理及其简单应用编程
执行到第二次call,并运行两次pop之后,可见第二次call之后确实执行了s2之后的代码,并且bx的值确实是s2标记地址的偏移量,cx确实是当前代码段地址也就是cs寄存器中的内容

3.实验任务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, 实现题目要求。
    实现代码:
    task3.asm:

assume cs:code, ds:data
 
data segment
	x	db  99, 72, 85, 63, 89, 97, 55
	len	equ $- x
data ends
 
code segment
	main:     
		mov ax, data
		mov ds, ax
		
		mov cx, len
		mov si, offset x
	print:
		mov al, [si]
		mov ah, 0
		call printNumber
		call printSpace
		inc si
		loop print
 
		mov ah, 4ch
		int 21h
 
	;功能:以十进制形式输出一个两位数
	;入口参数:寄存器ax(待输出的数据 --> ax)
	;出口参数:无
	printNumber:
		mov bl, 10
		div bl
		mov bx, ax
		
		mov ah, 2
 
		mov dl, bl	; 打印商(10位)
		or dl, 30h
		int 21h
 
		mov dl, bh	; 打印余数(个位)
		or dl, 30h
		int 21h
		ret
             
	printSpace:
		mov ah, 2
		mov dl, ' '
		int 21h
		ret
 
code ends
end main

运行效果如下:
实验3 转移指令跳转原理及其简单应用编程

4.实验任务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,使得在屏幕最上方以黑底绿字显示字符串,在屏幕最下方以黑
    底红色显示字符串
    实现代码:
    task4.asm:
assume cs:code
data segment
	str db	'try'
	len equ	$ - str
data ends
code segment
	main:
		mov ax, 0b800h
		mov es, ax
	
	first_print:
		mov ax, data
		mov ds, ax
		mov si, offset str
		mov cx, len
		mov bl, 00000010b	; 黑底绿字
		mov bh, 0			; 第0行
		call printStr
	
	second_print:
		mov si, offset str
		mov cx, len
		mov bl, 00000100b	; 黑底红字
		mov bh, 24			; 第24行
		call printStr
	
		mov ah, 4ch
		int 21h
 
	; 入口参数:
  	;	字符串首字符地址 --> ds:si(其中,字符串所在段的段地址—> ds, 字符串起始地址的偏移地址—> si)
  	;	字符串长度 --> cx
  	;	字符串颜色 --> bl
  	;	指定行 --> bh (取值:0 ~24)
	printStr:
		push bp						; 因为要用到bp和di, 先保存现场
		push di
 
		mov ah, 0
		mov al, 160
		mul bh					
		mov bp, ax					; 计算行数偏移地址存储在bp
		mov di, si					; di存储显存中每个字符偏移地址
		printChar:
			mov al, ds:[si]
			mov es:[bp+di], al		; 字符
			mov es:[bp+di+1], bl	; 颜色
			inc si
			inc di
			inc di					; di要加两次
			loop printChar
		
		pop bp						; 还原现场
		pop di
		ret 
 
code ends
end main

运行结果:
实验3 转移指令跳转原理及其简单应用编程

5.实验任务5

针对8086CPU,针对8086CPU,已知逻辑段定义如下:

data segment
stu_no db '20498329042'
len = $ - stu_no
data ends

在80×25彩色字符模式下,在屏幕最后一行正中间显示学号。要求输出窗口蓝底,学号和两侧折线,以
白色前景色显示。

实现代码:

assume cs:code, ds:data
data segment
	stu_no db '201983300514'
	len = $ - stu_no
data ends
 
code segment
	main:
		call print_blue_screen
		call print_stu_no
 
		mov ah, 4ch
		int 21h
	print_blue_screen:
		push ax		; 保存现场
		push es
		push si
 
		mov ax, 0b800h
		mov es, ax
		mov cx, 2000
		mov si, 1
		single_blue:
			mov byte ptr es:[si], 00010000b
			inc si
			inc si
			loop single_blue
		
		pop si		; 还原现场
		pop es
		pop ax		
	ret
	
	print_stu_no:
		push ax		
		push es
		push si
		push ds
		push di
	prefix:
		mov ax, 0b800h
		mov es, ax
		mov cx, 34
		mov si, 3840	; si存放每次显存输出的偏移地址
		call print_dash
	content:
		mov ax, data
		mov ds, ax
		mov cx, len
		mov di, 0		; di存放data中每个字符的偏移地址
		single_no:
			mov al, ds:[di]
			inc di
			mov byte ptr es:[si], al 
			inc si
			mov byte ptr es:[si], 00010111b
			inc si
			loop single_no
	postfix:
		mov cx, 34
		call print_dash
		
		pop di
		pop ds
		pop si
		pop es
		pop ax
	ret
	
	; 输入参数:
	;	显示的基地址si
	;	输出长度cx
	; 输出:
	;	迭代后的基地址si
	print_dash:
		single_dash:
			mov byte ptr es:[si], '-'
			inc si
			mov byte ptr es:[si], 00010111b
			inc si
			loop single_dash
	ret
 
code ends
end main

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

实验总结

这次的实验我了解了loop指令的原理以及call、ret的原理,熟悉了call和ret指令进行代码块封装和重用的能力,运用标记和ret可以封装一部分代码,并且可以通过call指令调用这段代码,这样就可以避免代码的重复冗余,节省了代码编写的时间。我还学习到了很多在dos命令行上显示字符的知识,能够在命令行的任意位置以任何颜色显示想要显示的字符。此外,我还学会了向显存中写入内容以格式化输出字符。

上一篇:2.5 表达式与运算符


下一篇:实验4 8086标志寄存器及中断