Name Mangling in C++

 

Name Mangling(C++)
Author: Chaos Lee
Date: 2012/05/06

摘要:詳細介紹了C++中的Name Mangling的原理和gcc中對應的實現,經過程序代碼和nm c++filt等工具來驗證這些原理。對於詳細瞭解程序的連接過程有必定的幫助。ios

Name Mangling概述c++

大型程序是經過多個模塊構建而成,模塊之間的關係由makefile來描述。對於由C++語言編制的大型程序而言,也是符合這個規則。
程序的構建過程通常爲:各個源文件分別編譯,造成目標文件。多個目標文件經過連接器造成最終的可執行程序。顯然,從某種程度上說,編譯器的輸出是連接器的輸入,連接器要對編譯器的輸出作二次加工。從通訊的角度看,這兩個程序須要必定的協議來規範符號的組織格式。這就是Name Mangling產生的根本緣由。

C++的語言特性比C豐富的多,C++支持的函數重載功能是須要Name Mangling技術的最直接的例子。對於重載的函數,不能僅依靠函數名稱來區分不一樣的函數,由於C++中重載函數的區分是創建在如下規則上的:git

函數名字不一樣 || 參數數量不一樣||某個參數的類型不一樣
那麼區分函數的時候,應該充分考慮參數數量和參數類型這兩種語義信息,這樣才能爲卻分不一樣的函數保證充分性。

固然,C++還有不少其餘的地方須要Name Mangling,如namespace, class, template等等。sql

總的來講,Name Mangling就是一種規範編譯器和連接器之間用於通訊的符號表表示方法的協議,其目的在於按照程序的語言規範,使符號具有足夠多的語義信息以保證連接過程準確無誤的進行。
簡單的實驗
Name Mangling會帶了一個很常見的負面效應,就是C語言的程序調用C++的程序時,會比較棘手。由於C語言中的Name Mangling很簡單,不如C++中這麼複雜。下面的代碼用於演示這兩種不一樣點:
 
  
  
           
  
  
  1. /* 
  2. * simple_test.c 
  3. * a demo to show that different name mangling technology in C++ and C 
  4.  
  5. * Author: Chaos Lee 
  6.  
  7. */ 
  8.   
  9. #include<stdio.h> 
  10.   
  11. int rect_area(int x1,int x2,int y1,int y2) 
  12.  
  13.         return (x2-x1) * (y2-y1); 
  14.   
  15. int elipse_area(int a,int b) 
  16.  
  17.         return 3.14 * a * b; 
  18.   
  19. int main(int argc,char *argv[]) 
  20.  
  21.         int x1 = 10, x2 = 20, y1 = 30, y2 = 40; 
  22.         int a = 3,b=4; 
  23.         int result1 = rect_area(x1,x2,y1,y2); 
  24.         int result2 = elipse_area(a,b); 
  25.         return 0; 

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ gcc -c simple_test.c 
  2.  
  3. [lichao@sg01 name_mangling]$ nm simple_test.o 
  4.  
  5. 0000000000000027 T elipse_area 
  6.  
  7. 0000000000000051 T main 
  8.  
  9. 0000000000000000 T rect_area 
從上面的輸出結果上,能夠看到使用gcc編譯後對應的符號表中,幾乎沒有對函數作任何修飾。接下來使用g++編譯:
 
  
  
           
  
  
  1.  [lichao@sg01 name_mangling]$ nm simple_test.o 
  2. 0000000000000028 T _Z11elipse_areaii 
  3.  
  4. 0000000000000000 T _Z9rect_areaiiii 
  5.  
  6.                  U __gxx_personality_v0 
  7. 0000000000000052 T main 
顯然,g++編譯器對符號的改編比較複雜。因此,若是一個由C語言編譯的目標文件中調用了C++中實現的函數,確定會出錯的,由於符號不匹配。
簡單對_Z9rect_areaiiii作個介紹:

l C++語言中規定 :如下劃線並緊挨着大寫字母開頭或者以兩個下劃線開頭的標識符都是C++語言中保留的標示符。因此_Z9rect_areaiiii是保留的標識符,g++編譯的目標文件中的符號使用_Z開頭(C99標準)。express

l 接下來的部分和網絡協議很相似。9表示接下來的要表示的一個字符串對象的長度(如今知道爲何不讓用數字做爲標識符的開頭了吧?)因此rect_area這九個字符就做爲函數的名稱被識別出來了。
l 接下來的每一個小寫字母表示參數的類型,i表示int類型。小寫字母的數量表示函數的參數列表中參數的數量。
l 因此,在符號中集成了用於區分不一樣重載函數的足夠的語義信息。
若是要在C語言中調用C++中的函數該怎麼作?這時候可使用C++的關鍵字extern 「C」。對應代碼以下:
 
  
  
           
  
  
  1. /* 
  2. * simple_test.c 
  3. * a demo to show that different name mangling technology in C++ and C 
  4.  
  5. * Author: Chaos Lee 
  6.  
  7. */ 
  8.   
  9. #include<stdio.h> 
  10.   
  11. #ifdef __cplusplus 
  12.  
  13. extern "C" { 
  14.  
  15. #endif 
  16. int rect_area(int x1,int x2,int y1,int y2) 
  17.  
  18.         return (x2-x1) * (y2-y1); 
  19.   
  20. int elipse_area(int a,int b) 
  21.  
  22.         return (int)(3.14 * a * b); 
  23.   
  24. #ifdef __cplusplus 
  25.  
  26. #endif 
  27.   
  28. int main(int argc,char *argv[]) 
  29.  
  30.         int x1 = 10, x2 = 20, y1 = 30, y2 = 40; 
  31.         int a = 3,b=4; 
  32.         int result1 = rect_area(x1,x2,y1,y2); 
  33.         int result2 = elipse_area(a,b); 
  34.         return 0; 
下面是使用gcc編譯的結果:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ gcc -c simple_test.c 
  2.  
  3. [lichao@sg01 name_mangling]$ nm simple_test.o 
  4.  
  5. 0000000000000027 T elipse_area 
  6.  
  7. 0000000000000051 T main 
  8.  
  9. 0000000000000000 T rect_area 
在使用g++編譯一次:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ g++ -c simple_test.c 
  2.  
  3. [lichao@sg01 name_mangling]$ nm simple_test.o 
  4.  
  5.                  U __gxx_personality_v0 
  6.  
  7. 0000000000000028 T elipse_area 
  8.  
  9. 0000000000000052 T main 
  10.  
  11. 0000000000000000 T rect_area 
可見,使用extern 「C」關鍵字以後,符號按照C語言的格式來組織了。

事實上,C標準庫中使用了大量的extern 「C」關鍵字,由於C標準庫也是能夠用C++編譯器編譯的,可是要確保編譯以後仍然保持C的接口而不是C++的接口(由於是C標準庫),因此須要使用extern 「C」關鍵字。網絡

下面是一個簡單的例子:
 
  
  
           
  
  
  1. /* 
  2. * libc_test.c 
  3. * a demo program to show that how the standard C 
  4.  
  5. * library are compiled when encountering a C++ compiler 
  6.  
  7. */ 
  8. #include<stdio.h> 
  9. int main(int argc,char * argv[]) 
  10.  
  11.         puts("hello world.\n"); 
  12.         return 0; 

搜索一下puts,咱們並無看到extern 「C」.奇怪麼?ide

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ g++ -E libc_test.c | grep 'puts' 
  2.  
  3. extern int fputs (__const char *__restrict __s, FILE *__restrict __stream); 
  4.  
  5. extern int puts (__const char *__s); 
  6.  
  7. extern int fputs_unlocked (__const char *__restrict __s, 
  8.  
  9.  puts("hello world.\n"); 
搜索一下 extern 「C」試下

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ g++ -E libc_test.c | grep 'extern "C"' 
  2.  
  3. extern "C" { 
  4.  
  5. extern "C" { 
這是因爲extern 「C」可使用{}的形式將其做用域內的函數所有聲明爲C語言可調用的接口形式。
標準

不一樣編譯器使用不一樣的方式進行name mangling, 你可能會問爲何不將C++的 name mangling標準化,這樣就能實現各個編譯器之間的互操做了。事實上,在C++的FAQ列表上有對此問題的回答:函數

"Compilers differ as to how objects are laid out, how multiple inheritance is implemented, how virtual function calls are handled, and so on, so if the name mangling were made the same, your programs would link against libraries provided from other compilers but then crash when run. For this reason, the ARM (Annotated C++ Reference Manual) encourages compiler writers to make their name mangling different from that of other compilers for the same platform. Incompatible libraries are then detected at link time, rather than at run time."工具

「編譯器因爲內部實現的不一樣而不一樣,內部實現包括對象在內存中的佈局,繼承的實現,虛函數調用處理等等。因此若是將name mangling標準化了,不錯,你的程序確實可以連接成功,可是運行確定要崩的。偏偏是由於這個緣由,ARM鼓勵爲同一平臺提供的不一樣編譯器應該使用不一樣的name mangling方式。這樣在編譯的時候,不兼容的庫就會被檢測到,而不至於連接時雖然經過了,可是運行時崩潰了。」
顯然,這是基於「運行時崩潰比連接時失敗的代價更大」這個原則而考慮的。
GCC name mangling

GCC採用IA 64的name mangling方案,此方案定義於Intel IA64 standard ABI.在g++的FAQ列表中有如下一段話:
       "GNU C++ does not do name mangling in the same way as other C++ compilers.佈局

This means that object files compiled with one compiler cannot be used with

another」

GNU C++的name mangling方案和其餘C++編譯器方案不一樣,因此一種編譯器生成的目標文件並不能被另一種編譯器生成的目標文件使用。

如下爲內置的編碼類型:

 

  
  
           
  
  
  1. Builtin types encoding 
  2.  
  3.   <builtin-type> ::= v  # void 
  4.                  ::= w  # wchar_t 
  5.                  ::= b  # bool 
  6.                  ::= c  # char 
  7.                  ::= a  # signed char 
  8.                  ::= h  # unsigned char 
  9.                  ::= s  # short 
  10.                  ::= t  # unsigned short 
  11.                  ::= i  # int 
  12.                  ::= j  # unsigned int 
  13.                  ::= l  # long 
  14.                  ::= m  # unsigned long 
  15.                  ::= x  # long long, __int64 
  16.                  ::= y  # unsigned long long, __int64 
  17.                  ::= n  # __int128 
  18.                  ::= o  # unsigned __int128 
  19.                  ::= f  # float 
  20.                  ::= d  # double 
  21.                  ::= e  # long double, __float80 
  22.                  ::= g  # __float128 
  23.                  ::= z  # ellipsis 
  24.                  ::= u <source-name>    # vendor extended type 
操做符編碼:

Operator encoding

 
  
  
           
  
  
  1. <operator-name> ::= nw # new           
  2.                  ::= na        # new[] 
  3.                  ::= dl        # delete        
  4.                  ::= da        # delete[]      
  5.                  ::= ps        # + (unary) 
  6.                  ::= ng        # - (unary)     
  7.                  ::= ad        # & (unary)     
  8.                  ::= de        # * (unary)     
  9.                  ::= co        # ~             
  10.                  ::= pl        # +             
  11.                  ::= mi        # -   
  12.  
  13.                                   ::= ml        # *             
  14.  
  15.                  ::= dv        # /             
  16.                  ::= rm        # %             
  17.                  ::= an        # &             
  18.                  ::= or        # |             
  19.                  ::= eo        # ^             
  20.                  ::= aS        # =             
  21.                  ::= pL        # +=            
  22.                  ::= mI        # -=            
  23.                  ::= mL        # *=            
  24.                  ::= dV        # /=            
  25.                  ::= rM        # %=            
  26.                  ::= aN        # &=            
  27.                  ::= oR        # |=            
  28.                  ::= eO        # ^=            
  29.                  ::= ls        # <<            
  30.                  ::= rs        # >>            
  31.                  ::= lS        # <<=           
  32.                  ::= rS        # >>=           
  33.                  ::= eq        # ==            
  34.                  ::= ne        # !=            
  35.                  ::= lt        # <             
  36.                  ::= gt        # >             
  37.                  ::= le        # <=            
  38.                  ::= ge        # >=            
  39.                  ::= nt        # !             
  40.                  ::= aa        # &&            
  41.                  ::= oo        # ||            
  42.                  ::= pp        # ++            
  43.                  ::= mm        # --            
  44.                  ::= cm        # ,              
  45.                  ::= pm        # ->*           
  46.                  ::= pt        # ->            
  47.                  ::= cl        # ()            
  48.                  ::= ix        # []            
  49.                  ::= qu        # ?             
  50.                  ::= st        # sizeof (a type) 
  51.                  ::= sz        # sizeof (an expression) 
  52.                  ::= cv <type> # (cast)        
  53.  
  54.                  ::= v <digit> <source-name>   # vendor extended operator 
類型編碼:

 

  
  
           
  
  
  1. <type> ::= <CV-qualifiers> <type> 
  2.  
  3.          ::= P <type>   # pointer-to 
  4.          ::= R <type>   # reference-to 
  5.          ::= O <type>     # rvalue reference-to (C++0x) 
  6.          ::= C <type>   # complex pair (C 2000) 
  7.          ::= G <type>   # imaginary (C 2000) 
  8.          ::= U <source-name> <type>     # vendor extended type qualifier 
下面是一段簡單的代碼:
 
  
  
           
  
  
  1. /* 
  2. * Author: Chaos Lee 
  3.  
  4. * Description: A simple demo to show how the rules used to mangle functions' names work 
  5.  
  6. * Date:2012/05/06 
  7.  
  8. */ 
  9. #include<iostream> 
  10. #include<string> 
  11. using namespace std; 
  12.  
  13. int test_func(int & tmpInt,const char * ptr,double dou,string str,float f) 
  14.  
  15.         return 0; 
  16. int main(int argc,char * argv[]) 
  17.  
  18.         char * test="test"
  19.         int intNum = 10; 
  20.         double dou = 10.012; 
  21.         string str="str"
  22.         float f = 1.2; 
  23.         test_func(intNum,test,dou,str,f); 
  24.         return 0; 

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ g++ -c func.cpp 
  2.  
  3. [lichao@sg01 name_mangling]$ nm func.cpp 
  4.  
  5. nm: func.cpp: File format not recognized 
  6.  
  7. [lichao@sg01 name_mangling]$ nm func.o 
  8.  
  9. 0000000000000060 t _GLOBAL__I__Z9test_funcRiPKcdSsf 
  10.                  U _Unwind_Resume 
  11. 0000000000000022 t _Z41__static_initialization_and_destruction_0ii 
  12.  
  13. 0000000000000000 T _Z9test_funcRiPKcdSsf 
  14.  
  15.                  U _ZNSaIcEC1Ev 
  16.                  U _ZNSaIcED1Ev 
  17.                  U _ZNSsC1EPKcRKSaIcE 
  18.                  U _ZNSsC1ERKSs 
  19.                  U _ZNSsD1Ev 
  20.                  U _ZNSt8ios_base4InitC1Ev 
  21.                  U _ZNSt8ios_base4InitD1Ev 
  22. 0000000000000000 b _ZSt8__ioinit 
  23.  
  24.                  U __cxa_atexit 
  25.                  U __dso_handle 
  26.                  U __gxx_personality_v0 
  27. 0000000000000076 t __tcf_0 
  28.  
  29. 000000000000008e T main 

加粗的那行就是函數test_func通過name mangling以後的結果,其中:

l Ri,表示對整型變量的引用
l PKc:表示const char *指針
Ss :目前尚未找到緣由。先留着~
l f:表示浮點型
name demangling

C++的name mangling技術通常使得函數變得面目全非,而不少狀況下咱們在查看這些符號的時候並不須要看到這些函數name mangling以後的效果,而是想看看是否認義了某個函數,或者是否引用了某個函數,這對於咱們調試程序是很是有幫助的。

因此須要一種方法從name mangling以後的符號變換爲name mangling以前的符號,這個過程稱之爲name demangling.事實上有不少工具提供這些功能,最經常使用的就是c++file命令,c++filt命令接受一個name mangling以後的符號做爲輸入並輸出demangling以後的符號。例如:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ c++filt _Z9test_funcRiPKcdSsf 
  2.  
  3. test_func(int&, char const*, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float
通常更經常使用的方法爲:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ nm func.o | c++filt 
  2.  
  3. 0000000000000060 t global constructors keyed to _Z9test_funcRiPKcdSsf 
  4.  
  5.                  U _Unwind_Resume 
  6. 0000000000000022 t __static_initialization_and_destruction_0(intint
  7.  
  8. 0000000000000000 T test_func(int&, char const*, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float
  9.  
  10.                  U std::allocator<char>::allocator() 
  11.  
  12.                  U std::allocator<char>::~allocator() 
  13.  
  14.                  U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) 
  15.  
  16.                  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&) 
  17.  
  18.                  U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() 
  19.  
  20.                  U std::ios_base::Init::Init() 
  21.                  U std::ios_base::Init::~Init() 
  22. 0000000000000000 b std::__ioinit 
  23.  
  24.                  U __cxa_atexit 
  25.                  U __dso_handle 
  26.                  U __gxx_personality_v0 
  27. 0000000000000076 t __tcf_0 
  28.  
  29. 000000000000008e T main 
另外使用nm命令也能夠demangle符號,使用選項-C便可,例如:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ nm -C func.o 
  2.  
  3. 0000000000000060 t global constructors keyed to _Z9test_funcRiPKcdSsf 
  4.  
  5.                  U _Unwind_Resume 
  6. 0000000000000022 t __static_initialization_and_destruction_0(intint
  7.  
  8. 0000000000000000 T test_func(int&, char const*, double, std::string, float
  9.  
  10.                  U std::allocator<char>::allocator() 
  11.  
  12.                  U std::allocator<char>::~allocator() 
  13.  
  14.                  U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) 
  15.  
  16.                  U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) 
  17.  
  18.                  U std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() 
  19.  
  20.                  U std::ios_base::Init::Init() 
  21.                  U std::ios_base::Init::~Init() 
  22. 0000000000000000 b std::__ioinit 
  23.  
  24.                  U __cxa_atexit 
  25.                  U __dso_handle 
  26.                  U __gxx_personality_v0 
  27. 0000000000000076 t __tcf_0 
  28.  
  29. 000000000000008e T main 

又到了Last but not least important的時候了,還有一個特別重要的接口函數就是__cxa_demangle(),此函數的原型爲:

 
  
  
           
  
  
  1. namespace abi { 
  2. extern "C" char* __cxa_demangle (const char* mangled_name, 
  3.  
  4. char* buf, 
  5. size_t* n, 
  6. int* status); 
用於將mangled_name所指向的mangled進行demangle並將結果存放在buf中,n爲buf的大小。status存放函數執行的結果,返回值爲0表示執行成功。
下面是使用這個接口函數進行demangle的例子:
 
  
  
           
  
  
  1. /* 
  2. * Author: Chaos Lee 
  3.  
  4. * Description: Employ __cxa_demangle to demangle a mangling function name. 
  5.  
  6. * Date:2012/05/06 
  7.  
  8. * 
  9. */ 
  10. #include<iostream> 
  11. #include<cxxabi.h> 
  12. using namespace std; 
  13.  
  14. using namespace abi; 
  15.  
  16. int main(int argc,char *argv[]) 
  17.  
  18.         const char * mangled_string = "_Z9test_funcRiPKcdSsf"
  19.  
  20.         char buffer[100]; 
  21.         int status; 
  22.         size_t n=100; 
  23.         __cxa_demangle(mangled_string,buffer,&n,&status); 
  24.  
  25.         cout<<buffer<<endl; 
  26.         cout<<status<<endl; 
  27.         return 0; 
測試結果:

 

  
  
           
  
  
  1. [lichao@sg01 name_mangling]$ g++ cxa_demangle.cpp -o cxa_demangle 
  2.  
  3. [lichao@sg01 name_mangling]$ ./cxa_demangle 
  4.  
  5. test_func(int&, char const*, double, std::string, float
  6.  
name mangling 與***
l 使用demangling能夠破解動態連接庫中的沒有公開的API

l 編寫名稱爲name mangling接口函數,打開重複符號的編譯開關,能夠替換原來函數中連接函數的指向,從而改變程序的運行結果。

相關文章
相關標籤/搜索