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

原创 2015年07月09日 19:57:08

平时工作中有接触到汇编,一时兴起,就想看看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中。

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


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


相关文章推荐

C编译器剖析_6.2 汇编代码生成_寄存器的管理

在计算机中,CPU的速度比内存的速度快得多,编译器应尽量有效地利用寄存器资源,减少对内存的不必要访问,从而提高由编译器生成的汇编代码的运行速度。在中间代码生成阶段,UCC编译器用临时变量t来存放形如“...
  • SheIsC
  • SheIsC
  • 2015年04月26日 22:04
  • 729

C编译器剖析_6.1 汇编代码生成_简介

6.1 汇编代码生成简介     历经词法分析、语法分析、语义检查和中间代码生成阶段,我们终于来到了“目标代码生成阶段”,由于UCC编译器的目标代码即为32位x86汇编代码,因此我们就把本章称为“汇...
  • SheIsC
  • SheIsC
  • 2015年04月25日 19:24
  • 1050

IAR编译器生成Cortex M3汇编代码的相关问题

IAR如何查看C源代码生成的汇编指令:options->C/C++ Compiler ->List->Output list file & Output assembler file,生成的文件在工程...
  • qcopter
  • qcopter
  • 2016年05月20日 11:11
  • 1100

shellcode转换成汇编代码

方法1:提取ShellCode中的机器码---->把机器码粘贴到WinHEX并保存成exe文件---->用C32Asm反汇编---->得到反汇编码。 方法2:提取ShellCode中的机器码----...
  • SauceJ
  • SauceJ
  • 2014年09月16日 16:24
  • 1340

C编译器剖析_6.3.1 汇编代码生成_由中间指令产生汇编代码的主要流程

6.3.1  由中间指令产生汇编代码的主要流程     在这一小节,我们可把关注的焦点放在“如何把某条中间代码翻译成汇编代码”上。UCC编译器的中间代码是如下所示的四元式,包括运算符和3个操作数。 ...
  • SheIsC
  • SheIsC
  • 2015年04月29日 17:54
  • 1077

C编译器剖析_6.3.3 汇编代码生成_为跳转指令产生汇编代码

6.3.3        为跳转指令产生汇编代码     在这一小节中,我们要为“有条件跳转”、“无条件跳转”和“间接跳转”产生相应的汇编指令。中间指令的四元式如下所示:           ...
  • SheIsC
  • SheIsC
  • 2015年04月30日 17:08
  • 1081

C编译器剖析_6.3.2 汇编代码生成_为算术运算产生汇编代码

6.3.2  由EmitAssign函数产生算术运算的汇编代码    在这一小节中,我们要讨论的中间指令形如“t1: a+b;”或者“t2:&number”,这些指令用于进行一元或二元算术运算,并把运...
  • SheIsC
  • SheIsC
  • 2015年04月30日 13:22
  • 698

C编译器剖析_6.3.5 汇编代码生成_为类型转换产生汇编代码

6.3.5  为类型转换产生汇编代码     在这一小节中,我们来讨论一下整型和浮点型之间的类型转换。有些类型转换并不需要在汇编层次进行数据转换,例如int和unsigned  int之间的转换只是改...
  • SheIsC
  • SheIsC
  • 2015年05月01日 22:55
  • 892
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:看看GNU编译器都生成了什么样的汇编代码
举报原因:
原因补充:

(最多只允许输入30个字)