Name Mangling(C++)
Author: Chaos Lee
Date: 2012/05/06
大型程序是通过多个模块构建而成,模块之间的关系由makefile来描述。对于由C++语言编制的大型程序而言,也是符合这个规则。
程序的构建过程一般为:各个源文件分别编译,形成目标文件。多个目标文件通过链接器形成最终的可执行程序。显然,从某种程度上说,编译器的输出是链接器的输入,链接器要对编译器的输出做二次加工。从通信的角度看,这两个程序需要一定的协议来规范符号的组织格式。这就是Name Mangling产生的根本原因。
函数名字不同 || 参数数量不同||某个参数的类型不同
那么区分函数的时候,应该充分考虑参数数量和参数类型这两种语义信息,这样才能为却分不同的函数保证充分性。
总的来说,Name Mangling就是一种规范编译器和链接器之间用于通信的符号表表示方法的协议,其目的在于按照程序的语言规范,使符号具备足够多的语义信息以保证链接过程准确无误的进行。
简单的实验
Name Mangling会带了一个很常见的负面效应,就是C语言的程序调用C++的程序时,会比较棘手。因为C语言中的Name Mangling很简单,不如C++中这么复杂。下面的代码用于演示这两种不同点:
- /*
- * simple_test.c
- * a demo to show that different name mangling technology in C++ and C
- * Author: Chaos Lee
- */
- #include<stdio.h>
- int rect_area(int x1,int x2,int y1,int y2)
- {
- return (x2-x1) * (y2-y1);
- }
- int elipse_area(int a,int b)
- {
- return 3.14 * a * b;
- }
- int main(int argc,char *argv[])
- {
- int x1 = 10, x2 = 20, y1 = 30, y2 = 40;
- int a = 3,b=4;
- int result1 = rect_area(x1,x2,y1,y2);
- int result2 = elipse_area(a,b);
- return 0;
- }
- [lichao@sg01 name_mangling]$ gcc -c simple_test.c
- [lichao@sg01 name_mangling]$ nm simple_test.o
- 0000000000000027 T elipse_area
- 0000000000000051 T main
- 0000000000000000 T rect_area
从上面的输出结果上,可以看到使用gcc编译后对应的符号表中,几乎没有对函数做任何修饰。接下来使用g++编译:
- [lichao@sg01 name_mangling]$ nm simple_test.o
- 0000000000000028 T _Z11elipse_areaii
- 0000000000000000 T _Z9rect_areaiiii
- U __gxx_personality_v0
- 0000000000000052 T main
显然,g++编译器对符号的改编比较复杂。所以,如果一个由C语言编译的目标文件中调用了C++中实现的函数,肯定会出错的,因为符号不匹配。
简单对_Z9rect_areaiiii做个介绍:
l 接下来的部分和网络协议很类似。9表示接下来的要表示的一个字符串对象的长度(现在知道为什么不让用数字作为标识符的开头了吧?)所以rect_area这九个字符就作为函数的名称被识别出来了。
l 接下来的每个小写字母表示参数的类型,i表示int类型。小写字母的数量表示函数的参数列表中参数的数量。
l 所以,在符号中集成了用于区分不同重载函数的足够的语义信息。
如果要在C语言中调用C++中的函数该怎么做?这时候可以使用C++的关键字extern “C”。对应代码如下:
- /*
- * simple_test.c
- * a demo to show that different name mangling technology in C++ and C
- * Author: Chaos Lee
- */
- #include<stdio.h>
- #ifdef __cplusplus
- extern "C" {
- #endif
- int rect_area(int x1,int x2,int y1,int y2)
- {
- return (x2-x1) * (y2-y1);
- }
- int elipse_area(int a,int b)
- {
- return (int)(3.14 * a * b);
- }
- #ifdef __cplusplus
- }
- #endif
- int main(int argc,char *argv[])
- {
- int x1 = 10, x2 = 20, y1 = 30, y2 = 40;
- int a = 3,b=4;
- int result1 = rect_area(x1,x2,y1,y2);
- int result2 = elipse_area(a,b);
- return 0;
- }
下面是使用gcc编译的结果:
- [lichao@sg01 name_mangling]$ gcc -c simple_test.c
- [lichao@sg01 name_mangling]$ nm simple_test.o
- 0000000000000027 T elipse_area
- 0000000000000051 T main
- 0000000000000000 T rect_area
在使用g++编译一次:
- [lichao@sg01 name_mangling]$ g++ -c simple_test.c
- [lichao@sg01 name_mangling]$ nm simple_test.o
- U __gxx_personality_v0
- 0000000000000028 T elipse_area
- 0000000000000052 T main
- 0000000000000000 T rect_area
可见,使用extern “C”关键字之后,符号按照C语言的格式来组织了。
下面是一个简单的例子:
- /*
- * libc_test.c
- * a demo program to show that how the standard C
- * library are compiled when encountering a C++ compiler
- */
- #include<stdio.h>
- int main(int argc,char * argv[])
- {
- puts("hello world.\n");
- return 0;
- }
- [lichao@sg01 name_mangling]$ g++ -E libc_test.c | grep 'puts'
- extern int fputs (__const char *__restrict __s, FILE *__restrict __stream);
- extern int puts (__const char *__s);
- extern int fputs_unlocked (__const char *__restrict __s,
- puts("hello world.\n");
搜索一下 extern “C”试下
- [lichao@sg01 name_mangling]$ g++ -E libc_test.c | grep 'extern "C"'
- extern "C" {
- extern "C" {
这是由于extern “C”可以使用{}的形式将其作用域内的函数全部声明为C语言可调用的接口形式。
标准
“编译器由于内部实现的不同而不同,内部实现包括对象在内存中的布局,继承的实现,虚函数调用处理等等。所以如果将name mangling标准化了,不错,你的程序确实能够链接成功,但是运行肯定要崩的。恰恰是因为这个原因,ARM鼓励为同一平台提供的不同编译器应该使用不同的name mangling方式。这样在编译的时候,不兼容的库就会被检测到,而不至于链接时虽然通过了,但是运行时崩溃了。”
显然,这是基于“运行时崩溃比链接时失败的代价更大”这个原则而考虑的。
GCC的name mangling
another”
以下为内置的编码类型:
- Builtin types encoding
- <builtin-type> ::= v # void
- ::= w # wchar_t
- ::= b # bool
- ::= c # char
- ::= a # signed char
- ::= h # unsigned char
- ::= s # short
- ::= t # unsigned short
- ::= i # int
- ::= j # unsigned int
- ::= l # long
- ::= m # unsigned long
- ::= x # long long, __int64
- ::= y # unsigned long long, __int64
- ::= n # __int128
- ::= o # unsigned __int128
- ::= f # float
- ::= d # double
- ::= e # long double, __float80
- ::= g # __float128
- ::= z # ellipsis
- ::= u <source-name> # vendor extended type
操作符编码:
- <operator-name> ::= nw # new
- ::= na # new[]
- ::= dl # delete
- ::= da # delete[]
- ::= ps # + (unary)
- ::= ng # - (unary)
- ::= ad # & (unary)
- ::= de # * (unary)
- ::= co # ~
- ::= pl # +
- ::= mi # -
- ::= ml # *
- ::= dv # /
- ::= rm # %
- ::= an # &
- ::= or # |
- ::= eo # ^
- ::= aS # =
- ::= pL # +=
- ::= mI # -=
- ::= mL # *=
- ::= dV # /=
- ::= rM # %=
- ::= aN # &=
- ::= oR # |=
- ::= eO # ^=
- ::= ls # <<
- ::= rs # >>
- ::= lS # <<=
- ::= rS # >>=
- ::= eq # ==
- ::= ne # !=
- ::= lt # <
- ::= gt # >
- ::= le # <=
- ::= ge # >=
- ::= nt # !
- ::= aa # &&
- ::= oo # ||
- ::= pp # ++
- ::= mm # --
- ::= cm # ,
- ::= pm # ->*
- ::= pt # ->
- ::= cl # ()
- ::= ix # []
- ::= qu # ?
- ::= st # sizeof (a type)
- ::= sz # sizeof (an expression)
- ::= cv <type> # (cast)
- ::= v <digit> <source-name> # vendor extended operator
类型编码:
- <type> ::= <CV-qualifiers> <type>
- ::= P <type> # pointer-to
- ::= R <type> # reference-to
- ::= O <type> # rvalue reference-to (C++0x)
- ::= C <type> # complex pair (C 2000)
- ::= G <type> # imaginary (C 2000)
- ::= U <source-name> <type> # vendor extended type qualifier
下面是一段简单的代码:
- /*
- * Author: Chaos Lee
- * Description: A simple demo to show how the rules used to mangle functions' names work
- * Date:2012/05/06
- */
- #include<iostream>
- #include<string>
- using namespace std;
- int test_func(int & tmpInt,const char * ptr,double dou,string str,float f)
- {
- return 0;
- }
- int main(int argc,char * argv[])
- {
- char * test="test";
- int intNum = 10;
- double dou = 10.012;
- string str="str";
- float f = 1.2;
- test_func(intNum,test,dou,str,f);
- return 0;
- }
- [lichao@sg01 name_mangling]$ g++ -c func.cpp
- [lichao@sg01 name_mangling]$ nm func.cpp
- nm: func.cpp: File format not recognized
- [lichao@sg01 name_mangling]$ nm func.o
- 0000000000000060 t _GLOBAL__I__Z9test_funcRiPKcdSsf
- U _Unwind_Resume
- 0000000000000022 t _Z41__static_initialization_and_destruction_0ii
- 0000000000000000 T _Z9test_funcRiPKcdSsf
- U _ZNSaIcEC1Ev
- U _ZNSaIcED1Ev
- U _ZNSsC1EPKcRKSaIcE
- U _ZNSsC1ERKSs
- U _ZNSsD1Ev
- U _ZNSt8ios_base4InitC1Ev
- U _ZNSt8ios_base4InitD1Ev
- 0000000000000000 b _ZSt8__ioinit
- U __cxa_atexit
- U __dso_handle
- U __gxx_personality_v0
- 0000000000000076 t __tcf_0
- 000000000000008e T main
l Ri,表示对整型变量的引用
l PKc:表示const char *指针
l
Ss:目前还没有找到原因。先留着~
l f:表示浮点型
name demangling
- [lichao@sg01 name_mangling]$ c++filt _Z9test_funcRiPKcdSsf
- test_func(int&, char const*, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float)
一般更常用的方法为:
- [lichao@sg01 name_mangling]$ nm func.o | c++filt
- 0000000000000060 t global constructors keyed to _Z9test_funcRiPKcdSsf
- U _Unwind_Resume
- 0000000000000022 t __static_initialization_and_destruction_0(int, int)
- 0000000000000000 T test_func(int&, char const*, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float)
- U std::allocator<char>::allocator()
- U std::allocator<char>::~allocator()
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
- U std::ios_base::Init::Init()
- U std::ios_base::Init::~Init()
- 0000000000000000 b std::__ioinit
- U __cxa_atexit
- U __dso_handle
- U __gxx_personality_v0
- 0000000000000076 t __tcf_0
- 000000000000008e T main
另外使用nm命令也可以demangle符号,使用选项-C即可,例如:
- [lichao@sg01 name_mangling]$ nm -C func.o
- 0000000000000060 t global constructors keyed to _Z9test_funcRiPKcdSsf
- U _Unwind_Resume
- 0000000000000022 t __static_initialization_and_destruction_0(int, int)
- 0000000000000000 T test_func(int&, char const*, double, std::string, float)
- U std::allocator<char>::allocator()
- U std::allocator<char>::~allocator()
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&)
- U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
- U std::ios_base::Init::Init()
- U std::ios_base::Init::~Init()
- 0000000000000000 b std::__ioinit
- U __cxa_atexit
- U __dso_handle
- U __gxx_personality_v0
- 0000000000000076 t __tcf_0
- 000000000000008e T main
- namespace abi {
- extern "C" char* __cxa_demangle (const char* mangled_name,
- char* buf,
- size_t* n,
- int* status);
- }
用于将mangled_name所指向的mangled进行demangle并将结果存放在buf中,n为buf的大小。status存放函数执行的结果,返回值为0表示执行成功。
下面是使用这个接口函数进行demangle的例子:
- /*
- * Author: Chaos Lee
- * Description: Employ __cxa_demangle to demangle a mangling function name.
- * Date:2012/05/06
- *
- */
- #include<iostream>
- #include<cxxabi.h>
- using namespace std;
- using namespace abi;
- int main(int argc,char *argv[])
- {
- const char * mangled_string = "_Z9test_funcRiPKcdSsf";
- char buffer[100];
- int status;
- size_t n=100;
- __cxa_demangle(mangled_string,buffer,&n,&status);
- cout<<buffer<<endl;
- cout<<status<<endl;
- return 0;
- }
测试结果:
- [lichao@sg01 name_mangling]$ g++ cxa_demangle.cpp -o cxa_demangle
- [lichao@sg01 name_mangling]$ ./cxa_demangle
- test_func(int&, char const*, double, std::string, float)
- 0
name mangling与黑客
l 使用demangling可以破解动态链接库中的没有公开的API