C语言之函数调用及栈帧分析

Posted 可爱的乐乐哥哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言之函数调用及栈帧分析相关的知识,希望对你有一定的参考价值。

一.前言 

每一次函数调用都是一个过程。这个过程我们通常称之为:函数的调用过程。

这个过程要为函数开辟栈空间,用于本次函数的调用中临时变量的保存,现场保护。这块栈空间就是函数栈帧。

实验代码:

#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = add(a, b);
	printf("%d", c);
	return 0;
}

二.实验调试

1.按F11进入单步调试,在窗口处进入反汇编。

 进入反汇编界面

 三、函数栈帧的创建与销毁

1.在调试的时候我们要明白两个寄存器ebp内部存储的是栈底指针,esp存储的是栈顶指针。

eip是程序计数器,存的是当前正在执行指令的下一条指令的地址

2.进入程序,一步一步进行调试

(1).先看前三条

 push        ebp  
 mov         ebp,esp  
 sub         esp,0E4h 

栈区是从下到上,由高地址到低地址,先将ebp入栈,在将esp移到和ebp相同的位置。在将esp的地址减掉0E4h

 (2).再看三条操作

 push        ebx  
 push        esi  
 push        edi

继续向 main()函数上方又压入ebx,esi,edi,且esp也随着压入的数据而地址降低,

 (3).继续执行操作

 lea         edi,[ebp-24h]  
 mov         ecx,9  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  

第一条指令是将[ebp-0E4h]的值加载到edi当中,该地址是228个字节的上方。
第二,三条指令分别是将9赋给到ecx,0CCCCCCCCh 赋给到eax。
第四条指令则是将从edi开始,向下的39hdword(就是39hdoubleword(两个字),一共是228个字节,就是最开始开辟的main函数栈帧大小)都赋值为0CCCCCCCCh,也就是将main函数内的所有值都变成了0CCCCCCCCh。现在的栈就成了:

 (4).现在开始代码段

	int a = 10;
 mov         dword ptr [ebp-8],0Ah  
	int b = 20;
 mov         dword ptr [ebp-14h],14h  
	int c = 0;
 mov         dword ptr [ebp-20h],0  

这就是正常的赋值操作,第一条创建a=10,第二条创建b=20,第三条创建c=0;

(5)add函数的调用

	c = add(a, b);
 mov         eax,dword ptr [ebp-14h]  
 push        eax  
 mov         ecx,dword ptr [ebp-8]  
 push        ecx  
 call        00961023  
 add         esp,8  
 mov         dword ptr [ebp-20h],eax

第一条是现将b的值放到eax中,第二条是讲a的值放在ecx中,然后将eax,ecx压栈,在执行call指令,将call指令的下一条语句地址压入栈顶,然后进入准备调用的函数

 (6)add函数内部:

int add(int a, int b)
{
009617A0  push        ebp  
009617A1  mov         ebp,esp  
009617A3  sub         esp,0C0h  
009617A9  push        ebx  
009617AA  push        esi  
009617AB  push        edi  
009617AC  mov         edi,ebp  
009617AE  xor         ecx,ecx  
009617B0  mov         eax,0CCCCCCCCh  
009617B5  rep stos    dword ptr es:[edi]  
009617B7  mov         ecx,96C003h  
009617BC  call        0096130C  
	return a + b;
009617C1  mov         eax,dword ptr [ebp+8]  
009617C4  add         eax,dword ptr [ebp+0Ch]  
}

这个和main函数开始的时候一样都是创建空间,但eax和ecx的值用于add函数的初始化,且在过程中被更改,但压入栈中的"eax"和"ecx"的值并不受影响,因为它们并不是我们所说的eax与ecx,留在栈中的只是将a,b赋给eax和ecx后所留下的一个值拷贝。

最后在将结果送到eax寄存器中,通过寄存器带回函数的返回值。

(7)add函数的返回,

前三句是出栈,然后将ebp的值赋给esp使得esp下移

倒数第二句从栈顶弹出一个元素到ebp中,此时栈顶放的正好是main函数的ebp,然后回到main函数的栈帧,ret指令会使得出栈一次,并将出栈的内容作为地址,将程序执行跳转到该地址

009617C7  pop         edi  
009617C8  pop         esi  
009617C9  pop         ebx  
009617CA  add         esp,0C0h  
009617D0  cmp         ebp,esp  
009617D2  call        00961235  
009617D7  mov         esp,ebp  
009617D9  pop         ebp  
009617DA  ret

最后打印并输出

	printf("%d", c);
009625BD  mov         eax,dword ptr [ebp-20h]  
009625C0  push        eax  
009625C1  push        967BCCh  
009625C6  call        009613A2  
009625CB  add         esp,8  
	return 0;

这就是完整的函数栈帧创建和销毁过程。

以上是关于C语言之函数调用及栈帧分析的主要内容,如果未能解决你的问题,请参考以下文章

函数调用过程原理及栈帧分析

图解C/C++语言底层:函数调用过程之函数栈帧的创建和销毁(上)

图解C/C++语言底层:函数调用过程之函数栈帧的创建和销毁(上)

c语言函数的栈帧

函数调用时的栈帧变化

C函数调用过程原理及函数栈帧分析(转)