Linux汇编之条件分支

汇编 2015年03月10日 ,

Linux汇编条件分支

本文中枫竹梦介绍Linux汇编语言(ASM)中的条件分支语句。和无条件分支不同,条件分支不总是被执行,其取决于EFLAGS寄存器的状态。条件分支与下面的5个标志有关:

  • 进位标志CF
  • 溢出标志OV
  • 奇偶标志PF
  • 符号标志SF
  • 零标志ZF

每个条件跳转指令都检查特定的标志位以便确定是否符号跳转的条件。

条件跳转指令

条件跳转指令根据EFLAGS寄存器的状态来确定是否进行跳转,不同的条件跳转指令使用不同的EFLAGS标志位,跳转指令格式如下:

jxx address

其中jxx是1个到3个字符的条件代码,address是要跳转的内存位置。

指令 描述(跳转条件) EFLAGS
JA 如果大于(Above) CF=0且ZF=0
JAE 如果大于(Above)或者等于(Equal) CF=0
JB 如果小于(Below) CF=1
JBE 如果小于(Below)或者等于(Equal) CF=1或ZF=1
JC 如果进位(Carry) CF=1
JCXZ 如果CX寄存器为0(Zero)
JECXZ 如果ECX寄存器为0(Zero)
JE 如果等于(Equal) ZF=1
JG 如果大于(Greater) ZF=0且SF=OF
JGE 如果大于(Greater)或者等于(Equal) SF=OF
JL 如果小于(Less) SF!=OF
JLE 如果小于(Less)或者等于(Equal) ZF=1或者SF!=OF
JNA 如果不大于(Above) CF=1或者ZF=1
JNAE 如果不大于(Above)或者不等于(Equal) CF=1
JNB 如果不小于(Below) CF=0
JNBE 如果不小于(Below)或者不等于(Equal) CF=0且ZF=0
JNC 如果无进位(Carry) CF=0
JNE 如果不等于(Equal) ZF=0
JNG 如果不大于(Greater) ZF=0或者SF!=OF
JNGE 如果不大于(Greater)或者不等于(Equal) SF!=OF
JNL 如果不小于(Below) SF=OF
JNLE 如果不小于(Below)或者不等于(Equal) ZF=0或者SF=OF
JNO 如果不溢出(Overflow) OF=0
JNP 如果不奇偶校验(Parity) PF=0
JNS 如果无符号(Sign) SF=0
JNZ 如果不为零(Zero) ZF=0
JO 如果溢出(Overflow) OF=1
JP 如果奇偶校验(Parity) PF=1
JPE 如果偶校验 PF=1
JPO 如果奇校验 PF=0
JS 如果带符号(Sign) SZ=1
JZ 如果为零(Zero) ZF=1

如果注意到很多的条件跳转指令似乎是多余的,如果是above大于,则使用JA;如果是greater大于,则使用JG。不同之处在于处理带符号数值与无符号数值的时候。如果是无符号数,则使用above和below关键字;如果是有符号数,则使用greater和less关键字。

条件跳转允许两种类型的跳转:

  • 短跳转
  • 近跳转

短跳转使用8位带符号地址偏移量,而近跳转使用16位或者32位带符号地址偏移量。偏移量直接回到指令指针中。

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

比较指令

比较指令是为进行条件跳转而比较两个值的最常见的途径。比较指令的作用比较两个值并设置EFLAGS寄存器。CMP指令的格式如下:

cmp operand1, operand2

CMP指令将使用operand2减去operand1,并不会改变两个操作数,同时设置EFLAGS寄存器。下面演示如何将比较指令和条件跳转指令一同使用:

# cmp.s - By furzoom @ Mar 7, 2015
.section .text
.globl _start
_start:
nop
movl $15, %eax
movl $10, %ebx
cmp %eax, %ebx
jge greater
movl $1, %eax
int $0x80
greater:
movl $20, %ebx
movl $1, %eax
int $0x80

该程序首先比较EAXEBX寄存器的值,根据结果使用JGE指令进行分支操作,由于EBX寄存器的值小于EAX寄存器的值,所有没有执行分支。运行该程序并查看返回值,如下:

[mn@furzoom asm]$ as -o cmp.o cmp.s
[mn@furzoom asm]$ ld -o cmp cmp.o
[mn@furzoom asm]$ ./cmp
[mn@furzoom asm]$ echo $?
10
[mn@furzoom asm]$

运行结果证明没有进行分支操作。CMP指令还可以如下要用方式:

cmp $20, $ebx
cmp data, %ebx
cmp (%edi), %ebx

使用标志位

在编写汇编语言的跳转指令是比较需要技巧的,必须要了解不同标志位满足不同跳转条件。下面几个例子就是这个目的。

零标志

进行条件跳转时,零标志的使用是最简单的。如果令标志被置1,JEJZ指令就跳转分支。零标志可以由CMP指令或者数学指令设置。如:

movl $30, %ebx
subl $30, %ebx
jz overthere

JZ指令将被执行,因为SUB指令的结果为零。也可以在递减寄存器时使用零标志,如:

movl $10, %edi
loopl:
< 其他指令 >
dec %edi
jz out
jmp loop1
out:

这个代码使用EDI寄存器作为变址计数器,它从10递减到1,当其为0时,JZ指令将退出循环。

溢出标志

溢出标志专门用在处理带符号数字时。当带符号值对于包含它的数据元素来说太大时,溢出标志被设置为1。如:

movl $1, %eax
movb $0x7f, %bl
addb $10, %bl
jo overhere
int $0x80
overhere:
movl $0, %ebx
int $0x80

该代码把带符号字节值127加上10。结果应该是137,这对于无符号的字节值来说是合法的。但是对带符号字节数来讲是非法的,带符号的字节数范围为-127到127。因为这个带符号值是非法的,所以溢出标志被置为1。

奇偶校验标志

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

# parity.s - By furzoom @ Mar 7, 2015
.section .text
.globl _start
_start:
movl $1, %eax
movl $4, %ebx
subl $3, %ebx
jp overhere
int $0x80
overhere:
movl $100, %ebx
int $0x80

在该代码中,减法的结果为1。因为为1的位数目为奇数,所以不设置奇偶校验位,JP指令不会跳转。程序退出,并应返回1。运行结果如下:

[mn@furzoom asm]$ as -o parity.o parity.s
[mn@furzoom asm]$ ld -o parity parity.o
[mn@furzoom asm]$ ./parity
[mn@furzoom asm]$ echo $?
1
[mn@furzoom asm]$

与分析的结果一致。

如果将减法指令改为如下:

subl $1, %ebx

减法结果为3,为1的数目为偶数,所以设置奇偶校验位,JP指令会跳转到overhere继续执行,返回结果应该为100。

[mn@furzoom asm]$ as -o parity.o parity.s
[mn@furzoom asm]$ ld -o parity parity.o
[mn@furzoom asm]$ ./parity
[mn@furzoom asm]$ echo $?
100
[mn@furzoom asm]$

与分析的结果一致。

符号标志

符号标志也使用在带符号数中,用于表示寄存器中包含的值的符号的情况。在带符号数中,最后一位(最高位)用作符号位,为1表示负数,为0表示正数。如果运算结果最高位为1,则符号标志置为1,否则置为0。

当在循环内进行计数并且监视零值时,该标志很有用。当值被递减到零时设置零标志。但是,如果正在处理数组,很可能需要在零值之后才停止,而不是在到达零值的位置时停止。如下示例演示对符号位的使用:

# sign.s - By furzoom @ Mar 7, 2015
.section .data
value:
.int 1, 2, 3, 4
output:
.asciz "The value is: %d\n"
.section .text
.globl _start
_start:
movl $3, %edi
loop:
pushl value(, %edi, 4)
pushl $output
call printf
addl $8, %esp
dec %edi
jns loop
movl $1, %eax
movl $0, %ebx
int $0x80

该程序反向遍历数组并输出值。使用EDI寄存器作为变址,处理第个数组元素时递减其值。使用JNS指令检查EDI寄存器的值什么时候变为负值,如果不是负值则跳转loop标签。输出如下:

[mn@furzoom asm]$ as -o sign.o sign.s
[mn@furzoom asm]$ ld -dynamic-linker /lib/ld-linux.so.2 -lc -o sign sign.o
[mn@furzoom asm]$ ./sign
The value is: 4
The value is: 3
The value is: 2
The value is: 1
[mn@furzoom asm]$

正如期望输出的那样。

进位标志

进位标志用在数学表达式中,表示无符号数中何时发生溢出,当指令导致寄存器超出其数据长度限制时设置进位标志。和溢出标志不同,DECINC指令不影响进位标志。如下并不会进行跳转:

movl $0xffffffff, %ebx
inc %ebx
jc overflow

但是下面这个代码会设置进位标志:

movl $0xffffffff, %ebx
addl $1, %ebx
jc overflow

当无符号值小于零时的运算也会设置进位标志:

movl $2, %eax
subl $4, %eax
jc overflow

和其他标志不同,有可以专门修改进行标志的指令。如:

  • CLC清除进位标志
  • CMC对进位标志取反
  • STC设置进位标志

这些指令都直接修改EFLAGS寄存器中的进位标志位。
(完)

如无特别说明,本站文章皆为原创,若要转载,务必请注明以下原文信息:
日志标题:《Linux汇编之条件分支》
日志链接:http://furzoom.com/linux-asm-conditional-branch/
博客名称:枫竹梦

【上一篇】
【下一篇】

发表评论

插入图片

NOTICE1:请申请gravatar头像,没有头像的评论可能不会被回复!

回到顶部