【微机原理与接口】—— 常用指令分析 之 转移类指令分析

一、无条件转移指令(直接 & 间接)

我们知道,一般的指令是不允许操作 C S CS I P IP 的,因为它们一旦被允许操作了,执行代码的顺序就会变得不确定,但是相反,转移类指令就是要去操作 C S CS I P IP ,通过改变 C S CS I P IP 的值使得系统执行不同的代码。

那么转移类指令如果按照转移功能来讲,可以分为段内转移段间转移。如果还是以楼层比喻,CS 里面存放的就是楼层地址,IP 里面就是偏移。段内转移就是不用改变楼层 CS ,而只是改变 IP。段间转移就是在代码段之间转移,既是改变了 CS ,也改变了 IP。

如果按照转移条件分,可以分为无条件转移和有条件转移。

我们常常使用:JMP OPRD 表示无条件转移。其中,OPRD 一定表示的是下一条将要执行的指令的地址。可以与 JMP 指令在同一个段内,也可以不在同一个段内。

1.1 无条件段内转移指令

如下图所示,假设我们有这样一个指令:JMP Label
那么,我们知道,Label这个符号代表下一条将要执行的指令的地址。如果这个 Label 是 16 位的,那么就表示是段内转移。

另外,无条件的段内转移我们也可以分为:无条件段内直接转移 和 无条件段内间接转移。我们刚刚不是说了吗,Label 代表的其实是下一条代码的地址,那么所谓直接转移,就是我们在代码里面直接给出 Label (也就是地址)。

如果我们是通过指令里面的寄存器或者存储器操作数给出的目标地址,就称为间接转移。例如:
我们先看看段内间接转移的第一种表现形式:

MOV BX, 1200H
JMP BX    //运行完了之后 IP = 1200H,这里 BX 是寄存器操作数

我们在看看另外一种表现形式:

MOV BX, 1200   //注意:这里不是 H 
JMP WORD PTR [BX]  //这样一来,我们把数据段里面 标号为 1200和 1201 的两个单元分别赋给 IP的低位和高位

那么,对于这种段内转移,我们首先找到 Label 所指向的地址,将 Label 的地址与 JMP 的地址相减,得到位移量。因此,就会修改 IP \to IP + 位移量,得到指示下一条指令的 IP

1.2 无条件段间转移指令

还是以上面的为例,如果我们的 Label 符号是 32 位的地址,那么就表示段间转移。

同样,段间转移也分为了 段间直接转移和段间间接转移。段间直接转移就是我们在指令里面直接给出 32 位目标地址(CS:IP),段间间接转移就是需要通过 32 位的存储器操作数(注意不能是寄存器了)给出目标地址。

对于段间直接转移,指令格式是:JMP FAR Label(FAR 代表远地址)

那么对于段间间接转移,我们可以用下面的表述:

JMP DWORD PTR[BX]    //[BX]指向的是存储器操作数

下面这幅图很好地说明了这个指令的意思:

1.2 条件转移指令

  1. 根据单个条件标志的设置情况转移
    在这里插入图片描述
    这种转移指令常常用于适用于测试某一次运算的结果并根据其不同特征产生程序分支不同的处理的情况

  2. 比较两个无符号数,并根据比较的结果转移
    在这里插入图片描述

  3. 比较两个带符号数,并根据比较的结果转移
    在这里插入图片描述

二、循环转移指令

循环控制指令有几个要点:

  1. 循环范围:以当前 IP 为中心的 -128~ +127的范围内循环
  2. 循环次数:循环的次数必须要由 CX 寄存器指定。
  3. 循环指令也分为无条件循环 LOOP 和 条件循环指令 LOOPZ/ LOOPNZ

2.1 无条件循环指令 LOOP

格式:LOOP Label
循环的条件是:当 CX ≠ 0时。

2.2 条件循环指令

功能:先使得 CX -1,再根据 CX 的值以及 ZF 的值去决定是否循环。

  1. LOOPZ(相等则循环):当 CX ≠ 0,且 ZF = 1时循环
  2. LOOPNZ(不相等则循环):当 CX ≠ 0,且 ZF = 0时循环

因此,条件循环指令前面需要跟能够改变 ZF 状态的指令,用以控制循环

三、过程调用指令

过程调用,就是类似于我们在 C++ 里面的主程序中调用子函数一样。那么话说回来,我们在刚刚所讨论的转移类指令,是程序运行到某个地方之后,就跳转到另外一段代码,不会再回到原处了。但是对于过程调用指令,我们跳转到子程序运行完了之后是需要回到原来的地方继续执行主程序的。用下面的图说明一下:

注意:我们执行完子程序返回的位置是原来跳转位置的后一条指令!

因此,既然需要返回原来的位置,也就不能忘了初心。过程调用指令需要保护返回地址(也就是 段基地址 和 偏移地址)。即我们需要把 CS 和 IP 里面的内容压栈。

过程调用指令我们一般用 CALL 表示。CALL 后面也是直接或间接地给出子程序的入口地址。
同样,CALL 指令 也分为了段内调用和段间调用。

3.1 段内直接/间接调用

【1】直接调用就是在指令里面直接给出地址的标号:CALL TIMER (此时的 TIMER 是 16 位地址
那么,使用时,系统会自动将现在代码的 偏移地址压栈。

【2】间接调用:就是我们通过寄存器或者存储器操作数给出偏移地址:
CALL WORD PTR[SI]
因为 SI 的数据默认在数据段 DS 里面,又因为指定了是 WORD,所以在 DS 中以 SI为偏移的两个内存单元的内容将会被赋值给 IP 。

3.2 段间直接/间接调用

【1】直接调用时,我们在标号前面需要加上 FAR:CALL FAR TIMER (此时的 TIMER 是 32 位地址
那么,使用时,系统会自动将现在代码的 段基地址 和偏移地址同时压入堆栈(先压入CS,后压入IP)

【2】间接调用:CALL DWORD PTR[SI]
这就是将 DS 段内,SI 为偏移地址的4个单元作为地址,其中低地址单元赋值给 IP,高地址单元赋值给 CS。

值得注意的是:所有子程序最后都需要跟一个返回指令:RET。无操作数。用于在执行完子程序之后系统从堆栈弹出原来保存的 CS,IP。

发布了140 篇原创文章 · 获赞 411 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_44586473/article/details/105098719