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++语言底层:函数调用过程之函数栈帧的创建和销毁(上)