平时工作中有接触到汇编,一时兴起,就想看看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;
}
如下是注释了的汇编代码,编译器生成的汇编代码是没有注释的。
.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中。
局部变量也是通过栈来实现的。
文章写得简略。若有不明之处。还请见谅。