c c++函数名编译符号修饰符

前言

函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串。用来指明函数的定义或原型。

LINK程序或其它工具有时须要指定函数的名字修饰来定位函数的正确位置。

多数情况下程序猿并不须要知道函数的名字修饰。LINK程序或其它工具会自己主动区分他们。

当然,在某些情况下须要指定函数的名字修饰,比如在C++程序中, 为了让LINK程序或其它工具可以匹配到正确的函数名字,就必须为重载函数和一些特殊的函数(如构造函数和析构函数)指定名字装饰。

还有一种须要指定函数的 名字修饰的情况是在汇编程序中调用C或C++的函数。

假设函数名字,调用约定。返回值类型或函数參数有不论什么改变,原来的名字修饰就不再有效,必须指定新的 名字修饰。

C和C++程序的函数在内部使用不同的名字修饰方式,以下将分别介绍这两种方式

1、C编译器的函数名修饰规则

​ 对于__stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其參数的字节数。比如 _functionname@number。

2. C++编译器的函数名修饰规则

无论 __cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”開始,后面紧跟函数的名字。再后面是參数表的開始标识和 依照參数类型代号拼出的參数表。

參数表标识:

__stdcall方式,是“@@YG”;

__cdecl方式,是“@@YA”;

__fastcall方式,是“@@YI;

参数类型 拼写代号
void X
char D
unsigned char E
short F
int H
unsigned int I
long J
unsigned long(DWORD) K
float M
double N
bool _N
struct U
指针 PA
const指针 PB

PA or PB后面的代号表明指针类型。

假设同样类型的指针连续出现,以“0”取代,一 个“0”代表一次反复。

U表示结构类型。通常后跟结构体的类型名,用“@@”表示结构类型名的结束。

參数表后以“@Z”标识整个名字的结束。假设该函数无參数,则 以“Z”标识结束。

对于C++的类成员函数(其调用方式是thiscall)。函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和參数表之间插入以“@”字 符引导的类名。其次是參数表的開始标识不同,公有(public)成员函数的标识是“@@QAE,保护(protected)成员函数的标识是 “@@IAE,私有(private)成员函数的标识是“@@AAE,假设函数声明使用了constkeyword,则对应的标识应分别为 “@@QBE“@@IBE“@@ABE

假设參数类型是类实例的引用。则使用“AAV1”,对于const类型的引用。则使用“ABV1”

例子:
1
int Function1 (char *var1,unsigned long);

其函数修饰名为: ?Function1@@YGHPADK@Z

1
void Function2(); 

其函数修饰名则为?Function2@@YGXXZ

1
2
3
4
5
6
7
8
9
10
11
12
class CTest 
{
......
private:
void Function(int);
protected:
void CopyInfo(const CTest &src);
public:
long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);
long InsightClass(DWORD dwClass) const;
......
};

对于成员函数Function,其函数修饰名为?Function@CTest@@AAEXH@Z 对于成员函数CopyInfo,其函数修饰名为 “?CopyInfo@CTest@@IAEXABV1@@Z DrawText是一个比較复杂的函数声明。不仅有字符串參数。还有结构体參数和HDC 句柄參数。须要指出的是HDC实际上是一个HDC结构类型的指针,这个參数的表示就是“PAUHDC@@”,其完整的函数修饰名为 “?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。 对于InsightClass是一个共有的const函数。它的成员函数标识是“@@QBE”,完整的修饰名就是 “?InsightClass@CTest@@QBEJK@Z”。

不管是C函数名修饰方式还是C++函数名修饰方式均不改变输出函数名中的字符大写和小写。这和PASCAL调用约定不同,PASCAL约定输出的函数名无不论什么修饰且所有大写。

3、相关函数

1
2
3
4
5
6
DWORD IMAGEAPI UnDecorateSymbolName(
[in] PCSTR name,
[out] PSTR outputString,
[in] DWORD maxStringLength,
[in] DWORD flags
);

被包含于头文件<dbghelp.h>

参数说明:

1
2
3
4
5
6
7
8
9
10
11
[in] name:
//已修饰的 C++ 符号名。此名称能以始终为问号 (?) 的首字符鉴别。

[out] outputString
//指向字符串缓冲区的指针,该缓冲区接收未修饰的名字。

[in] maxStringLength
//outputString缓冲区的大小,为字符数。

[in] flags
//用于反修饰已修饰名称的方式的选项。此参数能为零或更多个下列值。

flag值:

含义
UNDNAME_32_BIT_DECODE
0x0800
反修饰 32 位已修饰名。
UNDNAME_COMPLETE
0x0000
启用完全的反修饰。
UNDNAME_NAME_ONLY
0x1000
只反修饰初等声明的名称。返回 [作用域::]名称 。不展开模板形参。
UNDNAME_NO_ACCESS_SPECIFIERS
0x0080
禁用成员的访问指定符的展开。
UNDNAME_NO_ALLOCATION_LANGUAGE
0x0010
禁用声明语言说明符的展开。
UNDNAME_NO_ALLOCATION_MODEL
0x0008
禁用声明模型的展开。
UNDNAME_NO_ARGUMENTS
0x2000
不反修饰函数参数。
UNDNAME_NO_CV_THISTYPE
0x0040
禁用初等声明的 this 类型上的 CodeView 修饰符的展开。
UNDNAME_NO_FUNCTION_RETURNS
0x0004
禁用初等声明的返回类型展开。
UNDNAME_NO_LEADING_UNDERSCORES
0x0001
从 Microsoft 关键字中移除前导下划线。
UNDNAME_NO_MEMBER_TYPE
0x0200
禁用成员的 static 或 virtual 属性的展开。
UNDNAME_NO_MS_KEYWORDS
0x0002
禁用 Microsoft 关键字的展开。
UNDNAME_NO_MS_THISTYPE
0x0020
禁用初等声明的 this 类型上的 Microsoft 关键字的展开。
UNDNAME_NO_RETURN_UDT_MODEL
0x0400
禁用用户定义类型返回的 Microsoft 模型的展开。
UNDNAME_NO_SPECIAL_SYMS
0x4000
不反修饰特殊名称,如 vtable 、 vcall 、 vector 和 metatype 等。
UNDNAME_NO_THISTYPE
0x0060
禁用 this 类型上的所有修饰符。
UNDNAME_NO_THROW_SIGNATURES
0x0100
禁用函数和函数指针的 throw 签名的展开。

返回值

若函数成功,则返回 UnDecoratedName 缓冲区中的字符数,不包括 NULL 终止符。

若函数失败,则返回值为零。欲取得额外的错误信息,需调用 GetLastError

若函数失败且返回零,则 UnDecoratedName 缓冲区的内容不确定。

例1

1
2
3
4
5
6
name = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"
UnDecorateSymbolName(name, outputString, 0x100u, 0)
cout<<outputString;
/*
private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
*/

1
__FUNCDNAME__

例2

1
2
3
4
5
6
7
char* __thiscall R0Pxx::My_Aut0_PWN(unsigned char*) {
std::cout << __FUNCDNAME__ << std::endl;
return 0;
}
/*
?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
*/

4、神奇的事

因为C的编译方式和C++有点不同,所以会导致同一个函数,各自在两种语言的的环境下名字修饰会不一样 例如:

long MakeFun(long lFun);在C下是_MakeFun@4,但是在CPP下则是MakeFun@@YGJJ@Z