阅读本文需要掌握的知识
熟练使用——c语言函数
进入正题前
首先我们为什么要学习函数的栈帧?
简单回答就是:增加内功
学习函数调用的底层代码,了解函数如何传参、如何返回
这样对于函数的使用有很大的帮助 对于日后出现的问题提会有所了解
列如:
函数在传参时,将在函数内部创建变量a,
然后将a的地址传了回去,
当函数test调用完 后会被销毁
这时传回去的a的地址被p接收会就造成野指针,使用野指针会造成非法访问等问题
如不了解函数栈帧 ,理解起来可能有一些麻烦 展示代码如下int* test() { int a = 10; int *pa = &a; return pa; } int main() { int* p = test();//此时p指向了一个不存在 或者不属于当前函数的地址 }当此时用
p指针做一些其他操作,会出现问题,所以了解函数栈帧很重要
前期准备:
函数栈帧大致分为以下几步:简单了解不至于被劝退。
1.main函数在栈区创建,并且传参
2.被调用的函数在栈区创建,计算值,并带回返回值
3.以上操作都是在栈区中完成,所以有了压栈、弹栈
我们将围绕以下代码展开函数栈帧的讲解
此代码通过Add函数实现a+b 将返回值带给c的过程
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}
函数栈帧的创建和销毁所解决的问题
最后我会回答这些问题,记不住没关系
局部变量是怎么创建的?
为什么局部变量的值是随机值?
函数是怎么传参的?
传参的顺序是怎样的?
形参和实参是什么关系?
函数调用是怎么做的?
函数调用结束后是怎么返回的?
此次过程所需软件
我所用到的软件是 Visual Studio 2019
使用VS2013也可以,可能略有差异
软件准备
将代码写在VS2019中,按F10开始调试
并点开反汇编
或者F10 后,空白处右键鼠标转到反汇编
打开内存 和监视
像这样排列以便观察
并且
内存监视 列改为4因为我们写的代码是int类型 int是4字节
准备完成 ,解读反汇编代码,你还要知道这些!
在反汇编内 ,你看到如下代码
挺长 但是不用害怕
下面代码暂时你看看就行,看不懂没关系,下面我一句一句解释 你很快就会明白
int main()
{
002918B0 push ebp
002918B1 mov ebp,esp
002918B3 sub esp,0E4h
002918B9 push ebx
002918BA push esi
002918BB push edi
002918BC lea edi,[ebp-24h]
002918BF mov ecx,9
002918C4 mov eax,0CCCCCCCCh
002918C9 rep stos dword ptr es:[edi]
002918CB mov ecx,29C003h
002918D0 call 0029131B
int a = 10;
002918D5 mov dword ptr [ebp-8],0Ah
int b = 20;
002918DC mov dword ptr [ebp-14h],14h
int c = 0;
002918E3 mov dword ptr [ebp-20h],0
c = Add(a, b);
002918EA mov eax,dword ptr [ebp-14h]
002918ED push eax
002918EE mov ecx,dword ptr [ebp-8]
002918F1 push ecx
002918F2 call 002910B4
002918F7 add esp,8
002918FA mov dword ptr [ebp-20h],eax
printf("%d\n", c);
002918FD mov eax,dword ptr [ebp-20h]
00291900 push eax
00291901 push 297B30h
00291906 call 002910D2
0029190B add esp,8
return 0;
0029190E xor eax,eax
}
00291910 pop edi
00291911 pop esi
00291912 pop ebx
00291913 add esp,0E4h
00291919 cmp ebp,esp
0029191B call 00291244
00291920 mov esp,ebp
00291922 pop ebp
00291923 ret
看完上面的代码 ,如果你还没被劝退,那么继续跟我往下看吧!
先介绍一下 上述代码的一些英文有些寄存器暂时
记不住没关系,用到的时候我会加以说明
eax - 寄存器 不会被销毁 集成到cpu上的
ebx
ecx
edxesp - 栈顶指针
记住
ebp - 栈底指针记住
这两个寄存器中放的是地址
这两个地址是用来维护函数栈帧的
这里了解eax esp ebp 就可以啦
记不住也没关系,后面都会讲解
首先要了解 main()函数执行的时候也是被一个叫__tmainCRTStartup函数调用的
所以先给__tmainCRTStartup在栈区开辟空间
这是编译器底层的调用暂时不做探讨 知道就可以
查看方法如下 在监视中添加esp、ebp查看地址
esp、ebp是两个维护函数空间的指针
上面为低地址 ,下面为高地址
栈顶指针 esp 0x008ff99c
栈底指针 ebp 0x008ff9b8
首先分配一块空间给__tmainCRTStartup
栈区:
| | 低地址
| |
| |
| |
| |\ esp 0x008ff99c 栈顶
| | \
| | __tmainCRTStartup
| | /
| |/ ebp 0x008ff9b8 栈底
| | 高地址
从以上代码可以看出
栈区先使用高地址,再使用低地址
栈顶指针 esp 0x008ff99c(十六进制) —— 9,435,548 (十进制)
栈底指针 ebp 0x008ff9b8(十六进制) —— 9,435,576 (十进制)
8ff99c - 8ff9b8 计算得出 栈区为__tmainCRTStartup 开辟了28字节的空间
解读反汇编语句开始
一、为main函数开辟空间
1.002918B0 push ebp
按下F11进行逐语句操作,后面所有操作都是F11完成
push意为压栈 此行代码意为 将ebp压栈
黄色箭头指向第二条语句 说明第一条语句执行完了
此时的espebp是维护__tmainCRTStartup函数的
代码展示
图解展示
push 压栈 ebp
把ebp的地址压栈到栈顶 esp自动指向最上面的地址
又因为栈先使用高地址 后使用低地址
所以esp 的地址减小4 esp = 0x008ff998
| | 低地址
| |
| |
| ebp | esp 0x008ff998
| |\ ↑↑↑向上移动 4字节 ~~0x008ff99c~~
| | \
| | __tmainCRTStartup
| | /
| |/ ebp 0x008ff9b8
| | 高地址
2. 002918B1 mov ebp,esp
mov可以理解为赋值,此代码意为:ebp = esp
此时是espebp向上移动的过程 要去维护main函数
把esp的值赋给ebp
此时ebp指向 esp指向的位置
ebp = 0x008ff998
| |
| |
| |
| |
| | 低地址
| |
| |
| ebp | ebp 0x008ff998 esp 0x008ff998
| |\
| | \
| | __tmainCRTStartup
| | /
| |/
| | 高地址
3. 002918B3 sub esp,0E4h
sub的意思是 减去
esp减0E4h,esp指针就指向了低地址
此操作将esp(栈顶指针) 向上移动,为main函数开辟空间并维护
002918B3 sub esp,0E4h
esp 减去 0E4h 0E4h == 228
esp = 0x008ff8b4
低地址
| |\ esp 0x008ff8b4
| | \
| | \
| | main
| | /
| | /
| |/
| ebp | ebp 0x008ff998
| |\
| | \
| | __tmainCRTStartup
| | /
| |/
| | 高地址
main函数的空间
二、压栈
002918B9 push ebx
002918BA push esi
002918BB push edi
esp每次压栈后会自动指向最低地址处
EBX是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ESI/EDI分别叫做"源/目标索引寄存器"
暂时不理解没关系,直接看三
低地址
| edi | esp 0x008ff8a8
| esi | ↑ 0x008ff8ac
| ebx | ↑ 0x008ff8b0
| |\ ↑ 0x008ff8b4
| | \
| | \
| | main
| | /
| | /
| | /
| | /
| |/
| ebp | ebp 0x008ff998
| |\
| | \
| | __tmainCRTStartup
| | /
| |/
| | 高地址
三、初始化main函数
002918BC lea edi,[ebp-24h]
002918BF mov ecx,9
002918C4 mov eax,0CCCCCCCCh
002918C9 rep stos dword ptr es:[edi]
edi存储ebp减24h的位置,24h是main函数空间的大小
ecx存储9
eax存储0CCCCCCCCh
将ebp到edi存储的位置的ecx(9)个位置都 初始化为eax(0CCCCCCCCh)
讲人话就是 :将main空间的内容初始化为cc cc cc cc
低地址
| edi | esp 0x008ff8a8
| esi | ↑ 0x008ff8ac
| ebx | ↑ 0x008ff8b0
|cc cc cc cc|\ ↑ 0x008ff8b4
|cc cc cc cc| \
|cc cc cc cc| \
|cc cc cc cc| main
|cc cc cc cc| /
|cc cc cc cc| /
|cc cc cc cc| /
|cc cc cc cc| /
|cc cc cc cc|/
| ebp | ebp 0x008ff998
| |\
| | \
| | __tmainCRTStartup
| |
本文详细介绍了C语言函数栈帧的工作原理,包括函数的创建、参数传递、返回值处理等步骤,通过实例解析了反汇编代码,帮助读者深入理解函数调用的底层机制。













最低0.47元/天 解锁文章
974

被折叠的 条评论
为什么被折叠?



