预备知识:
相关汇编命令
mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令
相关寄存器
eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数据
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址
什么是栈?
栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。
在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。
在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小
栈总是向下增长(由高地址向低地址)的。
相关知识
1.每一次函数调用,都要为本次函数调在栈区上用开辟空间,就是函数栈帧的空间。
2.这块空间的维护是使用了2个寄存器: esp 和 ebp ,ebp记录的是栈底的地址,esp记录的是栈顶的地址。
3.正在调用哪个函数,ebp,esp就维护哪个函数的栈帧
函数栈帧
什么是函数栈帧
我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。
那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。
函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:
1.函数参数和函数返回值
2.临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
3.保存上下文信息(包括在函数调用前后需要保持不变的寄存器)
下面用VS2013去演示,每个编译器产生的过程会有差异
演示代码:
#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); return 0; }
函数的调用堆栈
首先按F10,进行编译,然后点击调试–>窗口–>调用堆栈
进入堆栈后可以看到main被调用
这时,我们会困惑main函数在被谁调用
继续按F10,让代码运行,当代码执行完时,会发现main函数被一个叫__tmainCRTStartup的函数调用
这里要知道一点:main函数也是被其他函数调用的
而__tmainCRTStartup函数也是被一个叫mainCRTStartup的函数调用
此时,我们可以获知一个大概的轮廓:
转到反汇编
F10编译->单击右键->转到反汇编
然后就可以看见代码对应的汇编代码
在进入main之前,main就被__tmainCRTStartup调用,所以此时,走到了main函数中,所以__tmainCRTStartup的函数栈帧已经创建
1.执行00341410 push ebp 语句,把ebp的值压栈,同时esp指针向上移动,指向栈顶
这里也可以通过监视窗口看到,执行push语句之后,esp的地址减小了,就说明esp向上移动了
同时,在内存窗口中,也可以看到ebp的值被存到了esp指向的空间里·
2.执行00341411 mov ebp,esp ,这句的作用是:把esp的值赋给ebp
此时esp,ebp的值相等
3.执行00341413 sub esp,0E4h 语句,给esp的值减去0E4h
,这就意味着esp指向上面的某一块区域,此时esp和ebp之间的空间,就是给main预开辟的空间
4.执行这段语句,在栈顶压入三个元素
5.0034141C lea edi,[ebp-0E4h] ,edi
6.00341422 mov ecx,39h 把39h这个值赋给ecx
7.00341427 mov eax,0CCCCCCCCh 把0CCCCCCCCh赋给eax
8.0034142C rep stos dword ptr es:[edi] 这句的意思是把从edi向下ecx(39h)大小的空间,改为eax的值,也就是0CCCCCCCCh
此时main的栈帧已经真正意义上开辟好了
9.0034142E mov dword ptr [ebp-8],0Ah ,把0Ah的值放到[ebp-8]中去
在程序中定义变量时进行了初始化,所以在相应位置把值存入栈中,如果没有初始化,栈中对应位置中的值就是cccccccccc,这也就是有时未初始化变量而导致打印烫烫烫烫烫烫烫烫烫烫烫烫的原因
10.00341435 mov dword ptr [ebp-14h],14h ,把14h的值放到[ebp-14h]中去
11.0034143C mov dword ptr [ebp-20h],0 把0的值放到[ebp-20h]中去
12.00341443 mov eax,dword ptr [ebp-14h] 把[ebp-14h]的值放到eax里,00341446 push eax 再把eax压入栈中
13.00341447 mov ecx,dword ptr [ebp-8] 把[ebp-14h]的值放到eax里,0034144A push ecx 再把ecx压入栈中
12.13.步实际上是在传参,先压b,再压a,参数是从右往左传的