曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 6372|回复: 2
打印 上一主题 下一主题

控制程序流程--条件分支

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
跳转到指定楼层
楼主
发表于 2009-12-8 13:02:16 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
和无条件分支不同,条件分支不总是被执行。条件分支的结果取决于执行分支时 EFLAGS 寄存器的状态。

EFLAGS 寄存器中有很多位,但是条件分之只和其中 5 位有关:
      进位 (Carry) 标志 (CF) --- 第 0 位 (借位有效位)
      溢出 (Overfow) 标志 (OF) --- 第 11 位
      奇偶校验 (Parity) 标志 (PF) --- 第 2 位
      符号 (Sign) 标志 (SF) --- 第 7 位
      零 (Zero) 标志 (ZF) --- 第 6 位
每个跳转指令都检查特定的标志位以便确定是否符合进行跳转的条件。使用这 5 个不同的标志位,可以执行几种跳转组合。下表是所有可用的条件跳转指令:
指令
描述
EFLAGS
JA
如果大于(above),则跳转
CF=0与ZF=0
JAE
如果大于(above)或等于,则跳转
CF=0
JB
如果小于(below),则跳转
CF=1
JBE
如果小于(below)或等于,则跳转
CF=1或ZF=1
JC
如果进位,则跳转
CF=1
JCXZ
如果CX寄存器为0,则跳转

JECXZ
如果ECX寄存器为0,则跳转

JE
如果相等,则跳转
ZF=1
JG
如果大于(greater),则跳转
ZF=0或SF=OF
JGE
如果大于(greater)或等于,则跳转
SF=OF
JL
如果小于(less),则跳转
SF<>OF
JLE
如果小于(less)或等于,则跳转
ZF=1或SF<>OF
JNA
如果不大于(above),则跳转
CF=1或ZF=1
JNAE
如果不大于(above)或等于,则跳转
CF=1
JNB
如果不小于(below),则跳转
CF=0
JNBE
如果不小于(below)或等于,则跳转
CF=0或ZF=0
JNC
如果无进位,则跳转
CF=0
JNE
如果不等于,则跳转
ZF=0
JNG
如果不大于(greater),则跳转
ZF=1或SF<>OF
JNGE
如果不大于(greater)或等于
SF<>OF
JNL
如果不小于(less),则跳转
SF=OF
JNLE
如果不小于(less)或等于,则跳转
ZF=0与SF=OF
JNO
如果不溢出,则跳转
OF=0
JNP
如果不奇偶校验,则跳转
PF=0
JNS
如果无符号,则跳转
SF=0
JNZ
如果非零,则跳转
ZF=0
JO
如果溢出,则跳转
OF=1
JP
如果奇偶校验,则跳转
PF=1
JPE
如果偶校验,则跳转
PF=1
JPO
如果计校验,则跳转
PF=0
JS
如果带符号,则跳转
SF=1
JZ
如果为零,则跳转
ZF=1

above 大于 JA 跳转 和 greater JG 跳转的区别在于前者是处理无符号值的,后者是处理带符号值的。

条件跳转允许两种跳转类型:短跳转 和 近跳转。
短跳转使用 8 位带符号地址偏移量,而近跳转使用 16 位或者 32 位带符号地址偏移量。偏移量值被加到指令指针(EIP)上。

条件跳转指令不支持分段内存模式下的远跳转。如果在分段内存模式下进行程序设计,就必须使用程序设计逻辑确定的条件是否存在,然后再用无条件跳转(JMP)转移到另一个段中的指令上。

为了能够使用条件跳转,在进行跳转之前,必须进行 EFLAGS 寄存器的相关操作。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
沙发
 楼主| 发表于 2009-12-11 22:24:59 | 只看该作者

比较指令

比较指令是为进行条件跳转而比较两个值的最常见的途径。它比较两个值,并且相应底设置 EFLAGS 寄存器。

CMP 命令格式如下:
cmp operand1,  operand2
CMP 指令把第 2 个操作数和第 1 个操作数进行比较。在幕后,其实对两数进行减法操作 (operand2 - operand1),比较指令不会修改这两个数,但在发生减法操作时会修改 EFLAGS 寄存器。

注意:使用 GNU 汇编时,在 CMP 指令中, operand1 和 operand2 的顺序与 Intel 文档中的顺序是相反的!


测试程序
# cmptest.s - An example of using the CMP and JGE instructionS

.section .text
.global _start
_start:
    nop
    movl $10, %eax
    movl $15, %ebx
    cmp %eax, %ebx
    jge greater
    movl $1, %eax
    int $0x80
greater:
    movl $20, %ebx
    movl $1, %eax
    int $0x80
运行后,执行以下命令:
$ echo $?
20
说明
因为 EBX 中的 15 减去 EAX 中的 10 > 0 ,所以程序跳转。
$?    表示显示最后命令的退出状态,这里的这个值(20)由 EBX 寄存器带出。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
板凳
 楼主| 发表于 2009-12-13 12:07:34 | 只看该作者

使用标志位

如果希望程序按照结果正确跳转,那么需要了解标志位的相关状态。

1、使用零标志
如果零标志被置1 (两个操作数相等),JE 和 JZ 指令就跳转到分支。零标志可以由 CMP 指令设置,也可以由计算结果为零的数学指令设置。如:
movl $30, %eax
subl $30, %eax
jz  overthere
SUB 相减结果为 0,JZ 指令被执行。

递减寄存器,当寄存器中的值为 0 时,零标志也会置位:
movl $10, %edi
loop1:
< other code instructions >
dec %edi
jz out
jmp loop1
out:
当 EDI 寄存器中的值由 10 减到 1 时,JZ 指令执行,程序退出循环。

2、使用溢出标志
溢出标志专门用在处理带符号数字时。当带符号值对于包含它的数据元素来说太大时,溢出标志被设置为 1 。这经常发生在溢出了保存数据的寄存器长度的数学操作的过程中,如下所示:
.section .text
.global _start

_start:
    nop
    movl $1, %eax    #move 1 to the EAX register
    movb $0x7f, %bl #move the signed value 127 to the 8-bit BL register
    addb $10, %bl   #add 10 to the BL register
    jo   overthere
    int  $0x80
overthere:
    movl $0, %ebx    #move 0 to the EBX register
    int  $0x80
调试时,在执行 addb $10, %bl 后,查看标志位寄存器可以看到
eflags         0xa92    [ AF SF IF OF ]
可见,发生了溢出。这个程序中,把带符号字节值 127 加上 10,结果应该是 137,这对于字节来说是合法值,但对带符号字节数则是非法的 (带符号字节数只能使用 -127 到 127 的值)。因为这个带符号值非法,所以设置溢出标志为 1,并且执行 JO 指令。

3、使用奇偶校验标志
奇偶校验标志表明数学运算答案中应该为 1 的位的数目。可以使用它作为粗略的错误检查系统,确保数学操作成功执行。
如果结果中被设置为 1 的位的树木是偶数,则设置奇偶校验位 (置1);如果设置为 1 的位的数目是奇数,则不设置奇偶校验位 (置0)。

测试程序(奇偶校验标志位不设置情况):
.section .text
.global _start
_start:
    movl $1, %eax
    movl $4, %ebx
    subl $3, %ebx
    jp  overthere
    int $0x80

overthere:
    movl $100, %ebx
    int  $0x80
运行输出:
$ ./paritytest
$ echo $?
1
由此可以看到,程序并没有跳转,这是因为计算结果为 1,以二进制表示是 00000001 。因为 1 的位数是奇数,所以不设置奇偶校验位,JP 指令不会跳转到分支,程序退出。

测试相反情况,改变 SUB 指令这一行,得到的结果为 3,二进制表示为 00000011 。这时因为 1 的位数是偶数,所以设置了奇偶校验位,指令 JP 发生跳转来到 overthere 标签分支,并最后退出时带处的结果代码为 100:
$ ./paritytest
$ echo $?
100

4、使用符号标志
符号标志使用在带符号数中,用于表示寄存器中包含的值的符号改变。在带符号数中,最后一位 (最高位)用作符号位。它表明数字表示的是负数(设置为1)还是整数(设置为0).
当在循环内进行计数并且监视零值时,这个标志很有用。
在循环计数中,在寄存器中的值递减为 0 时,零标志位被设立。但是,如果正在处理的是数组,很可能也需要在零值后才停止,而不是在到达零值的位置时停止(因为数组的第一个下标为0)。使用符号标志,可以得到值从 0 到 -1 的变化通知,如下测试程序所示:
.section .text
.global _start
_start:
    movl $1, %eax
    movl $4, %ebx
    subl $3, %ebx
    jp  overthere
    int $0x80

overthere:
    movl $100, %ebx
    int  $0x80
运行与输出:
$ ./signtest
The value is: 2
The value is: 10
The value is: 80
The value is: 32
The value is: 50
The value is: 6
The value is: 11
The value is: 34
The value is: 15
The value is: 21
程序反向遍历数据数组,使用 EDI 寄存器作为变址,处理每个数组元素时递减这个寄存器。使用 JNS 指令检查 EDI 寄存器的值什么时候变成负值,如果不是负值,则返回到循环开头。

5、使用进位标志
进位标志用在数学表达式中,表示无符号数何时发生溢出(注意,带符号数使用溢出标志)。当指令导致寄存器超出其数据长度限制时设置进位标志。
和溢出标志不同,DEC 和 INC 指令不影响进位标志。例如,下面代码片段不会设置标志位:
movl $0xffffffff, %ebx
inc %ebx
jc overflow
但是,下面的这个代码片段会设置进位标志,并且 JC 指令会跳转到 overflow 的位置:
movl $0xffffffff, %ebx
addl $1, %ebx
jc overflow
无符号值小于零时也会设置进位标志,如:
movl $2, %eax
subl $4, %eax
jc  overflow
EAX 寄存器中的结果值是 254,作为带符号数,它代表 -2 ,即正确的答案。这就是说不会设置溢出标志。但是,对于无符号数来说,答案小于零,所以设置进位标志。

和其他标志不同,有可以专门修改进位标志的指令,如下表所示:
指令
描述
CLC
清空进位标志(设置它为零)
CMC
对进位标志求反(把它改变为相反的值)
STC
设置进位标志(设置它为1)
上面几个指令都直接修改 EFLAGS 寄存器中的进位标志位。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-29 05:08 , Processed in 0.090026 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表