堆栈在内存中的压栈和弹栈工作原理

堆栈在内存中的压栈和弹栈工作原理

一.概述:

     网上关于堆栈的文章很多,但多为不祥尽.趁清明假期整理验证下.VC编译,XP平台.

调用函数入栈过程分以下5步: 1.压参数(右向左)-->2.压调用完函数后的第一条汇编指令-->3.保存本函数的栈顶地址-->4.申请子函数的栈空间(会预留一部分空间)-->5.子函数局部变量压栈...其中第1步是在main函数压的栈;第2步是在main函数运行完CALL test这条指令进入test函数时压的栈;第3步是运行完第3步的第一条指令后压的栈;第4-5步也都是在test函数里压的栈.

 

二.源码:

#include <stdio.h>
#include <string.h>

int test(int iA, int iB)
{
 int iC = iA + iB;
 printf("test: 0x%p\n",test);
 
 return iC;
}

int main(int argc, const char* argv[])
{
 int a = 3;
 int b = 4;
 printf("main: 0x%p\n",main);
 test(a, b);
 return 0;
}

 

三.汇编:

    调用main函数前的栈空间初始化,堆初始化和后面main函数的弹栈处理就略过了.我们只看test子函数的过程就足够了.

      .

      .

      .

0040D75E CC                   int         3
0040D75F CC                   int         3
--- D:\VC\1\ollydog\ollydog_test.cpp  ----------------------------------------------------
1:
2:    #include <stdio.h>
3:    #include <string.h>
4:
5:    int test(int iA, int iB)  #压栈,也就是第2步  ,SP指针: 0x0012FF20
6:    {
0040D760 55                   push        ebp     #压栈,也就是第3步.   SP指针: 0x0012FF1C
0040D761 8B EC                mov         ebp,esp   #把SP指针暂存备份起来.
0040D763 83 EC 44             sub         esp,44h  #把SP指针往后移动0x44字节. SP指针: 0x0012FF1C - 0x44 =  0x0012FED8
0040D766 53                   push        ebx    #把一些临时变量压栈  SP指针: 0x0012FED4
0040D767 56                   push        esi    #把一些临时变量压栈  SP指针: 0x0012FED0
0040D768 57                   push        edi    #把一些临时变量压栈 SP指针: 0x0012FECC
0040D769 8D 7D BC             lea         edi,[ebp-44h]         #开辟test的栈空间.   edi =0x0012FF1C - 0x44 =  0x0012FED8
0040D76C B9 11 00 00 00       mov         ecx,11h       #计数 ecx = 0x44 / 4 = 0x11 ,准备循环0x11次
0040D771 B8 CC CC CC CC       mov         eax,0CCCCCCCCh    #初始化 eax  = 0xCCCCCCCC
0040D776 F3 AB                rep stos    dword ptr [edi]   #将刚才开辟的栈空间都初始化为0xCCCCCCCC.
7:        int iC = iA + iB;
0040D778 8B 45 08             mov         eax,dword ptr [ebp+8]       #eax = 4  ,将0x0012FF1C + 8 的值取出来给eax ,这个形参是在main压的栈.也就是第1步压的栈.
0040D77B 03 45 0C             add         eax,dword ptr [ebp+0Ch]    #eax += 3   ,将0x0012FF1C + C 的值取出来给eax,这个形参是在main压的栈.也就是第1步压的栈.
0040D77E 89 45 FC             mov         dword ptr [ebp-4],eax       #将结果7 压栈.(其实是在test栈空间的有效变量压的栈,也就是形参后面压的栈,而不是栈空间的栈顶(高地址为栈底,低地址为栈顶,sp指针会一直指向栈顶,也就是向低地址发展).)
8:        printf("test: 0x%p\n",test);
0040D781 68 05 10 40 00       push        offset @ILT+0(test) (00401005)     #进入printf库函数的压栈,我们不用关心,相于于在main压int iB 形参的栈
0040D786 68 A4 2F 42 00       push        offset string "test: 0x%p\n" (00422fa4)  #进入printf库函数的压栈,我们不用关心,相于于在main压int iA 形参的栈
0040D78B E8 50 FF FF FF       call        printf (0040d6e0)   # 调用库函数,相当于进入test函数一个道理
0040D790 83 C4 08             add         esp,8  #把刚的给printf函数形参的栈POP出来
9:
10:       return iC;
0040D793 8B 45 FC             mov         eax,dword ptr [ebp-4]   #把结果7从栈中取出来组eax,准备返回函数值.
11:   }
0040D796   5F            POP EDI       #把一些临时变量退栈POP完后SP指针: 0x0012FED0
0040D797   5E            POP ESI      #把一些临时变量退栈POP完后SP指针: 0x0012FED4
0040D798   5B            POP EBX     #把一些临时变量退栈POP完后SP指针: 0x0012FED8
0040D799   83C4 44       ADD ESP,44           #退第4-5步 ,退完整个栈空间, POP完后SP指针: 0x0012FF1C
0040D79C  3BEC          CMP EBP,ESP       #系统判断,栈指针和备份栈指针是否相等.相等后才能退出函数.
0040D79E   E8 2D39FFFF   CALL ollydog.__chkesp   #系统检查栈指针SP
0040D7A3   8BE5          MOV ESP,EBP     #把备份SP指针给栈指针.
0040D7A5   5D            POP EBP     #退第3步  POP完后SP指针: 0x0012FF20
0040D7A6   C3            RETN   #退第2 步   POP完后SP指针: 0x0012FF24  ,也就是指向了main函数压栈的最后一个形参int iA.

 

      .

      .

      .

 

 

      .

      .

      .

0040105E CC                   int         3
0040105F CC                   int         3
--- D:\VC\1\ollydog\ollydog_test.cpp  ----------------------------------------------------
12:
13:   int main(int argc, const char* argv[])   #main函数开始.
14:   {
00401060 55                   push        ebp
00401061 8B EC                mov         ebp,esp
00401063 83 EC 48             sub         esp,48h
00401066 53                   push        ebx
00401067 56                   push        esi
00401068 57                   push        edi
00401069 8D 7D B8             lea         edi,[ebp-48h]
0040106C B9 12 00 00 00       mov         ecx,12h
00401071 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00401076 F3 AB                rep stos    dword ptr [edi]
15:       int a = 3;
00401078 C7 45 FC 03 00 00 00 mov         dword ptr [ebp-4],3
16:       int b = 4;
0040107F C7 45 F8 04 00 00 00 mov         dword ptr [ebp-8],4
17:       printf("main: 0x%p\n",main);
00401086 68 0A 10 40 00       push        offset @ILT+5(_main) (0040100a)
0040108B 68 B0 2F 42 00       push        offset string "test: 0x%x\n" (00422fb0)
00401090 E8 4B C6 00 00       call        printf (0040d6e0)
00401095 83 C4 08             add         esp,8
18:       test(a, b);
00401098 8B 45 F8             mov         eax,dword ptr [ebp-8]    #test函数开始
0040109B 50                   push        eax          # 把 b = 4 取出来,当形参准备压栈
0040109C 8B 4D FC             mov         ecx,dword ptr [ebp-4]
0040109F 51                   push        ecx         # 把 a = 3 取出来,当形参准备压栈
004010A0 E8 60 FF FF FF       call        @ILT+0(test) (00401005)    #  进入test 函数

004010A5 83 C4 08             add         esp,8   #test函数结束       把形参int iB ,int iA 退栈
19:       return 0;
004010A8 33 C0                xor         eax,eax
20:   }                                    

004010AA 5F                   pop         edi          
004010AB 5E                   pop         esi
      .

      .

      .

 

四.分析图:

1)  VC 堆栈图

函数的压栈顺序结构图:  1.本函数的形参;2.运行后本函数的下一条指令(一般为退形参栈的指令)地址;3.父函数的栈空间栈顶的前一个DWORD;4.本函数内的要用到的栈空间;5.一些临时变量.

玫瑰红框:   

         0x0012FECC --> 0x0012FED4    临时变量

         0x0012FED8 --> 0x0012FF18      test栈空间

         0x0012FF1C --> 0x0012FF20       main函数的栈空间栈顶的前一个DWORD 和 运行后本test函数后退int iB ,int iA的退栈指令地址

         0x0012FF24 --> 0x0012FF28       test函数的形参,int iA ,int iB

橙色框:   

         0x0012FF2C --> 0x0012FF34       临时变量

         0x0012FF38 --> 0x0012FF7C       main栈空间

         0x0012FF80 --> 0x0012FF84       main函数的栈空间栈顶的前一个DWORD 和 运行后本test函数后退const char* argv[],int argc的退栈指令地址

         0x0012FF88 --> 0x0012FF8C       main函数的形参const char* argv[0],int argc

 

 

2-0)  通过上分析对应下图的分析图(通过下图就可以很清楚压栈的先后顺序了):

 

2-1) main 函数的形参

 

2-2)调用完main函数后的第一条汇编指令:add esp,0Ch.

 

2-3)调用完test函数后的第一条汇编指令:add esp,8.

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值