欢迎光临散文网 会员登陆 & 注册

Intel CPU中的条件转移指令

2023-08-05 22:20 作者:Login255  | 我要投稿

概述

毫无疑问,Intel CPU功能越来越强大,指令越来越复杂。

从Intel官网可以免费下载其全套CPU手册。这套手册是Intel汇编最权威的资料,而且可读性非常高,对于学习汇编语言,了解CPU工作原理来说,都是最有价值的资料,没有之一。

本文以《Intel 64 and IA-32 Architectures Software Developers Manual Volume 2 - Instruction Set Reference》为底稿,整理总结一下各种条件转移指令。

定义

条件转移指令,常见的有je,jnz,jcxz,jle等,是满足条件时进行跳转的指令。

手册中列出的条件转移指令总计有95条,记忆很不方便。

CPU的无记忆特性

CPU是没有记忆的,它只知道按指令功能严格执行完当前指令,然后继续执行下一条指令。假设某一种简单CPU只有四条指令,分别是向上、向下、向左、向右。那么下图左侧的程序运行起来以后,便可以得到右侧的运行结果。生成一条从A到B的路径。但任何一个时刻,你问CPU上一步从哪里来,上一条指令是什么?上一步寄存器内容是多少?它全然不知。

虚拟程序

条件判断依据?

典型的条件转移指令上下文如下(红色为标号,可以忽略):

L1:  mov eax, 0xFF

L2:  cmp eax, dword ptr [ebp-20h]

L3:  jne error

L4:  jmp continue

error: return

continue: ......

上面的程序中,L1处的指令将0xFF传送到寄存器eax,L2处的指令对eax的值和[epb-20]所指向内存中的双子进行比较,L3处的指令jne意思是:如果不相等,就跳转到error标号处(jump if not equal)。不过,问题来了:

前面说过,CPU没有历史记忆,CPU在执行条件转移指令时,并不知道其上一条指令到底是相减、相加还是相乘或按位与操作,也不知道eax中到底是有符号数还是无符号数,比如,把cmp换成test或者add,程序依旧可以顺利通过编译并顺利运行:

test eax, dword ptr [ebp-20h]

add eax, dword ptr [ebp-20h]

但这种情况下,我们再将jne理解为两数不等,就完全不合乎实际程序逻辑了。

事实上,条件转移指令执行时,并不依赖于上一条指令,仅依据当前EFLAGS寄存器的值进行是否转移的判定。

关于EFLAGS寄存器

对于IA-32架构CPU,其内部的EFLAGS寄存器布局如下图所示:

CPU中的标志与控制寄存器EFLAGS

影响EFLAGS寄存器的因素有很多,可以通过指令,如:LAHF, SAHF, PUSHF, PUSHFD, POPF, POPFD, CLI, STD等,任务切换时EFLAGS被保存在TSS中,任务恢复时EFLAGS也被恢复;中断和异常发生时,EFLAGS也需要保存和恢复。当然,这些听不懂也没关系。

和条件跳转指令紧密相关的是被称为状态标志的6个标志,分别是CF/PF/AF/ZF/SF/OF

这些标志位都会受到数学运算指令的影响,如加减乘除、移位、位运算等等。其中最难区分的是CF和OF,两个标志都和溢出有关。

从上面的英文画线部分看,CF标志是针对unsigned integer运算,而OF是针对signed运算,不过实际上远没有这么简单。比如mul是无符号乘法指令,但其结果会同时影响OF和CF两个标志,具体细节是:

比如,两个十六位数进行MUL乘法运算,结果保存到dx:ax寄存器对中。如果dx寄存器是0,则OF和CF均为0,否则都为1,而ZF,AF,PF则属于未定义。针对IMUL有符号数乘法,影响还要更复杂些。所以具体情况一定要查手册才能确定。

不过,条件转移指令的前一条指令,最常见的就是减法SUB,比较CMP和测试TEST,其中cmp和sub的唯一区别在于:cmp相减的结果不保存,只影响标志位。另外,cmp和sub均同时测试有符号数和无符号数,因为如果部考虑最高位的溢出,有符号和无符号相减运算的结果是相同的。举例:

假设:ax = 0x80F0,bx = 0x8F00,那么sub ax, bx对EFLAGS的影响就是:

CF = 1, ZF = 0, SF = 1, OF=0,具体分析如下:

1)假设ax和bx中存储的都是无符号数,则有:0x80F0可转换成十进制33008,0x8F00转换成十进制是36608,那么无符号减法 33008-36608 = -3600,转换成十六进制为F1F0,做减法运算过程中需要最高位借位。

2) 假设ax和bx中存储的都是有符号数,那么0x80F0对应十进制-32528,而0x8F00对应十进制-28928,则-32528 - (-289282) = -3600,而且数据不超过十六位二进制可以容纳的范围,未发生溢出,所以OF = 0

通过以上分析我们知道,对于CPU来说,SUB指令无需区分操作数是否有符号,只需按无符号数相减,如果最高位有借位,就置位CY,否则CY=0。然后再按有符号计算一下结果是否溢出,相应置位或复位OF。sub和cmp运算,根据结果会影响OF, SF, ZF, AF, PF, CF标志。

对于test指令来说,它使两个操作数按位进行与运算,影响SF, ZF, PF标志位,且OF=0, CF=0,运算结果不存储。


Intel CPU的条件转移指令速记

95个有条件转移指令,想要记住很难,即便自己不写,读别人程序总归需要理解正确才行。

其实,很多条件转移指令可以望文生义的,如:

jnz = jump not ZF(如果ZF不为1则转移)

jle = jump if less or equal (小于或等于转移)

jb = jump if below (小于则转移)

不过正如前文所述,CPU是没有记忆能力的,这里的所谓“等于”,“小于”等,都是基于前一条影响标志位的指令是sub或cmp的假设,如果不是,则根本就风马牛不相及了。何况below和less是否有区别也不清楚。

正因为Jxx类条件转移指令只根据EFLAGS当前值做出是否转移的决定,实际上还是需要熟记je是判断那个标志位,jl是判断哪个标志位。根据Intel手册,有如下主要规律:

这里,SF与运算结果最高位相同,对于有符号数来说,SF=1则是负数,否则是正数;而OF则代表是否存在溢出,也就是目的操作数能否装下完整运算结果。结合这两个因素仔细一想,上面通过SF和OF是否相等来判断两个有符号数大小的方法真的很巧妙。

只有明白了每条指令对标志位的影响,再明白了每一条条件转移指令到底是根据哪个标志位或标志位组合来判断,才算是真正搞懂了条件转移。

最后,将所有条件转移指令列表如下:


Intel CPU中的条件转移指令的评论 (共 条)

分享到微博请遵守国家法律