汇编学习笔记(二):转移指令

章节目录

  1. 转移指令原理
  2. jmp 指令
  3. jcxz 指令
  4. loop 指令
  5. ret 和 retf 指令
  6. call 指令
  7. call+ret

作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢!

本文中所有程序均在DOSBox下使用MASM, LINK编译运行

转移指令原理

转移指令是可以控制 CPU 执行内存中某处代码的指令. 而 CPU 执行内存中哪处的指令是由 CS 和 IP 寄存器中的决定的. 因此, 转移指令是通过修改 CS 和 IP 寄存器的值来控制 CPU 对指令的执行的.

只修改 IP 的转移行为称为 段内转移.
同时修改 CS 和 IP 的转移行为称为 段间转移.

由于转移指令对 IP 的修改范围不同, 段内转移又分为: 短转移和近转移.
短转移: 对 IP 修改范围为 -128 ~ 127.
近转移: 对 IP 修改范围为 -32768 ~ 32767.

jmp 指令

jmp为无条件转移指令, 可以修改 IP 或者同时修改 CS 和 IP.

jmp short 标号

这条指令完成段内短转移. 对 IP 的修改范围为:-128 ~ 127. 执行完该指令之后, CS 的值不变, IP 指向标号在段中的偏移地址.

示例:

这里写图片描述

在上面程序中我们通过 jmp short s 将 IP 指向标号 s 的偏移地址. 当程序执行到 jmp 时, 会直接跳转到 s 处, 从 s 处继续往下执行.

jmp far ptr 标号

这条指令完成段间转移, 又称为远转移. 执行完该指令之后, CS 指向标号所在的段基址, IP 指向标号在段中的偏移地址.

示例:
这里写图片描述

在上面程序中我们通过 jmp far ptr s 将 CS 指向标号 s 的段基址, IP 指向标号 s 的偏移地址. 当程序执行到 jmp 时, 会直接跳转到 s 处, 从 s 处继续往下执行.

其他 jmp 指令

当然转移地址可以存储在寄存器或者内存中, 相应的语法如下:

jmp reg // 其中 reg 为 16位寄存器,其中存储这要跳转的偏移地址.

jmp word ptr 内存单元地址 // 段内转移, 内存单元存放目的偏移地址.
// 示例:
mov ax, 0123H
mov ds:[0], ax
jmp word ptr [ds]:[0] // IP = 0123H

jmp dword ptr 内存单元地址 // 段间转移, 内存单元两个字节,
//高地址处存放着转移的目的段基址, 低字节存放的是转移的目的偏移地址.
// 示例:
mov ax, 0123H
mov ds:[0], ax
mov word ptr ds:[2], 0
jmp word ptr [ds]:[0] // CS = 0, IP = 0123H

Note: 转移指令,不仅仅可以向后跳转, 亦可以向前跳转.

jcxz 指令

该指令为有条件转移指令. 所有有条件转移指令都是短转移.

指令格式: jcxz 标号(如果 CX = 0, 转移到标号处执行)

jcxz 的功能相当于: if (CX == 0) jmp short 标号
当 CX != 0 时, jcxz 什么也不做, 程序继续向下执行.

示例:

这里写图片描述

loop 指令

该指令在上一篇文章中已经涉及,示例可参考汇编语言笔记(一)

ret 和 retf 指令

ret 指令用栈中的数据, 修改 IP 的值, 实现近转移.
retf 指令用栈中的数据, 修改 CS 和 IP 的值, 实现远转移.

CPU 执行 ret 指令时, 进行如下两步操作:

IP = SS * 16 + SP
SP = SP + 2

相当于: pop IP

CPU 执行 retf 执行时, 进行如下四步操作:

IP = SS * 16 + SP
SP = SP + 2
CS = SS * 16 + SP
SP = SP + 2

相当于:
pop IP
pop CS

示例:

ret 用法
这里写图片描述

retf 用法:

这里写图片描述

上述程序中, ret/retf 执行执行后, IP = 0, CS:IP 指向代码段的第一条指令.

call 指令

CPU 执行 call 指令时, 执行以下两步操作:

将当前的 IP 或者 CS 和 IP 压入栈中
转移

call 指令不能实现短转移.

call 标号

CPU 执行此指令时, 执行如下操作:

(1) SP = SP - 2, SS * 16 + SP = IP
(2) IP = IP + 16 位位移

执行 call 标号 指令相当于执行:
push IP
jmp near ptr 标号

call far ptr 标号

CPU 执行此指令时, 执行如下操作:

(1) SP = SP - 2, SS * 16 + SP = CS
SP = SP - 2, SS * 16 + SP = IP
(2) CS = 标号所在段的段基址
IP = 标号在段中的偏移地址

执行 call far ptr 标号 指令相当于执行:
push CS
push IP
jmp far ptr 标号

其他 call 指令

当然转移地址可以存储在寄存器或者内存中, 相应的语法如下:

call reg

reg 为16位寄存器
相应 CPU 操作:
SP = SP - 2, SS * 16 + SP = IP
IP = reg 中的值
相当于:
push IP
jmp reg

call word ptr 内存单元地址

相当于:
push IP
jmp word ptr 内存单元地址
示例:
mov sp, 10H
mov ax, 0123H
mov ds:[0], ax
call word ptr ds:[0] // 执行后 IP = 0123H, SP=0EH

call dword ptr 内存单元地址

相当于:
push CS
push IP
jmp dword ptr 内存单元地址
示例:
mov sp, 10H
mov ax, 0123H
mov ds:[0], ax
mov word ptr ds:[2], 0
call word ptr ds:[0] // 执行后 CS=0, IP = 0123H, SP=0CH

call + ret

这里写图片描述

建议在继续往下看之前, 仔细阅读一下上图中代码, 分析程序结束时 bx 中的值是多少?

分析一下这个程序将非常有助于我们理解 call 和 ret 指令.

(1) 程序执行到 call s 时, IP 此时指向了 call s 的下一条指令 mov bx, ax (CS 和 IP 指向了 CPU 当前要读取指令的地址. 而当前已经执行到了 call s,因此下一条要读取的指令将是 mov bx, ax);
(2) CPU 执行 call s 时, 将当前 IP 值(指向 mov bx, ax 的偏移地址)压栈, 并将 IP 的值改变为标号 s 的偏移地址.
(3) CPU 从标号 s 处开始执行指令, loop 循环结束后, ax = 8
(4) CPU 将 ret 指令读入, IP 指向了 ret 指令后的内存单元(此时仅仅是读取指令,还未执行 ret 指令);
(5) CPU 执行 ret 指令, 从栈中弹出一个值(即 call s 指令先前压入的 mov bx, ax 指向的偏移地址), 将该值存入 IP 中. 此时 CS:IP 指向指令 mov bx, ax.
(6) 执行完 ret 指令之后, 程序将跳转到 mov bx, ax 处继续执行, 直至完成.
(7) 最终, bx = 8.

欢迎交流任何想法.

End…

1