转移指令原理
可以修改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 s 和 cs: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