看看GNU编译器都生成了什么样的汇编代码

平时工作中有接触到汇编,一时兴起,就想看看GNU的编译器生成的汇编代码是什么样的。

1. 生成汇编代码

我写了一个非常简单的C语言代码,如下

int add(int a, int b);
int max(int a, int b);

int a = 3;
int b = 5;

int main()
{
	int result = 0;

	result = add(a, b);
	result = max(a, b);

	return 0;
}

int add(int a, int b)
{
	return a + b;
}

int max(int a, int b)
{
	return a > b ? a : b;
}
然后,执行“gcc -S simple_program.s simple_program.c”生成汇编代码(simple_program.s)。

如下是注释了的汇编代码,编译器生成的汇编代码是没有注释的。

.file	"simple_program.c"  # 没有特别的含义,说明我们正要开始一个新的文件
.globl a                    # 声明一个全局变量a
	.data                   # 声明一个.data数据段
	.align 4                # .data段中的数据4字节对齐
	.type	a, @object      # 告诉汇编器a是一个数据对象
	.size	a, 4            # 变量a的大小为4个字节
a:                          # label
	.long	3               # long型
.globl b                    # 如上
	.align 4
	.type	b, @object
	.size	b, 4
b:
	.long	5
	.text                   # 声明一个代码段
.globl main
	.type	main, @function     # 告诉汇编器main是一个函数名
main:                           # main函数起始处  
	pushl	%ebp                # 将EBP压栈,用于保存ESP栈指针
	movl	%esp, %ebp          # 将ESP保存于EBP中
	andl	$-16, %esp          # -16,0xfffffff0,调整栈指针为16字节对齐
	subl	$32, %esp           # 栈指针下移,留出32个字节,用于定义局部变量
	movl	$0, 28(%esp)        # 在esp+28地址处定义一个局部变量,也就是result
	movl	b, %edx             # 将全局变量a的值移至寄存器EDX中 
	movl	a, %eax             # 将全局变量b的值移至寄存器EAX中
	movl	%edx, 4(%esp)       # 函数调用时,函数参数是通过栈来传递的
	movl	%eax, (%esp)        # 函数参数a,b保存到栈中
	call	add                 # 调用add函数
	movl	%eax, 28(%esp)      # 函数返回值保存在寄存器EAX中,result = add()
	movl	b, %edx             # 如上
	movl	a, %eax             # 如上
	movl	%edx, 4(%esp)       # 如上
	movl	%eax, (%esp)        # 如上
	call	max                 # 如上
	movl	%eax, 28(%esp)      # 如上
	movl	$0, %eax            # main函数返回值
	leave                       # LEAVE instruction equal to
	                            # movl  %ebp,  %esp
	                            # popl  %ebp
	ret                         # return
	.size	main, .-main        # 用于设定函数符号的大小
.globl add                      # 声明add是一个全局变量
	.type	add, @function      # 告诉汇编器add是一个函数名
add:                            # add函数起始处
	pushl	%ebp                # 将EBP压栈,用于保存ESP栈指针              
	movl	%esp, %ebp          # 将ESP保存于EBP中
	movl	12(%ebp), %eax      # EAX = a
	movl	8(%ebp), %edx       # EDX = b
	leal	(%edx,%eax), %eax   # EAX = EAX + EDX
	popl	%ebp                # 从栈中还原EBP寄存器的值
	ret                         # 函数返回,返回值在EAX中
	.size	add, .-add
.globl max
	.type	max, @function
max:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %edx       # EDX = b
	movl	12(%ebp), %eax      # EAX = a
	cmpl	%edx, %eax          # 比较a与b的大小
	jge	.L6                     # if a >= b, 略过下一行语句,跳转到.L6处
	movl	%edx, %eax          # if !(a >= b), a = b
.L6:
	popl	%ebp                # 从栈中还原EBP寄存器的值
	ret                         # 函数返回
	.size	max, .-max
	.ident	"GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
	.section	.note.GNU-stack,"",@progbits

2. 汇编的知识,再说几点

在汇编代码中,我增加了不少注释。有些问题,我觉得光靠注释是说不清楚的,这里简明交待两点。

2.1 汇编程序中的segment问题

在汇编程序中,不同的数据是放在不同的段中的。

代码是放在代码段中的,就是.text段中。

数据是放在数据段中的,数据段有.data段和.bss段之分。赋了初值的全局变量放在.data段中,没有赋初值的全局变量放在.bss段中。

2.2汇编程序中的函数调用问题

汇编代码是由GNU编译器自动生成的。虽然是自动的,肯定有一个约定成俗的规范在起作用,要么岂不乱套了。

函数调用的规范,或许你从汇编代码中已经看出了一点端倪。

先说一下函数参数。函数参数是通过栈空间来传递的。

在调用函数之前,先将函数参数入栈,进栈的顺序依次为,函数参数n,函数参数n-1, ........函数参数1。

在汇编程序执行call function_name后,汇编程序会自行将函数的返回地址,也就是call function_name的下一行的指令地址压入栈中。

在汇编函数中,为了保证返回地址不被意外的更改,将EBP压入栈中,专门用EBP保存ESP的值。

这样,在汇编函数中,函数参数1就保存在ESP+8的位置处,函数参数2保存在ESP+12的位置处,依次类推。ESP+0保存的是EBP的值,ESP+4保存的是函数的返回地址。

汇编函数的返回值入在寄存器EAX中。

局部变量也是通过栈来实现的。


文章写得简略。若有不明之处。还请见谅。


阅读更多
个人分类: 汇编 编译 链接
上一篇请尽量不要为全局变量赋不必要的初值!
下一篇看看编译器是怎样用乘法代替除法的
博主设置当前文章不允许评论。

gnu as 汇编手册及翻译

2009年03月03日 336KB 下载

gnu as 汇编手册

2015年05月01日 336KB 下载

没有更多推荐了,返回首页

关闭
关闭