编程与调试 -- 调用约定 (Calling Conventions) C++ 函数名修饰 (Name Mangling)

调用约定 (Calling Conventions) (__cdecl、__stdcall、__fastcall) C++ 函数名修饰 (Name Mangling)。

代码防护思路:配置文件简单加密,避免字符串硬编码。

  • 关键函数 / 文件采用无意义名字
  • 避免明文存储,配置文件 hash 化
  • 尽量减少无用提示
  • 无关代码注释删除(特别是调试日志代码)

VS “dumpbin -exports ***.dll”。

调用约定

__cdecl

格式为 function。

__stdcall

格式为 _function@number。

__fastcall

格式为 @function@number。

C++ 函数名修饰(Decorated Names,Name Mangling)

  • __cdecl,@@YA
  • __stdcall,@@YG
  • __fastcall,@@YI

dll 导出函数方式比较

C++ 编译

对于 C++ 编译器的函数名修饰规则:不管 __cdecl, __fastcall 还是 __stdcall 调用方式,函数修饰名都是以 "?" 开始,后面是函数在名字,再后面是函数返回类型和参数类型按照代号拼出的参数表。 对于 __stdcall 方式,参数表的开始标示是 "@@YG”,对于 __cdecl 方式则是 "@@YA”,对于 __fastcall 方式则是 "@@YI”。 参数表后以 "@Z” 标示整个名字的结束,如果该函数无参数,则以 "Z” 标识结束。

  • __declspec(dllexport) int add(int, int);
  • __declspec(dllexport) int __cdecl add(int, int);
  • __declspec(dllexport) int __stdcall add(int, int);
  • __declspec(dllexport) int __fastcall add(int, int);

C 编译

__stdcall 和 __fastcall 编译出来的函数名还是和原始的函数名不同。 就拿 __stdcall 来说,它以 C 编译导出的时候,会在函数前面加入下划线,并在函数后面加入 @ 和参数总大小的字节数。

  • extern "C" __declspec(dllexport) int add(int, int);
  • extern "C" __declspec(dllexport) int __cdecl add(int, int);
  • extern "C" __declspec(dllexport) int __stdcall add(int, int);
  • extern "C" __declspec(dllexport) int __fastcall add(int, int);

Source

Tool to demangle C++ symbols .

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <cxxabi.h>

static void demangle(const char* mangled_name, bool quiet) {
    int status = 0;
    const char* realname = abi::__cxa_demangle(mangled_name, 0, 0, &status);
    switch (status) {
    case 0:
        if (quiet) {
            puts(realname);
        }
        else {
            printf("%s  %s\n", realname, mangled_name);
        }
        break;
    case -1:
        printf("FAIL: failed to allocate memory while demangling %s\n",
            mangled_name);
        break;
    case -2:
        printf("FAIL: %s is not a valid name under the C++ ABI mangling rules\n",
            mangled_name);
        break;
    default:
        printf("FAIL: some other unexpected error: %d\n", status);
        break;
    }
    free((void*)realname);
}

内部源码实现:Demangler for MSVC symbols

附件下载

一般本机上都有,用 Everything 检索本地:undname.exe。

x86 vs x64

template <class R>
DLLTEST_API void create_func(R(_stdcall* func_ptr)(void)) {

}
template <class R>
DLLTEST_API void create_func(R(_cdecl* func_ptr)(void)) {

}

这两个函数编译出来,x86:

No 	 Address 	 Hint 	 Ordinal 	  Name
1.	 $11159 	 - 	 1 	 ??$create_func@X@@YAXP6AXXZ@Z
2.	 $112FD 	 1 	 2 	 ??$create_func@X@@YAXP6GXXZ@Z

x64 是编译错误:

严重性 	 代码 	 说明 	 项目 	 文件 	 行 	 禁止显示状态
错误 	C2995	“void create_func(R (__cdecl *)(void))”: 函数模板已经定义

在设计调用约定时,x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混乱。在 Win64 中,只有一个本机调用约定。 而 __cdecl 之类的修饰符都被编译器忽略。除此之外,减少调用约定行为还为可调试性带来了好处。 from

原来 64 位平台下只有一种变形的 __fastcall 的调用约定,前 4 参数则先放入 ecx、edx、r8、r9 寄存器,更多的参数放入栈区。这个时候我们要注意的是,在 64 位下,系统还是为前 4 个参数预留了栈区空间(每个栈空间大小为 8 字节,共 32 字节大小),然后将基存器的值放入所预留的栈区空间。为什么系统要多此一举呢?我们都知道寄存器传递参数速度要远大于栈区传值,而将寄存器中的值再放入栈区预留空间,这是为了防止在传递参数的过程中,寄存器需要接收其他的值而导致参数无法传递,或者其他值无法接收的情况。 from

查看一下:

"MSVC\bin\Hostx64\x64\dumpbin.exe" /exports E:\kpdf\DllTest64.dll
    ordinal hint RVA      name
          1    0 00011172 ??$create_func@X@@YAXP6AXXZ@Z = @ILT+365(??$create_func@X@@YAXP6AXXZ@Z)
          1    0 00011172 ??$create_func@X@@YAXP6AXXZ@Z = @ILT+365(??$create_func@X@@YAXP6AXXZ@Z)

          1    0 000110F0 ??$create_func1@X@@YAXP6AXXZ@Z = @ILT+235(??$create_func1@X@@YAXP6AXXZ@Z)
          2    1 0001100A ??$create_func2@X@@YAXP6AXXZ@Z = @ILT+5(??$create_func2@X@@YAXP6AXXZ@Z)

          1    0 000110F0 ??$create_func1@X@@YAXP6AXXZ@Z = @ILT+235(??$create_func1@X@@YAXP6AXXZ@Z)
          2    1 0001100A ??$create_func2@X@@YAXP6AXXZ@Z = @ILT+5(??$create_func2@X@@YAXP6AXXZ@Z)

          1    0 000110EB ??$create_func3@X@@YAXP6AXXZ@Z = @ILT+230(??$create_func3@X@@YAXP6AXXZ@Z)
          2    1 00011131 ??$create_func4@X@@YAXP6AXXZ@Z = @ILT+300(??$create_func4@X@@YAXP6AXXZ@Z)

你会发现两种写法,生成的 签名是一样的,所以链接肯定不成功。

Refs


参考资料快照
参考资料快照

本文短链接:
If you have any questions or feedback, please reach out .