
环境:VS2013
为什么选择这个环境,因为低版本编译器便于观察,高版本往往会有很多优化,而且不同编译器的函数栈帧有所区别,但大体逻辑上一致
一、寄存器
有很多类型寄存器,比如eax,ebx,scx,edx等等
还有两个比较特殊的:esp 栈顶指针 和 ebp 栈底指针 ,用来维护函数栈帧
并且每一个函数的调用,都会在栈区上开辟空间
二、函数举例
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;
}
三、main函数分析(转到反汇编分析)
1.main函数调用
当你观察main函数反汇编代码时候,你会发现main函数被一个函数调用,被谁呢?
你发现是一个叫starmain........的函数,而你如果进入这个函数
你会发现这个函数又被一个函数调用,是谁呢?你发现是一个mainstarup函数
则我们再调用main函数的时候,main已经先被其他两个函数调用了
我们通过F10转到反汇编继续观察,记得关闭显示符号名,这样便于观察
因此我们就知道,在调用main函数时候,其他两个函数的栈帧已经创建好
而你的return0返回到mainret函数中
2.压栈
我们要开始准备创建main函数的函数栈帧了
进行压栈操作,压入栈底指针ebp,此时esp位置发生变化
接下来,我们再使用move操作让esp变成ebp
此时我们sep再减去一个值,可以看到对应我们图中的0E4h
那栈顶指针esp跑哪去了呢,我们可以看到esp减去了一个值
这个值根据不同编译器略有差异
我们来看看这个值对应的是什么地方
通过下面两张图对于,我们发现这个位置还是非常大的,因此此时我们的esp位置向上移动
ebp开始接管原来esp的位置




接着我们再在栈顶压入三个元素
压入第一个元素ebx时,esp栈顶指针位置就往上走,指到ebx顶端
接着压入第二个元素esi时,同理esp值再次变化,esp栈顶指针位置继续往上,指到esi顶端
压入第三个元素edi时,同理esp值再次变化,esp栈顶指针位置继续往上走,指到edi顶端
此时停下来,对应编译器的三条push指令


3.加载有效地址
进而我们执行下一个操作
lea(load effective adress) 加载有效地址,我们给edi放入了一个地址
![]()
我们通过move,把这个地址的值是ebp减去一个0E4h
我们把39h这个值存入ecx中,把0CCCCCCCCh这个值存入eax中,这些值之后会用到

4.初始化
接着我们开始执行初始化操作
可以看到我们通过的dword指令将edi向下的一片区域初始化的改为eax的内容
即0CCCCCCCCh,一直到栈底指针ebp结束,这个初始化空间还是很大的



我们此时才把main函数的函数栈帧开辟完毕,开始执行我们的第一条代码
假设每一行是4个字节,我们可以看到第一条指令时把变量a的值存入main函数的一块区域
同理变量b和变量c分别存入一块区域
假设你不初始化的话,存入的是随机值,打印出来就是乱码的字符(经典:烫烫烫烫.....)
所以,初始化很重要,对吧


5.调用add函数之前操作
接下来我们发现再调用add函数之前,还要进行一些列操作

我们先把变量b的值放入eax中,再push进行压栈,此时esp位置往上走
同理我们再把另外个变量a放入ecx中,再push压栈,esp位置再往上走
这个过程就是传参
我们还发现,下面有个call指令,这个是干嘛的呢,我们可以看到后边有个地址02710E6h
这个是标记用的,最后我们再来解释
这时候esp就继续往上走了一大块,为add函数栈帧创建做准备

四、add函数分析
好,此时我们进入了add函数,前面过程与add函数类似
创建函数栈帧,压入来自main的ebp,三次pushebp变化,再初始化,开始执行计算

1.形参创建分析
你会发下,我形参x和y怎么没见他们创建呢
诶,你之前可能没有发现,其实这两个变量是在add后函数栈帧的ebp+8和ebp+12中的
我们执行move指令就可以把变量拿过来,我们再把这两个变量的值相加,放在ebp-8中
即放入你之前规定的c变量中
说白了就是函数调用时传参之后形参x,y压了栈,但不是在函数内创建
这样就可以说形参是实参的一份临时拷贝也不为过
因此你修改形参并不会影响实参,形参只有在函数调用的时候才会传给函数对应的值



2.函数结果返回
好,我们开始执行返回操作
我们看到上图中最后一条指令时move,结尾跟了个寄存器eax
目的是把函数的结果的值放入一个全局寄存器eax中
以便在函数栈帧销毁的时候,函数返回结果不会消失

我们开始执行三条弹出pop指令,每次弹出esp位置都会增大,往下移(往回走)
此时我们esp位置变成了刚开始的esp位置
此时ebp再往下走(往开始地方,即高地址方向),回收空间
在add栈顶弹出之前,main函数栈底ebp回到了最开始的位置
我们的esp再重新指向原来main函数栈顶
还记得之前的call函数吗
我们就要回到那个位置继续执行我们的主函数代码,即之前的ecx位置
现在函数调用完了,形参x和y没有用了,此时形参x和y销毁
esp再往下走,再三次弹出pop:edi,esi,ebx
回到最最最开始的位置,此时我们再把事先存在eax中函数结果的值放入C中
完毕



整个基本流程就完成了,还是挺复杂的
声明
作者基础知识有限,本文只是阐述作者看法,并非完全正确,如果有错误,欢迎指出
友好交流,请勿上升到人身攻击
END
1万+

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



