程序不可能一顺到底的执行,需要有一些分支流程控制的语法,对高级语言来讲,有分支循环等,对于汇编,有一个“跳”,或者选择性跳,跳转指令本身非常简单,仅仅一个jmp指令,类似于c语言的goto,语法为:
label:
...
jmp label
跳转分为段跳转(小于128字节),远跳转(分段模式下跨段跳转),近跳转(其他),不过这些在AT&T里编译器会根据参数的 变化而选择性的生成机器码,但对于MASM,需要自己指定,jmp near ptr label
, jmp far ptr label
。
但本质上讲,倘若只有这样的jmp,那不论如何跳都将是个死循环,所以便有了条件跳转(Jcond),在一定条件下进行跳转,这里所谓的条件,仍然是eflags的不同标记位,如下:
指令 | 跳转条件 | eflags标志 |
---|---|---|
JA | Jump if above | CF=0 & ZF=0 |
JAE | Jump if above or equal | CF=0 |
JB | Jump if below | CF=1 |
JBE | Jump if below or equal | CF=1 or ZF=1 |
JC | Jump if carry | CF=1 |
JCXZ | Jump if CX=0 | register CX=0 |
JE | (is the same as JZ) Jump if equal | ZF=1 |
JG | Jump if greater (signed) | ZF=0 & SF=OF |
JGE | Jump if greater or equal (signed) | SF=OF |
JL | Jump if less (signed) | SF != OF |
JLE | Jump if less or equal (signed) | ZF=1 or SF!=OF |
JMP | Unconditional Jump | - |
JNA | Jump if not above | CF=1 or ZF=1 |
JNAE | Jump if not above or equal | CF=1 |
JNB | Jump if not below | CF=0 |
JNBE | Jump if not below or equal | CF=1 & ZF=0 |
JNC | Jump if not carry | CF=0 |
JNE | Jump if not equal | ZF=0 |
JNG | Jump if not greater (signed) | ZF=1 or SF!=OF |
JNGE | Jump if not greater or equal (signed) | SF!=OF |
JNL | Jump if not less (signed) | SF=OF |
JNLE | Jump if not less or equal (signed) | ZF=0 & SF=OF |
JNO | Jump if not overflow (signed) | OF=0 |
JNP | Jump if no parity | PF=0 |
JNS | Jump if not signed (signed) | SF=0 |
JNZ | Jump if not zero | ZF=0 |
JO | Jump if overflow (signed) | OF=1 |
JP | Jump if parity | PF=1 |
JPE | Jump if parity even | PF=1 |
JPO | Jump if paity odd | PF=0 |
JS | Jump if signed (signed) | SF=1 |
JZ | Jump if zero | ZF=1 |
根据上次的规律很容易理解,这里我们举几个例子,把c语言的流程控制语句转化为汇编:
if (num > 10)
num ++;
int sum = 0;
for (int i = 0; i < 10; ++i)
sum += i;
对应的汇编语言写成:
.section .data
sum: .int 0
num: .int 20
.section .text
.globl _main
_main:
cmpl $10, num
jbe label1
incl num
label1:
movl $0, %eax
label2:
addl %eax, sum
incl %eax
cmp $10, %eax
jb label2
label3:
pushl $0
call _exit
编译使用gcc hello.s -g
,加上调试信息,之后使用gdb调试,这里直接使用br label3
在label3
处下断,然后print sum
打印信息查看结果。
这段程序开始 cmpl $10, num
,比较num和10,jbe跳转,相当于if (num > 10)
部分,incl把操作数加一。
对于for循环比较麻烦,这里把eax寄存器当做循环计数,先初始化eax为0,紧接着调用addl %eax, sum把eax加到sum上,再把eax加一,后面的比较就与if相同了,如果小于则跳转到label2。
汇编中还有一类分支结构,就是循环loop,有三种形式:
loop 循环到ecx为0,每次循环ecx减一
loope/loopz 循环到ecx为0或者没有设置ZF标志
loopne/loopnz 循环到ecx为0,或者设置了ZF标志
还记得在寄存器那里提到,ecx被用作循环计数器,全部体现在这里了。loop本质上讲每次先把ecx减一,如果不为零则跳转到label,通过实现上面的for循环,很容易理解loop的使用。
.386
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
sum dd 0
.code
start:
mov ecx, 9;
label1:
add sum, ecx
loop label1
invoke ExitProcess, NULL
end start
当然准确的从逻辑上讲其实这样是少一个循环的,不过最后一次加的是0,忽略掉吧。 注意这里loop是先把ecx减一后判断是否为0,如果ecx开始为0,则不会停止,ecx直接溢出,被减成负数。
在学习汇编时,可以多参阅编译器生成的汇编代码,用编译器gcc -S,便可生成汇编代码,也可使用不同的优化参数-O1 -O3优化生成的汇编,这样一来可以更好的学习汇编,而来可以理解c和汇编的对应关系,方便阅读反汇编代码。