在上边一篇博客中,最后一段代码的输出结果如下图(1)所示:

                  图(1)

来分析一下为什么会有这样的结果呢?

                                       图(2)

图二是函数帧的状态图

对上图的解释:

1,参数c,b,a的地址分别为0X0012FF240X0012FF200X0012FF1C,两个相邻的地址之间相差4,这是因为在win32中,int的存储占用了四个字节

2 因为ret为返回的地址,ebp为指针,所以二者均占用四个字节。因而可以算出存储ret的存储的单元首址为:0x0012FF18,存储ebp存储单元的首址0x0012FF14。又因为buffer数组空间为四个字节所以buffer1的值应该为0x0012FF10,和程序输出buffer1的值一致。

3 为什么buffer2数组只有五个字节,根据栈增长的方向,buffer2=bufeer1-5=0X0012FF0B,而程序输出却是0X0012FF08呢。

这是因为在分配空间是按字分配(上面提到过),win32一个字是四个字节,而buffer55个字节,因为会分配八个字节(内存对齐),所以buffer2=buffer1-8=0X0012FF08,运算的结果和程序输出的结果一致。

下面来解释返回地址RET的值,RET是函数调用后的返回地址,也就是调用函数后紧接着要执行的指令的地址。验证代码如下:

#include
void fun(){ char buffer[4]; unsigned *p=(unsigned*)(buffer+8);/*定义一个无符号指针指向RET*/ printf("this is a fun\n"); printf("the return address is :0X%08x",*p);/*把RET的值已十六进制数数出*/}void main(){ fun(); printf("\n**************\n");}

Buffer:4字节

EBP4字节

RET4字节

                      图(3)RET示意图

程序的结果运行如下:

可以看到返回地址RET的值为0X:0040108D;

我们在printf();语句前加入断点经过反编译可以得到pritnf()指令的地址:

可以发现我们输出的返回地址和printf()第一个动作的指令地址相一致。

因而,我们可以得到函数调用结束后的返回过程如下:

1 保存返回值:通常将函数的返回值保存在寄存器eax中。

2 弹出当前栈帧,恢复上一个栈帧。

A) 在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。

B)讲当前栈帧底部的EBP的值(母函数的栈底地址)弹入EBP寄存器中,使得ebp指向母函数的栈底。

C)将函数返回地址弹入EIP寄存器。

3 跳转到新的EIP处执行指令(已经返回到了母函数)。