可以修改CS和IP的指令统称为转移指令,概括的讲,转移指令就是可以控制CPU执行内存中某处代码的指令.
一、分类
8086CPU的转移行为有以下几类:
- 只修改IP,称为段内转移。比如:JMP ax
- 同时修改CS和IP时,称为段间转移。比如:JMP 2000:0
由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移
- 短转移IP的修改范围为-128~127
- 近转移IP的修改范围为-32768~32767
8086CPU的转移指令分为以下几类:
- 无条件转移指令
- 条件转移指令
- 循环指令
- 过程
- 中断
二、JMP指令
JMP指令为无条件转移指令,可以只修改IP,也可以修改CS和IP,有以下几种用法:
1.JMP short 标号(转到标号处执行,段内短转移)
assume cs:code
stack segment stack
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,0
jmp short s ;
inc ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end start
容易看出,上述代表执行后ax寄存器的值为1,为了了解其工作原理,我们先看看代码段中的机器指令:
0B47:0000 B8370B MOV AX,0B37
0B47:0003 8ED0 MOV SS,AX
0B47:0005 BC0001 MOV SP,0100
0B47:0008 B80000 MOV AX,0000
0B47:000B EB01 JMP 000E
0B47:000D 40 INC AX
0B47:000E 40 INC AX
0B47:000F B8004C MOV AX,4C00
0B47:0012 CD21 INT 21
注意到JMP指令的汇编代码EB01,并不能看出EB01是什么意思,修改原文件:
assume cs:code
stack segment stack
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,0
jmp short s ;
inc ax
inc ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end start
0B47:0000 B8370B MOV AX,0B37
0B47:0003 8ED0 MOV SS,AX
0B47:0005 BC0001 MOV SP,0100
0B47:0008 B80000 MOV AX,0000
0B47:000B EB02 JMP 000F
0B47:000D 40 INC AX
0B47:000E 40 INC AX
0B47:000F 40 INC AX
0B47:0010 B8004C MOV AX,4C00
0B47:0013 CD21 INT 21
这时候的汇编指令是EB02,也就是说JMP short指令不存储标号的地址,只存储标号到当前指令的位移(字节,inc指令只占用一个字节),此时的EB02是指,该指令执行后,IP的值增加2
我们得出结论,JMP short 指令只存储相对位移,注意到02(EB02)只占用1个字节,也就是说JMP short能够跳转的位移是-128~127,是这样吗?我们增加跳转的位移
assume cs:code
stack segment stack
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,0
jmp short s ;
db 256 dup(0)
inc ax
inc ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end start
此时编译出错
1.asm(11): error A2053: Jump out of range by 131 byte(s)
50952 + 450712 Bytes symbol space free
0 Warning Errors
1 Severe Errors
如何解决这一问题呢?这时我们需要使用jmp near ptr 指令
2.JMP near ptr 标号(转到标号处执行,段内近转移),修改代码:
assume cs:code
stack segment stack
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,0
jmp near ptr s ;
db 256 dup(0)
inc ax
inc ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end start
机器指令如下:
0B47:0000 B8370B MOV AX,0B37
0B47:0003 8ED0 MOV SS,AX
0B47:0005 BC0001 MOV SP,0100
0B47:0008 B80000 MOV AX,0000
0B47:000B E90201 JMP 0110
注意E90201,8086CPU是小端模式这里的0201实际上是16进制数0102,也就是位移.
如果位移超处-32768~32767,这时就需要短间转移指令
3.JMP far ptr 标号(短间转移指令)
assume cs:code
stack segment stack
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,0
jmp far ptr s ;
db 256 dup(0)
inc ax
inc ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end start
机器指令:
0B47:0000 B8370B MOV AX,0B37
0B47:0003 8ED0 MOV SS,AX
0B47:0005 BC0001 MOV SP,0100
0B47:0008 B80000 MOV AX,0000
0B47:000B EA1201470B JMP 0B47:0112
这事JMP就是采用绝对地址了,注意小端模式
4 .JMP 标号(根据标号的位置,编译器会自动判断采用前面三种的某一种转移方式)
5 JMP word ptr 内存单元地址(直接修改IP,段内转移)
比如下面代码,执行后IP=0123H
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
6 JMP dword ptr 内存单元地址(直接修改CS和IP,段间转移)
比如下面代码,执行后IP=0123H,CS=0000H
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp word ptr ds:[0]
又比如下面程序能够正常返回
assume cs:code
data segment
dw 0,0,0,0
data ends
stk segment stack
db 256 dup(0)
stk ends
stack segment
mov ax,100
jmp dword ptr [bx]
stack ends
code segment
start:
mov ax,stk
mov ss,ax
mov bx,0
mov ax,data
mov ds,ax
mov ax,stack
mov [bx+6],ax
mov [bx+4],bx
mov ax,code
mov [bx+2],ax
mov ax,offset s
mov [bx],ax
jmp dword ptr [bx+4] ;
s: mov ax,4c00h
int 21h
code ends
end start
三、LOOP指令
loop指令为循环指令,所有循环指令都是短转移,loop 标号 等价于下面两条指令:
- cx=cx-1
- if(cx!=0)jmp short 标号
四、根据位移进行转移的意义
前面我们讲到:
- jmp short 标号
- jmp near ptr 标号
- loop 标号
这几个汇编指令,它们对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的,它们对应的机器码中不包含转移的目的地址,而包含的是目的地址的位移.
这种设计为了方便程序段在内存中的浮动装配,例如
mov cx,6
mov ax,10
s: add ax,ax
loop s
这段程序在内存中的任何位置都能正确执行,而如果loop跳转的是绝对地址,该程序只能在指定的内存地址中执行了
五、CALL和RET指令
- RET指令相当于如下指令:
POP IP
- RETF指令相当于如下指令:
POP IP
POP CS
- CALL 标号 指令相当于如下指令:
PUSH IP
JMP near ptr 标号
- CALL far ptr 标号 指令相当于如下指令:
PUSH CS
PUSH IP
JMP far ptr 标号
- CALL 16位寄存器 指令相当于如下指令(其他以此类推):
PUSH IP
JMP 16位寄存器
例如如下代码执行后AX为E
0B47:0008 B80000 MOV AX,0000
0B47:000B E80100 CALL 000F
0B47:000E 40 INC AX
0B47:000F 58 POP AX
例如下面程序执行后AX的值为3(不要用DEBUG调试,中断操作会影响栈的值)
assume cs:codesg
stack segment
dw 8 dup (0)
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax
mov ax,0
call word ptr ds:[0EH]
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
codesg ends
end start
注意:执行到 call word ptr ds:[0EH] 的时候 , 具体的流程如下 :
- CPU取指令 : (call word ptr ds:[0EH])
- ip自增上述指令的长度 , 指向了下一条指令 (inc ax)
- 开始执行该指令
- push ip ; 将 ip 压入栈 , 也就是 : ss:[0EH] 保存 ip 的低 8 位 , ss:[0FH] 保存高 8 位
- jmp ds:[0EH] ( (ip) = ds:[0EH] , 也就是说 , 程序又从 ds:[0EH] 中取出数据赋值给 ip , 然后继续执行 )
- 现在其实就开始执行 ip 之前保存的地址的指令了 , 也就是三个 inc ax
- 因此最终 ax 值为 3
例如下面程序执行后AX为1,BX为0(不要用DEBUG调试,中断操作会影响栈的值)
assume cs:code
stack segment
dw 8 dup(0)
stack ends
code segment
start:mov ax,stack
mov ss,ax
mov sp,16
mov word ptr ss:[0],offset s
mov ss:[2],cs
call dword ptr ss:[0]
nop
s:mov ax,offset s
sub ax,ss:[0ch]
mov bx,cs
sub bx,ss:[0eh]
mov ax,4c00h
int 21h
code ends
end start
请按照上个例子的思路分析