天天看點

Name Mangling in C++

Name Mangling(C++)

Author: Chaos Lee

Date: 2012/05/06

摘要:詳細介紹了C++中的Name Mangling的原理和gcc中對應的實作,通過程式代碼和nm c++filt等工具來驗證這些原理。對于詳細了解程式的連結過程有一定的幫助。

<b>Name Mangling</b><b>概述</b>

大型程式是通過多個子產品建構而成,子產品之間的關系由makefile來描述。對于由C++語言編制的大型程式而言,也是符合這個規則。

程式的建構過程一般為:各個源檔案分别編譯,形成目标檔案。多個目标檔案通過連結器形成最終的可執行程式。顯然,從某種程度上說,編譯器的輸出是連結器的輸入,連結器要對編譯器的輸出做二次加工。從通信的角度看,這兩個程式需要一定的協定來規範符号的組織格式。這就是Name Mangling産生的根本原因。

C++的語言特性比C豐富的多,C++支援的函數重載功能是需要Name Mangling技術的最直接的例子。對于重載的函數,不能僅依靠函數名稱來區分不同的函數,因為C++中重載函數的區分是建立在以下規則上的:

函數名字不同 || 參數數量不同||某個參數的類型不同

那麼區分函數的時候,應該充分考慮參數數量和參數類型這兩種語義資訊,這樣才能為卻分不同的函數保證充分性。

當然,C++還有很多其他的地方需要Name Mangling,如namespace, class, template等等。

總的來說,Name Mangling就是一種規範編譯器和連結器之間用于通信的符号表表示方法的協定,其目的在于按照程式的語言規範,使符号具備足夠多的語義資訊以保證連結過程準确無誤的進行。

<b>簡單的實驗</b>

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&lt;stdio.h&gt; 

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 C++語言中規定 :以下劃線并緊挨着大寫字母開頭或者以兩個下劃線開頭的辨別符都是C++語言中保留的标示符。是以_Z9rect_areaiiii是保留的辨別符,g++編譯的目标檔案中的符号使用_Z開頭(C99标準)。

l 接下來的部分和網絡協定很類似。9表示接下來的要表示的一個字元串對象的長度(現在知道為什麼不讓用數字作為辨別符的開頭了吧?)是以rect_area這九個字元就作為函數的名稱被識别出來了。

l 接下來的每個小寫字母表示參數的類型,i表示int類型。小寫字母的數量表示函數的參數清單中參數的數量。

l 是以,在符号中內建了用于區分不同重載函數的足夠的語義資訊。

如果要在C語言中調用C++中的函數該怎麼做?這時候可以使用C++的關鍵字extern “C”。對應代碼如下:

#ifdef __cplusplus 

extern "C" { 

#endif 

        return (int)(3.14 * a * b); 

下面是使用gcc編譯的結果:

在使用g++編譯一次:

[lichao@sg01 name_mangling]$ g++ -c simple_test.c 

0000000000000028 T elipse_area 

可見,使用extern “C”關鍵字之後,符号按照C語言的格式來組織了。

事實上,C标準庫中使用了大量的extern “C”關鍵字,因為C标準庫也是可以用C++編譯器編譯的,但是要確定編譯之後仍然保持C的接口而不是C++的接口(因為是C标準庫),是以需要使用extern “C”關鍵字。

下面是一個簡單的例子:

* libc_test.c 

* a demo program to show that how the standard C 

* library are compiled when encountering a C++ compiler 

int main(int argc,char * argv[]) 

        puts("hello world.\n"); 

搜尋一下puts,我們并沒有看到extern “C”.奇怪麼?

[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”可以使用{}的形式将其作用域内的函數全部聲明為C語言可調用的接口形式。

<b>标準</b>

不同編譯器使用不同的方式進行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方式。這樣在編譯的時候,不相容的庫就會被檢測到,而不至于連結時雖然通過了,但是運作時崩潰了。”

顯然,這是基于“運作時崩潰比連結時失敗的代價更大”這個原則而考慮的。

<b>GCC</b><b>的</b><b>name mangling</b>

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++編譯器方案不同,是以一種編譯器生成的目标檔案并不能被另外一種編譯器生成的目标檔案使用。

以下為内置的編碼類型:

Builtin types encoding 

  &lt;builtin-type&gt; ::= 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 &lt;source-name&gt;    # vendor extended type 

操作符編碼:

Operator encoding

&lt;operator-name&gt; ::= nw # new           

                 ::= na        # new[] 

                 ::= dl        # delete        

                 ::= da        # delete[]      

                 ::= ps        # + (unary) 

                 ::= ng        # - (unary)     

                 ::= ad        # &amp; (unary)     

                 ::= de        # * (unary)     

                 ::= co        # ~             

                 ::= pl        # +             

                 ::= mi        # -   

                                  ::= ml        # *             

                 ::= dv        # /             

                 ::= rm        # %             

                 ::= an        # &amp;             

                 ::= or        # |             

                 ::= eo        # ^             

                 ::= aS        # =             

                 ::= pL        # +=            

                 ::= mI        # -=            

                 ::= mL        # *=            

                 ::= dV        # /=            

                 ::= rM        # %=            

                 ::= aN        # &amp;=            

                 ::= oR        # |=            

                 ::= eO        # ^=            

                 ::= ls        # &lt;&lt;            

                 ::= rs        # &gt;&gt;            

                 ::= lS        # &lt;&lt;=           

                 ::= rS        # &gt;&gt;=           

                 ::= eq        # ==            

                 ::= ne        # !=            

                 ::= lt        # &lt;             

                 ::= gt        # &gt;             

                 ::= le        # &lt;=            

                 ::= ge        # &gt;=            

                 ::= nt        # !             

                 ::= aa        # &amp;&amp;            

                 ::= oo        # ||            

                 ::= pp        # ++            

                 ::= mm        # --            

                 ::= cm        # ,              

                 ::= pm        # -&gt;*           

                 ::= pt        # -&gt;            

                 ::= cl        # ()            

                 ::= ix        # []            

                 ::= qu        # ?             

                 ::= st        # sizeof (a type) 

                 ::= sz        # sizeof (an expression) 

                 ::= cv &lt;type&gt; # (cast)        

                 ::= v &lt;digit&gt; &lt;source-name&gt;   # vendor extended operator 

類型編碼:

&lt;type&gt; ::= &lt;CV-qualifiers&gt; &lt;type&gt; 

         ::= P &lt;type&gt;   # pointer-to 

         ::= R &lt;type&gt;   # reference-to 

         ::= O &lt;type&gt;     # rvalue reference-to (C++0x) 

         ::= C &lt;type&gt;   # complex pair (C 2000) 

         ::= G &lt;type&gt;   # imaginary (C 2000) 

         ::= U &lt;source-name&gt; &lt;type&gt;     # vendor extended type qualifier 

下面是一段簡單的代碼:

* Description: A simple demo to show how the rules used to mangle functions' names work 

* Date:2012/05/06 

#include&lt;iostream&gt; 

#include&lt;string&gt; 

using namespace std; 

int test_func(int &amp; tmpInt,const char * ptr,double dou,string str,float f) 

        char * test="test"; 

        int intNum = 10; 

        double dou = 10.012; 

        string str="str"; 

        float f = 1.2; 

        test_func(intNum,test,dou,str,f); 

[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 

0000000000000076 t __tcf_0 

000000000000008e T main 

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

l Ri,表示對整型變量的引用

l PKc:表示const char *指針

l <b>Ss</b><b>:目前還沒有找到原因。先留着~</b>

l f:表示浮點型

<b>name demangling</b>

C++的name mangling技術一般使得函數變得面目全非,而很多情況下我們在檢視這些符号的時候并不需要看到這些函數name mangling之後的效果,而是想看看是否定義了某個函數,或者是否引用了某個函數,這對于我們調試程式是非常有幫助的。

是以需要一種方法從name mangling之後的符号變換為name mangling之前的符号,這個過程稱之為name demangling.事實上有很多工具提供這些功能,最常用的就是c++file指令,c++filt指令接受一個name mangling之後的符号作為輸入并輸出demangling之後的符号。例如:

[lichao@sg01 name_mangling]$ c++filt _Z9test_funcRiPKcdSsf 

test_func(int&amp;, char const*, double, std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, float) 

一般更常用的方法為:

[lichao@sg01 name_mangling]$ nm func.o | c++filt 

0000000000000060 t global constructors keyed to _Z9test_funcRiPKcdSsf 

0000000000000022 t __static_initialization_and_destruction_0(int, int) 

0000000000000000 T test_func(int&amp;, char const*, double, std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, float) 

                 U std::allocator&lt;char&gt;::allocator() 

                 U std::allocator&lt;char&gt;::~allocator() 

                 U std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;::basic_string(char const*, std::allocator&lt;char&gt; const&amp;) 

                 U std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;::basic_string(std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; const&amp;) 

                 U std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;::~basic_string() 

                 U std::ios_base::Init::Init() 

                 U std::ios_base::Init::~Init() 

0000000000000000 b std::__ioinit 

另外使用nm指令也可以demangle符号,使用選項-C即可,例如:

[lichao@sg01 name_mangling]$ nm -C func.o 

0000000000000000 T test_func(int&amp;, char const*, double, std::string, float) 

                 U std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;::basic_string(std::string const&amp;) 

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

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的例子:

* Description: Employ __cxa_demangle to demangle a mangling function name. 

#include&lt;cxxabi.h&gt; 

using namespace abi; 

        const char * mangled_string = "_Z9test_funcRiPKcdSsf"; 

        char buffer[100]; 

        int status; 

        size_t n=100; 

        __cxa_demangle(mangled_string,buffer,&amp;n,&amp;status); 

        cout&lt;&lt;buffer&lt;&lt;endl; 

        cout&lt;&lt;status&lt;&lt;endl; 

測試結果:

[lichao@sg01 name_mangling]$ g++ cxa_demangle.cpp -o cxa_demangle 

[lichao@sg01 name_mangling]$ ./cxa_demangle 

test_func(int&amp;, char const*, double, std::string, float) 

<b>name mangling</b><b>與黑客</b>

l 使用demangling可以破解動态連結庫中的沒有公開的API

l 編寫名稱為name mangling接口函數,打開重複符号的編譯開關,可以替換原來函數中連結函數的指向,進而改變程式的運作結果。

本文轉自hipercomer 51CTO部落格,原文連結:http://blog.51cto.com/hipercomer/855223

繼續閱讀