• Win32程序函数调用时堆栈变化情况


    Win32程序函数调用时堆栈变化情况分析   
                在经典的汇编语言教程中,函数调用时堆栈的使用都是着重讲解的问题。如今随着高级语言的越来越完善,单纯使用汇编开发的程序已经不多了。但对函数调用时堆栈动向的了解仍有助于我们明晰程序的执行流程,从而在程序编写和调试的过程中有一个清晰的思路。  
       
      一.调用约定  
      在Win32中,有关函数的调用主要有两种约定。  
      1._stdcall  
                      以__stdcall方式调用的函数有以下特征:  
              •     参数由右至左压栈  
              •   调用返回时,堆栈由被调函数调整  
      2.__cdecl  
      __cdecl约定是C/C++函数的默认调用约定。它有以下特征:  
              •   参数由右至左压栈  
              •   调用返回时,堆栈由调用者调整  
       
      二.Win32函数调用过程  
       
      1.         压入参数  
      这里依据以上的调用方式将调用者给出的参数一一压入堆栈。  
       
      2.         压入断点  
      当程序执行到Call指令的时候,当前语句的地址作为断点地址压入堆栈。  
       
      3.         跳转  
      eip的值被重新设置为被调函数的起始地址。  
       
      4.         mov   ebp,   esp  
      这里ebp被用来在堆栈中寻找调用者压入的参数,同时作为调用者堆栈指针的一个备份。在此前还应该执行一条:  
      push   ebp  
      把ebp中原来的数值保存。  
       
      5.         sub   esp,N  
      这里N是函数内局部变量的总字节数加上一个整数,一般为40。此后esp即为被调函数的堆栈指针了。  
       
      6.         初始化esp   ~   esp-N之间的N字节空间  
      这是对堆栈中已分配给局部变量使用的内存空间的初始化,一般全部设置为0xcc。  
       
      7.         顺序执行函数内语句。  
      此时函数的堆栈位于所有局部变量的内存空间之后,二者之间一般有40字节的隔离带。  
       
      8.返回  
      为保障调用的正常返回,函数内应当保证规范使用堆栈,使即将返回的时候esp的值恢复为执行第一条语句前的状态。说明白点,就是每一条push都要有相应的pop。  
      调用返回的过程如下:  
      mov   esp,   ebp  
      执行后,esp恢复为调用者的堆栈指针,栈顶除断点地址外,还存有原ebp的值和调用时压入的参数。  
      然后依次弹出ebp的值和断点地址。如果是__cdecl约定则直接返回调用者,调用者将负责调整堆栈,丢弃调先前压入的参数。如果是__stdcall则这个工作由被调函数来执行。  
       
      程序样例如下:  
      ……  
      0040B8E8       push                 1                         ;压入参数  
      0040B8EA       call                 00401028                 ;调用函数  
      ……  
      00401028       jmp                   0040b7c0                 ;跳转到函数入口  
      ……  
      0040B7C0       push                 ebp                         ;保存ebp  
      0040B7C1       mov                   ebp,esp                  
      0040B7C3       sub                   esp,44h                 ;设置函数的堆栈指针,此函数中有4  
      ;字节的局部变量  
      0040B7C6       push                 ebx  
      0040B7C7       push                 esi                  
      0040B7C8       push                 edi  
      0040B7C9       lea                   edi,[ebp-44h]          
      0040B7CC       mov                   ecx,11h  
      0040B7D1       mov                   eax,0CCCCCCCCh  
      0040B7D6       rep   stos         dword   ptr   [edi]         ;初始化局部变量空间  
      0040B7D8       mov                   eax,dword   ptr   [ebp+8]  
      0040B7DB       mov                   dword   ptr   [ebp-4],eax  
      ……  
      0040B7DE       pop                   edi                         ;弹出曾压栈的数据  
      0040B7DF       pop                   esi  
      0040B7E0       pop                   ebx  
      0040B7E1       mov                   esp,ebp                 ;恢复调用者的堆栈  
      0040B7E3       pop                   ebp                         ;弹出原ebp值  
      0040B7E4       ret                   4                         ;返回并将堆栈向上调整4字节。  
      ;此处为__stdcall约定,所以由函数调  
      ;整堆栈  
       
      相应的C代码如下:  
       
      void   __stdcall   fun(int);  
       
      int   main(void)  
      {  
              ……          
      fun(1);  
      ……  
              return   0;  
      }  
       
      void   __stdcall   fun(int   para)  
      {  
              int   localpara   =   para;  
              ……  
      }  
         
       
       
      C   与C++的两种调用方法  
      (1)_stdcall:用于Win32   API函数和COM+接口。它从右向左将参数推入堆栈,被调函数在返回之前从堆栈中弹出自己的参数。从堆栈中弹出自己参数的函数不支持参数数目的变化。  
      (2)_cdecl:C和C++默认方式)。它也是从右向左传递参数。但是被调函数不负责从堆栈中弹出参数,调用函数将在函数调用返回后晴空堆栈。  
      CALLBACK采用方式(1)。它称为回调函数,即供系统调用的函数。  
      例如窗口函数、定时处理函数、线程处理函数等。  
       
      CALLBACK   是老式叫法,现在微软都改为WINAPI.  
       
       
       
      至于编译器的解释上。c与c++搞的很麻烦。  
       
      我也记不住每次碰到都去查一下.  
       
      我列个表:  
       
      语言                                     调用约定                         输出方式  
       
      c                                           stdcall                     _SomeFunctionname@Number  
       
       
      c                                           cdecl                         _SomeFunctionname  
           
       
       
       
      C++的烦的要死。  
      C++编译时函数名修饰约定规则:    
       
      __stdcall调用约定:    
      1、以“?”标识函数名的开始,后跟函数名;    
      2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;    
      3、参数表以代号表示:    
      X--void   ,    
      D--char,    
      E--unsigned   char,    
      F--short,    
      H--int,    
      I--unsigned   int,    
      J--long,    
      K--unsigned   long,    
      M--float,    
      N--double,    
      _N--bool,    
      ....    
      PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;    
      4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;    
      5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。    
       
      其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如    
      int   Test1(char   *var1,unsigned   long)-----“?Test1@@YGHPADK@Z”    
      void   Test2()   -----“?Test2@@YGXXZ”    
       
      __cdecl调用约定:    
      规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。    
       
      __fastcall调用约定:    
      规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。    
                   
       
      编写DLL的时候输出函数老是不认就是编译器解释的不同造成的。EXTERN   "C"就是为了统一。全部用c方式解释。  
       
       
       
      两种声明是不同的,vc默认的调用是__cdecl   ,所以   extern   "C"   __declspec(dllimport)   int   mydllfunc(int);是extern   "C"   __declspec(dllimport)   int   __cdecl   mydllfunc(int);  
      因此和extern   "C"   __declspec(dllimport)   int   __stdcall   mydllfunc(int);是有区别的    

  • 相关阅读:
    随机小错2_0527
    随计小错
    C++实现读写文件
    03-树2 List Leaves (25 分)
    获取毫秒级的时间戳
    new对象时,类名后加括号与不加括号的区别
    FoxMail 7.2的邮件存储目录修改
    C/C++ 开源算法库
    VS2015的安装
    脏内存导致BUG的问题
  • 原文地址:https://www.cnblogs.com/hellohuan/p/1237371.html
Copyright © 2020-2023  润新知