IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Visual C++中的几种函数调用方式

changming的blog 2012-06-14 13:56:48 累计浏览 1,682 次
本机暂存

    (本文中所有汇编代码均采用Intel语法,即dest在左边)

    C++中的函数被编译成汇编代码的时候,必须遵循一定的规范,如参数怎么传递,栈指针怎么增减。Visual C++中,一共有5种情况:

  • __cdecl
  • __stdcall
  • __fastcall
  • __thiscall
  •     默认情况下,是__cdecl。__cdecl 和__stdcall的区别是:__cdecl是调用者清理栈,而__stdcall是被调用者清理栈。所以,理论来说,__cdecl生成的代码体积会更大。但是,对于varargs函数,由于被调用者并不知道参数的具体长度,所以这样的函数只能采用__cdecl。

        所有这四种方式,生成的函数都有固定的边界特征,

        __cdecl 和__stdcall以这样的模式开始:

        push ebp ;保存ebp

        mov ebp,esp ;设置栈指针

        sub esp,0C0h ;为局部变量保留栈空间

        push ebx ;保存在这个函数中可能用到的寄存器

        push esi ;保存在这个函数中可能用到的寄存器

        push edi ;保存在这个函数中可能用到的寄存器

        以这样的模式结束:

        pop edi ;恢复寄存器原先的值

        pop esi ;恢复寄存器原先的值

        pop ebx ;恢复寄存器原先的值

        add esp,0C0h ;与前面的sub esp,0C0h对应

        mov esp,ebp ;恢复栈指针

        pop ebp ;恢复ebp

        ret

        其中 “mov esp,ebp; pop ebp;”也可替换成一句“leave”指令。

        如果是__stdcall,最后一行的ret会变成

        ret 0Ch

        这样。最后的那个数字代表从栈上弹出多少个字节,它应当等于函数参数的总大小。

        类的成员函数默认是采用__thiscall。它与__stdcall非常相似,区别是this指针会通过ecx传递。所以,如果在一个函数中发现ecx未被赋值就开始读它,那么多半是__thiscall。如

        push ebp ;保存ebp

        mov ebp, esp ;设置栈指针

        push ecx ;保存在这个函数中可能用到的寄存器

        push ebx ;保存在这个函数中可能用到的寄存器

        push esi ;保存在这个函数中可能用到的寄存器

        mov esi, ecx ; 注意!!!ecx尚未被赋值就开始读了

        mov ebx, [esi+344h] ; this指针一般存放在esi中,并且在整个函数体内,esi尽量保持不变。

        另外再次强调,__thiscall末尾的ret语句要跟一个数字,来清理栈上的函数参数。

        另外就是,如果类成员函数采用了变长参数列表,那么就不能用__thiscall,而必须用__cdecl,把this指针作为最后一个参数弹入。

        __fastcall和__thiscall有时候很难区分开。__fastcall是把前2个DWORD类型的参数用ecx,edx传递,其它的继续push进栈里。

        假设函数声明为:

        void __fastcall testfunc(int x,int y);

        那么调用这个函数的代码testfunc(3,4)就会被编译成

        mov edx,4

        mov ecx,3

        call testfunc


        "/GS" : Buffer Security Check

        如果编译的时候加了"/GS"参数,并且开启了优化(如Release版本),那么对于某些可能会遭受缓存区溢出攻击的函数,编译器在生成代码的时候,会在函数局部变量的最末尾加一个4字节的cookie。如:

        push ebp

        mov ebp, esp

        sub esp, 214h

        mov eax, ___security_cookie ; random value, initialized at module startup

        xor eax, ebp ; XOR it with the current base pointer

        mov [ebp+var_4], eax ; store the cookie

        ___security_cookie是一个随机数,在程序启动的时候由CRT初始化,此处把当前的栈指针(ebp)和这个cookie作XOR运算,然后在return之前再检查一遍:

        mov ecx, [ebp+var_4] ; get the cookie from the stack

        xor ecx, ebp ; XOR the cookie with the current base pointer

        call __security_check_cookie ; check the cookie

        leave

        retn 0Ch

        __security_check_cookie是一个__fastcall,代码如下:

    void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie)
    {
        /* x86 version written in asm to preserve all regs */
        __asm {
            cmp ecx, __security_cookie
                jne failure
                rep ret /* REP to avoid AMD branch prediction penalty */
    failure:
            jmp __report_gsfailure
        }
    }

        就是简单的做下比较,不正确则终止。

    原图已失效

    同分类推荐文章

    1. 科技爱好者周刊(第 401 期):如何赚到10亿美元 (2026-06-26 08:05:38)
    2. 如何做决策 - 从 Go 的一个 issue 说起 (2026-06-26 08:00:00)
    3. Seven Player:Windows上播放115网盘视频的增强工具 (2026-06-09 00:06:47)

    查看更多 开发者 文章 →

    建议继续学习

    1. 如何学好C++语言 (累计阅读 10,456)
    2. Emacs配置C/C++-mode的代码智能提示和自动补全 (累计阅读 10,417)
    3. colortail,让 tail 命令绚丽起来 (累计阅读 10,262)
    4. 在C++中实现foreach循环,比for_each更简洁! (累计阅读 9,507)
    5. 几个内存相关面试题(c/c++) (累计阅读 9,450)
    6. 关于使用STL的红黑树map还是hashmap的问题 (累计阅读 8,877)
    7. 浅析C++多线程内存模型 (累计阅读 8,805)
    8. C++ 多线程编程总结 (累计阅读 8,100)
    9. 使用gdb调试运行时的程序小技巧 (累计阅读 7,210)
    10. 在C++里写一个不能被继承的类 (累计阅读 6,582)