《汇编语言》王爽 第9章 转移指令的原理 实验8 实验9

第九章 转移指令的原理

可以修改IP,或同时修改CS和IP的指令统称为转移指令

8086CPU的转移行为有以下几类

  • 只修改IP时,称为段内转移,比如:jmp ax
  • 同时修改cs和ip时,称为段间转移,比如:jmp 1000:0

由于转移指令对P的修改范围不同,段内转移又分为:短转移和近转移。

  • 短转移IP的修改范围为-128~127。

  • 近转移P的修改范围为-32768~32767。

8086CPU的转移指令分为以下几类。

  • 无条件转移指令(如:jmp)
  • 条件转移指令
  • 循环指令(如:loop)
  • 过程
  • 中断
操作符 offset

操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。比如下面的程序:

assume cs:codesg

codesg segment

start:
		mov ax,offset start		;相当于mov ax,0
s:	
		mov ax,offset s		;相当于mov ax,3
		
codesg ends

end start
依据位移进行转移的jmp指令
jmp short 标号	;段内短位移,8位位移,-128~127
jmp near ptr 标号	;段内近位移,16位位移,-32768~32767

编译后的机器码不含有目标地址,而是含有从当前位置到目标位置的位移

cs不改变,ip改变

目的地址在指令中的jmp指令
jmp far ptr 标号

"jmp far ptr 标号"实现的是段间转移,又称为远转移.

转移地址在寄存器中的jmp指令
jmp 16位寄存器
转移地址在内存中的jmp指令
  1. jmp word ptr 偏移地址

  2. jmp dword ptr 地址

    地址的低地址处的字存储偏移地址, 高地址处的字存储段地址,因此第二种方式是段间转移.

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围为:-128~127。

jcxz 标号

如果(cx)=0, 才执行转移.

loop指令

1oop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127。

loop 标号

(cx) = (cx) - 1,如果(cx) != 0, 转移到标号执行

[!NOTE]

根据位移进行转移的设计方便了程序段在内存中的浮动装配,

因为使用的是相对位移,所以即使程序段的位置发生变化,也能保证程序正确执行.

[!WARNING]

如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错.

实验8
assume cs:codesg

codesg segment
	mov ax,4c00h
	int 21h

start:	mov ax,0
s:nop
	nop

	mov di,offset s
	mov si,offset s2
	mov ax,cs:[si]
	mov cs:[di],ax

s0:	jmp short s

s1:	mov ax,0
	int 21h
	mov ax,0

s2:	jmp short s1
	nop
	
codesg ends
end start

这个实验演示了jmp short的特性

11~14行代码把22行的命令"移到了"第8行

我们可能期待着它同样能跳转到s1

但是jmp short采用相对位移进行跳转, 而且在编译时计算并且写入机器码, 不会因为命令位置的改变而改变.

所以实际上jmp short s1在运行时代表向前移动8个字节

很巧的是,第八行执行后转移到了第4行, 而这正是程序退出命令.

实验9

编程:
在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串’welcome to masm!'。

在这里插入图片描述

代码:

assume es:data,cs:code,ss:stack

data segment
	db	'welcome to masm'	;要打印的内容
data ends

stack segment
	db 0,0,0,0,0,0,0,0;存储临时变量
	db 1,2h,24h,71h		;索引、绿色、绿底红色、白底蓝色
stack ends

code segment
start:
	mov ax,0b800h			;初始段
	mov ds,ax					;
	mov ax,data				;
	mov es,ax					;
	mov ax,stack			;
	mov ss,ax					;
	mov sp,8					;

	mov cx,3					;共打印三行
	mov di,0					;每行自增160
s0:	;外层,打印一行
	mov bx,0	  			;每个字符自增2
	mov si,0					;       自增1
	push cx

	mov cx,15 				;共15个字符
s:  ;内层,打印一个字符
	mov al,es:[si]
	mov [di+bx],al		;ascii字符

	push bx						;获取属性索引
	mov bh,0					;
	mov bl,ss:[8]			;
	mov ax,ss:[8+bx]	;获取属性
	pop bx
	mov [di+bx+1],ax	;打印字符
	add bx,2
	add si,1
	loop s

	add di,00a0h			;进入下一行	
	mov ax,ss:[8]			;修改索引,切换下一种颜色
	inc ax						;
	mov ss:[8],ax			;
	pop cx
	loop s0

	mov ax,4c00H			;程序退出
	int 21H

code ends
end start

运行结果:

在这里插入图片描述

反思:

  1. 编译错误:
inc ss:[8]
mov [di+bx],es:[si]
;都是错误写法
  1. 逻辑错误: 运行过程中发现的两个bug都是由于使用16字节寄存器读取或写入一个字节, 导致错误, 应该使用对应的8字节寄存器

  2. 可优化的写法: 当寄存器不足或冲突时, 可以尝试栈内存

    比如本程序把当前颜色索引、用到的颜色都使用了栈存储

可改进的方向:

  1. 打印任意字符串(需要计算字符串长度) PS:不得多于80个字符
  2. 打印任意行数,更多的颜色
	;计算字符数
cnt:  mov ch, 0
	mov cl, es:[bx]
	jcxz cnt_OK
	inc bx
    jmp short cnt
cnt_OK:
	mov ss:[13],bx		;记录字符数

通过以上代码,只要修改data区内的字符串就直接可以打印出来了。

但是这暴露了新的问题,我把字符数记录在了stack段,字符数和颜色,临时变量之间强耦合,如果增加颜色,第八行的代码要改,如果增大临时区,它俩都要修改,目前还没有太好的解决办法,应该可以定义多个段,用哪个段时把它mov到ss上。因为这太麻烦了我就放到一个段了

;重写了获取属性,增加了取余,行数大于颜色数时颜色循环

	push bx					;获取属性索引
	mov bh,0				;
	mov bl,ss:[8]			;
	mov ax,bx				;索引除以颜色数
	push bx					;
	mov bx,ss[14]			;没错,我又往栈里放了一个颜色数...
	div bl					;8位除法,余数在ah中
	pop bx					;
	mov bl,ah				;
	mov ax,ss:[9+bx]		;获取属性

现在随便更改打印行数也不会出错了,因为颜色会循环使用,缺点是越来越屎山了…

使用总结:
修改字符串直接在data段里修改,打印行数和颜色在stack段修改(颜色数需要自行修改)

目前无法实现的功能:
读取终端输入,打印特定行数和特定字符串,目前只能修改代码data和stack段实现

汇编代码编写难度大概是随代码量成指数增长,最初的版本很快就写完了,也只碰到几个bug,然而写这个改进版本时就暴露了代码结构混乱汇编功底浅薄的问题。

问题关键是它没有一个简易可行的编程范式,编程中要使用的常量、变量和它们的载体(寄存器、内存)都需要自己管理,访问或者修改时稍有不慎就会导致严重后果,而且debug困难。

所以以后的实验还是以实现基本功能为主,防止浪费时间。

改进后的代码:

assume es:data,cs:code,ss:stack

data segment
	db	'hello,world!'
data ends

stack segment
	db 0,0,0,0,0,0,0,0
	db 0,2h,24h,71h,7h,0fh,3h		;索引,颜色
	db 0,6,15				   	    ;字符数,颜色数,打印行数
stack ends

code segment
start:
	mov ax,0b800h
	mov ds,ax	
	mov ax,data
	mov es,ax
	mov ax,stack
	mov ss,ax
	mov sp,8

	;计算字符数
cnt:  mov ch, 0
	mov cl, es:[bx]
	jcxz cnt_OK
	inc bx
    jmp short cnt
cnt_OK:
	mov ss:[15],bl				;记录字符数

	mov ch,0					;打印行数
	mov cl,ss:[17]				;
	mov di,0					;每行自增160
s0:	mov bx,0					;每个字符自增2
	mov si,0					;       自增1
	push cx

	mov ch,0					;打印字符数
	mov cl,ss:[15] 				;
s:	mov al,es:[si]
	mov [di+bx],al				;ascii字符

	push bx						;获取属性索引
	mov bh,0					;
	mov bl,ss:[8]				;
	mov ax,bx					;对这个索引进行取余颜色数
	push bx						;
	mov bl,ss:[16]	 		    ;
	div bl						;8位除法,余数在ah中
	pop bx						;
	mov bl,ah					;
	mov ah,0					;获取属性
	mov al,ss:[9+bx]			;
	pop bx
	mov [di+bx+1],ax
	add bx,2
	add si,1
	loop s

	add di,00a0h			;进入下一行	
	mov ah,0
	mov al,ss:[8]			;下一种颜色
	inc ax
	mov ss:[8],al
	pop cx
	loop s0

	mov ax,4c00H
	int 21H

code ends
end start
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值