源程序

int g(int x)
{
   		return x+5;
}

int f(int x)
{
   		return g(x);
}

int main(void)
{
   		return f(7)+2;
}

编译命令

gcc -S -o main.s main.c -m32
  • 将原程序编译成汇编指令,其中-m32是将其编译成32位下的汇编。
  • 将汇编文件中用于链接过程的代码删除,得到下面汇编代码

汇编代码

1 g:
2	pushl	%ebp
3	movl	%esp, %ebp
4	movl	8(%ebp), %eax
5	addl	$5, %eax
6	popl	%ebp
7	ret
8 f:
9	pushl	%ebp
10	movl	%esp, %ebp
11	subl	$4, %esp
12	movl	8(%ebp), %eax
13	movl	%eax, (%esp)
14	call	g
15	leave
16	ret
17 main:
18	pushl	%ebp 
19	movl	%esp, %ebp  
20	subl	$4, %esp   
21	movl	$7, (%esp)
22	call	f
23	addl	$2, %eax
24	leave
25	ret

分析汇编程序

  首先从main函数开始分析,18行将ebp推入栈中,19行中将esp的值赋给ebp(左边值赋给右边),20行开辟4个字节(32位)的栈空间给后面的局部变量7。函数传递传递参数的方式有三种:堆栈方式寄存器方式、以及通过全局变量进行隐含参数的传递。这里是利用堆栈传递参数,堆栈是一种“后进先出”的存储区,栈顶指针ESP指向堆栈中第一个可用的数据项。21行传入7,22行然后调用f函数,call指令可以看成是执行了

push %eip(保存23行指令地址)  
movl $9 %eip  

  此时的堆栈情况为:

  

  主要是保存23行的地址,并跳转到第9行执行。第9行开始又重新开辟了一段堆栈,12行取的是[ebp+8](栈地址是从高地址向低地址生长)的值,即7,然后把7赋给eax,13行把eax赋值给esp,这里实际上将7保存在当前的esp。14行调用g函数。call指令可以看成是执行了

pushl %eip (保存15行指令地址)  
movl $2 %eip 

  跳转到第2行执行,开辟一段栈空间,4行取[ebp+8]的值给eax,即7,5行执行7+5=12,把结果存在了eax。   此时的堆栈情况为:

  

  6行pop出当前的ebp,7行执行ret指令,实际上是:

popl %eip  
movl $15 %eip  

  跳转到15行执行leave指令,相当于:

movl %ebp %esp    
popl %ebp  

  此时f函数的栈空间已经被释放了,此时ebp指向main函数调用f函数之前的栈底,16行的ret使程序跳转到23行执行,eax保存着之前的12,此时再将eax+12=14。然后执行leave和ret指令释放main函数的栈空间。