_cdecl _stdcal _thiscall 等调用约定的汇编代码区别

原文地址:http://blog.sina.com.cn/s/blog_bad31d930102wuk4.html(这里备注下:可能上面还有一个原文链接,是csdn转载的文章类型自动加上去的,我在csdn没加这个功能之前,也会先把转载的原文地址列在最前面,这里忽略csdn的更改,保持习惯吧。)

以下是文章内容:

研究下_cdecl _stdcal _thiscall 等调用约定,以及成员函数与普通函数调用上的区别.

前奏:
      先说下几个asm寄存器以及命令:
      EAX一般用来存放返回值
      ECX一般用来存放this
      rep指令会将他后面的指令重复N次, N = ECX
      stos    dword ptr es:[edi] 将EAX里的内容复制到es:[edi]指向的内存空间,完毕后edi += 4
                  如果设置了direction flag, 那么edi会在该指令执行后 edi -= 4,
      call 标号 该指令将 IP(或CS+IP) 压栈(ESP -=4),然后转移到标号出执行
      ret指令 从栈中恢复IP(ESP +=4),
      ret 8 指令,从栈中恢复IP(ESP +=4),然后再将栈中8个字节弹出(ESP +=8),也就是说
      此命令使ESP自增12

      我们先写一段简单的控制台代码,再反编译成汇编,通过阅读汇编代码,可以彻底看明白
上述区别。源代码很简单:

InClass_CallType.h (C++ Code) 

#pragma once

class CallType {
public:
    int DefaultCall(int a, int b) {
        this->data = a + b;
        return this->data;
    }
    int _cdecl CdeclCall(int a, int b) {
        this->data = a + b;
        return this->data;
    }
    int _stdcall StdCall(int a, int b) {
        this->data = a + b;
        return this->data;
    }
    int _fastcall FastCall(int a, int b) {
        this->data = a + b;
        return this->data;
    }
    int __thiscall ThisCall(int a, int b) {
        this->data = a + b;
        return this->data;
    }
private:
    int data;
};

OutClass_CallType.h (C++ Code) 

#pragma once

int DefaultCall(int a, int b) {
    return a + b;
}
int _cdecl CdeclCall(int a, int b) {
    return a + b;
}
int _stdcall StdCall(int a, int b) {
    return a + b;
}
int _fastcall FastCall(int a, int b) {
    return a + b;
}

CallType.cpp (C++ Code) 

// CallType.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "InClass_CallType.h"
#include "OutClass_CallType.h"

int main()
{
    CallType callType;
    callType.DefaultCall(1, 2);
    callType.CdeclCall(3, 2); 
    callType.StdCall(1, 3);
    callType.ThisCall(2, 2);
    callType.FastCall(2, 3);

    DefaultCall(1, 2);
    CdeclCall(3, 2);
    StdCall(1, 3);
    FastCall(2, 3);

    return 0;
}

分别定义了几种常见的调用约定,涵盖类内的跟类外的。在分析调用约定之前,先借汇编代码复习下

函数的调用过程。

asm 

StdCall(1, 3);
00B619EA  push        3  
00B619EC  push        1  
00B619EE  call        StdCall (0B61032h)  

StdCall:
int _stdcall StdCall(int a, int b) {
00B61890  push        ebp  
00B61891  mov         ebp,esp  
00B61893  sub         esp,0C0h  
00B61899  push        ebx  
00B6189A  push        esi  
00B6189B  push        edi  
00B6189C  lea         edi,[ebp-0C0h]  
00B618A2  mov         ecx,30h  
00B618A7  mov         eax,0CCCCCCCCh  
00B618AC  rep stos    dword ptr es:[edi]  
    return a + b;
00B618AE  mov         eax,dword ptr [a]  
00B618B1  add         eax,dword ptr [b]  
}
00B618B4  pop         edi  
00B618B5  pop         esi  
00B618B6  pop         ebx  
00B618B7  mov         esp,ebp  
00B618B9  pop         ebp  
00B618BA  ret         8 

调用者首先将两个参数按照从右往左的顺序压入栈中,然后call StdCall;
函数(StdCall)的前两行一定是:
push        ebp 
mov         ebp,esp 
第一行(push        ebp  )汇编执行完毕后,栈空间的状态是这样的:

_cdecl _stdcal _thiscall 等调用约定的汇编代码区别_第1张图片

(执行完第二行),ebp+8即为参数a在栈中的位置,ebp+0xC即为参数b在栈中的位置。

ESP指向栈顶:距离参数a的距离为8(隔了一个ebp)
执行完第二行后,EBP = ESP,则EBP(的值)距离参数a的距离为也8。

接下来的三行是ebx、edi、esi三个寄存器的入栈。

再接下来的四行是初始化临时变量空间,全部初始化成0X0ccccccc
(debug模式下都这么干,据说跟int3中断有关)

然后是计算 a+b、然后将返回值存入eax
然后将esi、edi、ebx三个寄存器出栈。

接下来恢复esp、ebp、返回

可以这样来看上面的代码:”对称“
4、5 跟 21、22对称
7、8、9 跟 18、19、20对称

这两处对称保证了在调用函数前跟调用函数后的栈一致的。

接下来的几行,其作用是用0X0CCCCCCC初始化临时变量空间,rep以及stos命令的用法见本文开头。

接下来回到正题,看_cdecl 与 _stdcall的区别:

tmpfdsa.asm * (C++ Code) 


    CdeclCall(3, 2);
00B619DE  push        2  
00B619E0  push        3  
00B619E2  call        CdeclCall (0B61244h)  
00B619E7  add         esp,8  
    StdCall(1, 3);
00B619EA  push        3  
00B619EC  push        1  
00B619EE  call        StdCall (0B61032h)

_cdecl的函数,由调用者清理栈中参数——换句话说,由调用者负责还原esp——在函数调用结束后使用:

add         esp,8 (该命令在调用者调用完毕后的一行)

命令恢复esp;

_stdcall的函数,由函数自身清理栈中参数——换句话说,由函数(被调用者)负责还原esp——ret 8(该命令在函数结尾处)

接下来看下类内的函数调用(成员函数):

tmpfdsa.asm * (C++ Code) 

callType.DefaultCall(1, 2);
00B6198E  push        2  
00B61990  push        1  
00B61992  lea         ecx,[callType]  
00B61995  call        CallType::DefaultCall (0B610F5h)

    int DefaultCall(int a, int b) {
00B617B0  push        ebp  
00B617B1  mov         ebp,esp  
00B617B3  sub         esp,0CCh  
00B617B9  push        ebx  
00B617BA  push        esi  
00B617BB  push        edi  
00B617BC  push        ecx  
00B617BD  lea         edi,[ebp-0CCh]  
00B617C3  mov         ecx,33h  
00B617C8  mov         eax,0CCCCCCCCh  
00B617CD  rep stos    dword ptr es:[edi]  
00B617CF  pop         ecx  
00B617D0  mov         dword ptr [this],ecx  
        this->data = a + b;
00B617D3  mov         eax,dword ptr [a]  
00B617D6  add         eax,dword ptr [b]  
00B617D9  mov         ecx,dword ptr [this]  
00B617DC  mov         dword ptr [ecx],eax  
        return this->data;
00B617DE  mov         eax,dword ptr [this]  
00B617E1  mov         eax,dword ptr [eax]  
    }
00B617E3  pop         edi  
00B617E4  pop         esi  
00B617E5  pop         ebx  
00B617E6  mov         esp,ebp  
00B617E8  pop         ebp  
00B617E9  ret         8  

 

发第四行将this指针存入ECX,在第14行将ECX入栈,第19行出栈、开始使用。

从第20行开始,代码变得不是那么容易理解了:感觉第20行、第24行完全是多余的。。。

抛去这点,将this指针存入ECX寄存器还是很容易看出来的。

最后一行ret   8, 说明_thiscall也是由函数本身清理参数栈。

你可能感兴趣的:(技术,Cocos2dx,c++)