(二十二)jmp jcxz loop 转移指令 和 offset 指令--汇编笔记

汇编语言

Posted by YiMiTuMi on April 7, 2020

转移指令原理

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

转移指令就是可以控制CPU执行内存中某处代码的指令。

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

 1)只修改IP时,称为段内转移,比如:jmp ax。

 2)同时修改CS和IP时,称为段间转移(远转移),比如:jmp 1000:0。

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

 1)短转移IP的修改范围为-128 ~ 127

 2)近转移IP的修改范围为-32768 ~ 32767

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

 1)无条件转移指令(如:jmp)

 2)条件转移指令

 3)循环指令(如:loop)

 4)过程

 5)中断

操作符offset

操作符offset在汇编语言中是由编译器处理的符号,他的功能时取得标号的偏移地址。

例:

assume cs:codesg

codesg segment

	start:  mov ax, offset start //相当于mov ax, 3
		s:  mov ax, offset s     //相当于mov ax, 0

codesg ends

end start

offset分别获取了标号start和s的偏移地址0和3。

mov ax, offset start 相当于指令 mov ax, 0, 因为start是代码段中的标号,他所标记的指令时代码段中的第一条指令,偏移地址为0;

mov ax, offset s 相当于指令 mov ax, 3 ,因为s是代码段中的标号,它所标记的指令是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3.

例:将s处的一条指令复制到s0处

assume cs:codesg

codesg segment

	s:  mov ax, bx
		mov si, offset s
		mov di, offset s0
		
		mov ax, cs:[si]    //si保存 s指令的偏移地址
		mov cs:[di], ax    //di保存 s0指令的偏移地址
	s0: nop
		nop

codesg ends

end s

分析:

 1)s内存单元的地址:cs:offset s

   s0内存单元的地址:cs:offset s0

 2)s处复制到s0处,就是 cs:offset s 复制到 cs:offset s0

 3)cs:offset scs:offset s0 偏移地址送入si 和 di 中。

 4)要复制指令为 mov ax, bx 指令的长度为两个字节,即一个字用ax保存。

依据位进行转移的jmp指令(jmp short(或near ptr) 标号)

格式:

jmp short 标号(转到标号处执行命令)

这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128 ~ 127,也就是说,他向前转移时可以最多越过128个字节,向后转移最多越过127个字节。

short:说明是短转移。

标号:是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令。

例:

assume cs:codesg
	
codesg segment
	
	start:  mov ax, 0

			jmp short s

			add ax, 1

		s:  inc ax

codesg ends

end start		

程序执行 jmp short s 后,越过了 add ax, 1 ,IP指向了标号s处的 inc ax 。也就是说只执行了一次 ax 加一操作。

CPU在执行jmp指令的时候并不需要转移的目的地址

其对应的机器指令中并没有转移的目的地址,而是相对于当前IP的转移位移。

CPU执行指令的过程:

 1)从CS:IP指向内存单元读取指令,读取指令进入指令缓冲器

 2)(IP) = (IP) + 所读取指令的长度,从而指向下一条指令

 3)执行指令。转到1,重复这个过程。

在“jmp short 标号”指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移 。这个位移,是编译器根据汇编指令中的 “标号”计算出来的。

如:

偏移地址     机器码                        汇编指令

0000        00 40                         s: inc ax

0001        EB 03 //根据两点距离计算:6-3   jmp s0

0003        BB 03 00                      mov bx, 3

0006        00 43                         s0:inc bx

0007        EB F7  //0 - 9(补码F7)       jmp s

0009        00 90                         nop

实际上,“jmp short 标号”的功能为:(IP)= (IP) + 8位位移。

 1)8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址(即jmp指令下面一行指令的地址)。

 2)short 指明此处的位移为8位位移,段内近转移。

 3)8位位移的范围为-128 ~ 127,用补码表示。

 4)8位位移由编译程序在编译时算出。

jmp near ptr 标号

功能:

和“jmp short 标号”功能相近的指令格式,它实现的时段内近转移

jmp near ptr 标号”的功能为:(IP)= (IP) + 16位位移。

 1)16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址。

 2)near ptr 指明此处的位移为16位位移,进行的是段内近转移。

 3)16位位移地址为 -32768 ~ 32767,用补码表示。

 4)16位位移由编译程序在编译时算出。

转移的目的地址在指令中的jmp指令(jmp far ptr 标号)

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

功能:

 (CS) = 标号所在段的段地址

 (IP) = 标号在段中的偏移地址

far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP。(段内转移: 相对于当前IP的转移位移

例:

assume cs:codesg

codesg segment

	start:  mov ax, 0
			
			mov bx, 0

			jmp far pty s

			db 256 dup (0)

		s:  add ax, 1
			
			inc ax

codesg ends

end start

Debug:

偏移地址      机器码         汇编指令

0000         B80000         mov ax, 0000

0003         B80000         mov bx, 0000

0006         EA 0B01BD0B     jmp 0BBD:010B

和段内转移不同,“jmp far pty”在机器码中包含转移的目的地址

 低位地址“0B 01”是偏移地址:010BH

 高位地址“BD 0B”是段地址:0BBDH

转移地址在寄存器中的jmp指令 (jmp 16为reg)

指令格式: jmp 16位reg

功能:(IP) = (16位reg)

转移地址在内存中的jmp指令(jmp word pty(jmp dword pty) 内存单元)

1)jmp word pty 内存单元地址(段内转移)

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。

(IP) = (内存单元地址)

内存单元地址可用寻址方式的任意格式给出。(直接寻址,寄存器间接寻址,相对寻址,基址变址寻址,相对基址变址寻址)

例:

mov ax, 0123H

mov ds:[0], ax

jmp word ptr ds:[0]

执行后:(IP)= 0123H

mov ax, 0123H

mov [bx], ax

jmp word ptr [bx]

执行后:(IP)= 0123H

2)jmp dword ptr 内存单元地址(段间转移)

功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。

(CS)= (内存单元地址 + 2) (IP)= (内存单元地址)

内存单元地址可用寻址方式的任意格式给出。(直接寻址,寄存器间接寻址,相对寻址,基址变址寻址,相对基址变址寻址)

例:

mov ax, 0123H

mov ds:[0]:ax

mov word ptr ds:[2], 0

jmp dword ptr ds:[0]

执行后:

(CS) = 0000H

(IP) = 0123H

CS:IP 指向 0000:0123

mov ax, 0123H

mov [bx], ax

mov word ptr [bx + 2], 0

jmp dword ptr [bx]

执行后:

(CS) = 0000H

(IP) = 0123H

CS:IP 指向 0000:0123H

例:使jmp指令指向第一条指令,CS:IP指向第一条指令

assume cs:code ds:data

data segment

	dd 123456H

data ends

code segment

	start:  mov ax, data
			
			mov ds, ax

			mov ax, code

			mov cs, ax
			
			mov bx, 0
			
			mov ds:[bx], offset start //低位存偏移地址

			mov ds:[bx + 2], cs     //保存段地址 或 mov [bx + 2], code

			jmp dword ptr ds:[0]

code ends

end start

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短指令

对应的机器码中包含转移的位移(同jmp short),而不是目的的地址。

对IP的修改范围都为:-128 ~ 127。

指令格式:

jcxz 标号(如果(cx)= 0, 转移到标号处执行。)

操作:

 当(cx)= 0,时, IP = IP + 8位位移;

 8位位移 = 标号处的地址 - jcxz指令后的第一个字节的地址;

 8位位移的范围位-128 ~ 127,用补码表示;

 8位位移有编译程序在编译时算出。

 当(cx)!= 0 时, 什么也不做,程序向下执行。

相当于:

if ((cx) == 0)
{
	jmp short 标号;
}

例:

利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它们的偏移地址存储在dx中。

assume cs:code

code segment

	start:  mov ax, 2000H

			mov ds, ax
	
			mov bx, 0        //偏移地址

		s:  mov ch, 0

			mov cl, ds:[bx] //存放bx地址中的数据 最大8位

			jcxz ok
	
			inc bx      //偏移地址向下移
			
			jmp short s  //相当于死循环

		ok: mov dx, bx

			 mov ax, 4C00H

			int 21H

code ends

end start

loop指令(loop 标号)

loop指令 为循环指令,所有的循环指令都是短指令。

对应的机器码中包含转移的位移,而不是目的地址。

对IP的修改范围都为: -128 ~ 127。

指令格式:

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

操作:

 1)(cx)= (cx)- 1

 2)如果(cx)!= 0, (IP)= (IP) + 8位位移

 8位位移 = 标号处的地址 - loop指令后的第一个字节的地址;

 8位位移的范围位-128 ~ 127,用补码表示;

 8位位移由编译程序在编译时算出。

 如果(cx)= 0,什么也不做(程序向下执行)。

相当于:

(cx)--;

if (cx != 0)
{
	jmp short 标号;
}

例:

利用loop指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它们的偏移地址存储在dx中。

assume cs:code

code segment

	start:  mov ax, 2000H
			
			mov ds, ax

			mov bx, 0

		s:  mov cl, ds:[bx]

			mov ch, 0

			inc cx    //在判断cx是否为零前,会先执行cx - 1, 这样实际判断的数值就会少1,所以要先加上1。

			inc bx   //+1获取下个偏移地址,保存的话要减一

			loop s   //cx为 0 循环结束

		ok: dec bx    //dec 相当于 (bx) = (bx) - 1,上面循环结束后bx + 1为了获取下一行的偏移地址,所以要减去1获取当前行的地址

			mov dx, bx

			mov ax, 4c00H

			int 21H

code ends

end start

dec 相当于 bx = bx - 1;

根据位移进行转移的意义

jmp short 标号

jmp near pty 标号

jcxz 标号

loop 标号

这几种汇编指令,它们对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们的机器码中不包含转移的目的地址,而包含的是到目的地址的位移

这种设计,方便了程序段在内存中的浮动装配。

例:

	汇编指令              机器码

	mov cx, 6            B9 06 00

	mov ax, 10H          B8 10 00

s: add ax, ax            01 C0
 
	loop s               E2 FC

这段程序装在内存中的不同位置都可正确执行,因为 loop s 在执行时只涉及s的位移(-4,前移4个字节,补码表示为FCH),而不是s的地址。如果 loop s 的机器码中包含的时s的地址,则就对程序段在内存中的偏移地址有了严格的限制,因为机器码中包含的是s的地址,如果s处的指令不再目的地址处,程序的执行就会出错。而 loop s 的机器码中包含的是转移的位移,就不存在这个问题,因为,无论s处的指令的实际地址是多少,loop指令的转移位移是不变的。

分析一个奇怪的程序(实验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

分析:

nop作用,在运行时在代码段分配一个字节的空间。(机器码90,在内存中就是90H),它的作用是方便在程序运行时代码段分配空间,在此写入代码(实际是机器码)。执行二次nop后,在cs段中分配了2个字节空间,内容都是90H。

 1)程序从start开始运行。

 2)连续两次nop分配了2个字节的空间。

 3)

mov di, offset s   //将s标号处的偏移地址赋值给di,di指向了s
mov si, offset s2  //将s2标号处的偏移地址赋值给si,si指向了s2
mov ax, cs:[si]    //将cs:si指向的内存单元的内容赋值给ax(内容为机器码)
mov cs:[di], ax	   //将机器码赋值给s标号处

cs:ip指向的内存单元的内容为机器码,并不是单纯的把整条语句搬过去。

也就是说我们此处是将s2处这条跳转指令的机器码赋给了s后面2个字节的位置,刚好是nop分配出来的空间。

s2处的机器码怎末来:8位位移是在编译时就决定的,所以s2的指令是跳转到s1处所以s2的机器码应该是jmp的机器码和8位位移。8位位移 = s1处的地址 - s2后第一个字节的地址 经计算结果位-10(向上偏移10个字节)通过补码化为转换成16进制(补码方式存储)00001010(源码10)» 11110101(取反)»11110110(加一)=F6H 。

最后加上jmp指令的机器码为 EBF6,(EB代表jmp指令,F6代表了自此偏移地址开始,向前偏移10个字节)。

所以s后面赋值的机器码为 EB F6刚好2个字节。

 4)执行s0处跳转到s处,此时s后面就不是两个空的字节了,而是EB F6,即指令是位移F6个位移,jmp short就是根据位移进行跳转的。所以和s2处一样向上位移10个字节,刚好是从 mov ax, 4c00H 开始执行,然后正常返回。

实验9 显示字符串

1.一个字为一组,由 字符(1个字节) + 颜色(1个字节)组成。 2.存放字符的地址:第一行:B8000~B809F,第二行:B80A0~B813F 第三行:B8140 ~ B81DF

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

data segment

	db 'welcome to masm!'
	db 2, 24H, 71H

data ends

stack segment 

	db 16 dup (0)

stack ends

code segment

	start:  mov ax, stack
			mov ss, ax
			mov sp, 0

			mov ax, data
			mov ds, ax

			mov ax, 0B800H
			mov es, ax

			mov si, 10H //颜色的偏移位置
			
			mov cx, 3  //3行循环3次

		s:  push cx
			mov bx, 0
			mov cx, 16  //遍历16次 
			mov di, 0
			mov ah, ds:[si] //颜色只放3次			

		s1: mov al, ds:[bx]

			mov es:[di], ax

			inc bx
			add di, 2
			loop s1

			inc si //下一个颜色
			pop cx //下一次循环
			
			//换行
			mov dx, es
			add dx, 0AH
			mov es, dx
			
			loop s

		s1: jmp short s1 //死循环

code ends

end start