5. 转移指令的原理

187 阅读8分钟

转移指令的原理

可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。

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

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

由于转移执行对IP的修改范围不同,段内转移又分为:短转移和近转移。

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

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

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

1. 操作符offset

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

 assume cs:code
 ​
 code segment
   start: mov ax, offset start; 相当于 mov ax, 0
       s: mov ax, offset s;     相当于 mov ax, 3
 code ends
 ​
 end start

问题:有如下程序段,添加两条指令,使该程序在运行中将s处的一条指令复制到s0处

 assume cs:code
 ​
 code segment
   s:  mov ax, bx         ;占两个字节
       mov si, offset s
       mov di, offset s0
       _________________
       _________________
  s0:  nop                ;占一个字节
       nop
 code ends
 ​
 end

answer:

 mov ax, cs:[si]
 mov cs:[di], ax

2. jmp指令

jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。它转移的时候会给出以下两类信息(其一):

(1) 转移的目的地址

(2) 转移的距离(段间转移,段内短转移,段内近转移)

2.1 依据位移进行转移的jmp指令

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

这种格式的jmp指令实现的使段内短转移它对IP的修改范围为-128~127。jmp指令中的"shrot"符号,说明指令进行的是短转移。例子如下:

 assume cs:code
 ​
 code segment
   start: mov ax, 0
          jmp short s
          add ax, 1
       s: inc ax
 code ends
 ​
 end start

上述代码在执行完之后,ax的值为1。

image

我们可以观察上述汇编指令对应的机器指令,jmp指令实际上就是把程序的执行跳转到位于CS:0008内存单元的指令执行。但它的机器指令"EB03"并无目标内存单元的地址。那么程序又是如何跳转过去呢?我们再看个例子。

 assume cs:code
 ​
 code segment
  start: mov ax, 0
         jmp short s
         add ax, 1
         add ax, 2
      s: inc ax
 code ends
 ​
 end start

image

观察它的机器指令可知,这时候jmp指令的机器指令变成了"EB06",而一条"add"指令是三个字节,两条刚好是6个字节。说明"jmp short 标号"的机器指令中没有给出CPU要转移的目的地址,而是给出CPU要转移的位移,即当前的IP向后移动6个字节。

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

  1. 8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
  2. short指明此处的位移为8位位移。
  3. 8位位移的范围为-128~127。
  4. 8位位移由编译程序在编译时算出

还有一种类似指令:jmp near ptr 标号,它实现的是段内近转移。

"jmp near prt 标号"的功能为: (IP) = (IP) + 16位位移

  1. 8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
  2. near ptr指明此处的位移为16位位移,进行的是段内近转移。
  3. 16位位移的范围为-32768~32767,补码表示。
  4. 16位位移由编译程序再编译时算出

2.2 转移的目的地址在指令中的jmp指令

"jmp far ptr 标号"实现的是段间转移,功能如下:

(CS) = 标号所在段的段地址, (IP) = 标号在段中的偏移地址。

far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP

 assume cs:code
 ​
 code segment
   start: mov ax, 0
          mov bx, 0
          jmp far ptr s
          db 256 dup (0)
       s: add ax, 1
          inc ax
 code ends
 ​
 end start

image

可以看到要转移的目的地址为:076C:010B,而对应的机器指令为"EA0B016C07",说明高地址存放转移的段地址,低地址存放偏移地址

2.3 转移地址在内存中的jmp指令

转移地址在内存中的jmp指令有两种格式:

  • jmp word ptr 内存单元地址(段内转移)

功能:只修改IP

 mov ax, 0123H
 mov ds:[0], ax
 jmp word ptr ds:[0]

执行后,(IP) = 0123H

  • Jmp dword ptr 内存单元地址(段间转移)

同时修改CS和IP,高地址内存单元存放转移的目的段地址,低地址内存单元存放转移的目的偏移地址。

mov ax, 0123H
mov ds:[0], ax
mov word ptr ds:[2], 0
jmp dword ptr ds:[0]

若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应该定义哪些数据?

assume cs:code

data segment
  db 1, 0, 0, 2; 要填写的部分
data ends

code segment
  start: mov ax, data
         mov ds, ax
         mov bx, 0
         jmp word ptr [bx+1]

         mov ax, 4c00H
         int 21H
code ends

end start

解析:由于"jmp word ptr [bx+1]"是段内转移,只修改IP的内容。指向程序的第一条指令保持CS不变,IP为0即可。所以要求data段第二三个字节为0即可。

补全程序,是Jmp指令执行后,CS:IP指向程序的第一条指令。

assume cs:code

data segment
  dd 12345678H
data ends

code segment
  start: mov ax, data
         mov ds, ax
         mov bx, 0
         mov [bx], bx    ;要填写的部分
         mov [bx+2], cs  ;要填写的部分
         jmp dword ptr ds:[0]
code ends

end start

解析:需要注意这里是段间转移,注意高低地址的内容即可。"mov [bx] __ " 这里肯定是填0的,并且是一个字单元。由于前面没有使用"word ptr"指定,必须使用寄存器bx来送入数据。

2.4 转移地址在寄存器中的jmp指令

指令格式: jmp 16位reg

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

2.5 jcxz有条件转移指令

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

指令格式: jcxz 标号

操作: 当(cx) = 0 时, (IP) = (IP) + 8位位移

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

assume cs:code
 code segment
  start:mov ax,2000h
        mov ds,ax
        mov bx,0
      s:mov cl,[bx]   ;需要注意找的是字节,而cx寄存器是16位的
        mov ch,0
        jcxz ok
        inc bx
        jmp short s
      ok:mov dx,bx
         mov ax,4c00h
         int 21h
code ends
end start 

3. loop指令

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

指令格式: loop 标号

操作: (cx) = (cx) - 1

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

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


assume cs:code
 
code segment
 
    start:
        mov ax,2000h
        mov ds,ax
        mov bx,0
      s:mov cl,[bx]
        mov ch,0
        ________     
        inc bx
        loop s
     ok:dec bx            
        mov dx,bx
        mov ax,4c00h
        int 21h
 
code ends

答案是: inc cx,loop指令的操作是: cx--; if (cx == 0) jmp short 标号。所以先要加1,才能正常退出循环

实验八 分析一个奇怪的程序

考虑如下程序是否可以正确返回?

assume cs:code

code 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
code ends

end start

这个程序的关键就是要理解jmp short s1的机器指令中并没有包含转移的目的地址,而是偏移地址,且偏移-10个字节。当把这条指令复制到s处时,刚好可以定位到mov ax, 4c00H这条指令。所以程序也就可以正确返回。

实验9 根据材料编程

assume cs:code, ds:data, ss:stack
 
data segment
    db 'Welcome to masm!'
    db 2,36,113       ;字符属性代码(十进制)                                      
data ends
 
stack segment
    db 16 dup(0)
stack ends
 
code segment
start:
    mov ax,data
    mov ds,ax
    mov bx,0
    mov di,16
    
    mov ax,stack
    mov ss,ax
    mov sp,0
    
    mov ax,0b800h
    mov es,ax
    mov si,160*12+32*2;将字符输出到屏幕中央
                      ;(显示器可以显示25行,每行80个字符,每个字符两个字节)
    
    mov cx,3
s1:
    mov dh,ds:[di] ;高位放属性
    push cx
    mov cx,16
s0:
    mov dl,ds:[bx] ;低位放ASCⅡ码
    mov es:[si],dx ;放入缓冲区
    add si,2
    inc bx         ;下一个字符
    loop s0
    
    pop cx
    inc di
    add si,80h    ;移动到下一行
    sub bx,bx     ;字符串从头开始
    loop s1       ;循环3次,输出三行
    
    mov ax,4c00h
    int 21h
code ends
end start