汇编语言学习笔记(五)-控制流

程序不可能一顺到底的执行,需要有一些分支流程控制的语法,对高级语言来讲,有分支循环等,对于汇编,有一个“跳”,或者选择性跳,跳转指令本身非常简单,仅仅一个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 label3label3处下断,然后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和汇编的对应关系,方便阅读反汇编代码。

Built with Hugo
主题 StackJimmy 设计