章节目录
- 转移指令原理
- jmp 指令
- jcxz 指令
- loop 指令
- ret 和 retf 指令
- call 指令
- 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