在上边一篇博客中,最后一段代码的输出结果如下图(1)所示:
图(1)
来分析一下为什么会有这样的结果呢?
图(2)
图二是函数栈帧的状态图
对上图的解释:
1,参数c,b,a的地址分别为0X0012FF24,0X0012FF20,0X0012FF1C,两个相邻的地址之间相差4,这是因为在win32中,int的存储占用了四个字节
2 因为ret为返回的地址,ebp为指针,所以二者均占用四个字节。因而可以算出存储ret的存储的单元首址为:0x0012FF18,存储ebp存储单元的首址0x0012FF14。又因为buffer数组空间为四个字节所以buffer1的值应该为0x0012FF10,和程序输出buffer1的值一致。
3 为什么buffer2数组只有五个字节,根据栈增长的方向,buffer2=bufeer1-5=0X0012FF0B,而程序输出却是0X0012FF08呢。
这是因为在分配空间是按字分配(上面提到过),win32一个字是四个字节,而buffer5是5个字节,因为会分配八个字节(内存对齐),所以buffer2=buffer1-8=0X0012FF08,运算的结果和程序输出的结果一致。
下面来解释返回地址RET的值,RET是函数调用后的返回地址,也就是调用函数后紧接着要执行的指令的地址。验证代码如下:
#includevoid 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字节 | EBP:4字节 | RET:4字节 |
图(3)RET示意图
程序的结果运行如下:
可以看到返回地址RET的值为0X:0040108D;
我们在printf();语句前加入断点经过反编译可以得到pritnf()指令的地址:
可以发现我们输出的返回地址和printf()第一个动作的指令地址相一致。
因而,我们可以得到函数调用结束后的返回过程如下:
1 保存返回值:通常将函数的返回值保存在寄存器eax中。
2 弹出当前栈帧,恢复上一个栈帧。
A) 在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。
B)讲当前栈帧底部的EBP的值(母函数的栈底地址)弹入EBP寄存器中,使得ebp指向母函数的栈底。
C)将函数返回地址弹入EIP寄存器。
3 跳转到新的EIP处执行指令(已经返回到了母函数)。