轉:https://www.cnblogs.com/robinex/p/7892795.html。
C/C++語言在編譯以后,函數的名字會被編譯器修改,改成編譯器內部的名字,這個名字會在鏈接的時候用到。
將C++源程序標識符(original C++ source identifier)轉換成C++ ABI標識符(C++ ABI identifier)的過程稱為mangle;相反的過程稱為demangle。
1 ABI
ABI是Application Binary Interface的簡稱。
C/C++發展的過程中,二進制兼容一直是個問題。不同編譯器廠商編譯的二進制代碼之間兼容性不好,甚至同一個編譯器的不同版本之間兼容性也不好。
之后,C擁有了統一的ABI,而C++由於其特性的復雜性以及ABI標准推進不力,一直沒有自己的ABI。
這就涉及到標識符的mangle問題。比如,C++源碼中的同一個函數名,不同的編譯器或不同的編譯器版本,編譯后的名稱可能會有不同。
每個編譯器都有一套自己內部的名字,比如對於linux下g++而言。以下是基本的方法:
每個方法都是以_Z開頭,對於嵌套的名字(比如名字空間中的名字或者是類中間的名字,比如Class::Func)后面緊跟N , 然后是各個名字空間和類的名字,每個名字前是名字字符的長度,再以E結尾。(如果不是嵌套名字則不需要以E結尾)。
比如對於_Z3foov 就是函數foo() , v 表示參數類型為void。又如N:C:Func 經過修飾后就是 _ZN1N1C4FuncE,這個函數名后面跟參數類型。 如果跟一個整型,那就是_ZN1N1C4FuncEi。
2 RTTI與type_info
C++在編譯時開啟RTTI(Run-Time Type Identification,通過運行時類型識別)特性時,可以在代碼中使用typeid操作符(當然還需要包含<typeinfo>),此符號可以對一個變量或者一個類名使用,返回一個type_info對象的引用。編譯時會為每種使用到RTTI的特性的C++類都建立一個唯一的type_info對象,並且會包含繼承關系,dynamic_cast便是根據這個對象來判斷某個基類對象的指針能否向下轉換成子類對象的指針。下面為一個使用typeid的例子:
#include <iostream>
#include <string> #include <typeinfo> using namespace std; int main() { string s; if(typeid(s) == typeid(string)) { cout<<"same type"<<endl; } else { cout<<"different type"<<endl; } return 0; }
3 mangle
但是我們今天關注的不是RTTI,而是關注與通過type_info獲取到的名稱信息,type_info有一個name()的方法,返回const char*,但是這個name到底是什么在C++規范中沒有限定,因此不同編譯器返回的結果不同,例如下面的代碼:
cout<<typeid(std::string)<<endl;
如果使用vc編譯器進行編譯,將返回:
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
而g++編譯執行時返回的卻是:
Ss
后者很難理解,因為這是mangle后的符號名,而VC編譯器返回的是demangle后的結果。使用C++的類與函數編譯成obj文件后,都是使用mangle后的符號名。例如:假如我們編譯了某個linux靜態庫,可以用nm工具查看其內部包含接口的符號(windows下可以使用dumpbin):
nm libmyfunc.a
其會返回許多mangle后的符號名,它們其實就是我們在庫中編寫的函數接口。
4 demangle
將C++ ABI標識符(C++ ABI identifier)轉換成C++源程序標識符(original C++ source identifier)的過程稱為demangle。更簡單的說,識別C++編譯以后的函數名的過程,就叫demangle。
在libstdc++里關於abi命名空間的文檔中(https://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/namespaces.html),介紹了GCC所使用的跨廠商(cross-vendor) C++ ABI,其中暴露的一個函數abi::__cxa_demangle就是用於demangling。
像C++ filt,連接器(linker)及其他的工具,都具有解析C++ ABI名稱的能力,比如,使用C++filt命令行工具來demangle名字:
robin@centos7:~/blog$ c++filt St13bad_exception
std::bad_exception
現在你也可以做到(這個函數本身可能使用不同的demanglers,但是它所提供的抽象接口讓你不用關心具體的實現)。
在如下情況你會關注demangle:
- 運行時階段你想查看RTTI中的typeid字符串;
- 當你在處理runtime-support異常類時。
比如下面的示例:
#include <exception>
#include <iostream> #include <cxxabi.h> struct empty { }; template <typename T, int N> struct bar { }; int main() { int status; char *realname; // exception classes not in <stdexcept>, thrown by the implementation // instead of the user std::bad_exception e; realname = abi::__cxa_demangle(e.what(), 0, 0, &status); std::cout << e.what() << "\t=> " << realname << "\t: " << status << '\n'; free(realname); // typeid bar<empty,17> u; const std::type_info &ti = typeid(u); realname = abi::__cxa_demangle(ti.name(), 0, 0, &status); std::cout << ti.name() << "\t=> " << realname << "\t: " << status << '\n'; free(realname); return 0; }
程序輸出如下:
St13bad_exception => std::bad_exception : 0
3barI5emptyLi17EE => bar<empty, 17> : 0
本節的連接中的文檔中介紹了demangler接口。它是用C寫的,所以想demangle C++時,可以直接使用,而不需要再寫一個C++的版本。
需要注意的是,需要手動釋放返回的字符數組。比如,可以使用如下類似代碼來自動管理返回的字符數組:
#include <cxxabi.h> // needed for abi::__cxa_demangle std::shared_ptr<char> cppDemangle(const char *abiName) { int status; char *ret = abi::__cxa_demangle(abiName, 0, 0, &status); /* NOTE: must free() the returned char when done with it! */ std::shared_ptr<char> retval; retval.reset( (char *)ret, [](char *mem) { if (mem) free((void*)mem); } ); return retval; }
上面代碼使用了一個std::shared_ptr並附帶一個定制的lambda ‘delete’函數來釋放返回的內存。下面定義一個宏來訪問demangled類型名:
#define CLASS_NAME(somePointer) ((const char *)cppDemangle(typeid(*somePointer).name()).get() )
在C++程序中這樣來使用:
printf("I am inside of a %s\n",CLASS_NAME(this));
5 借助backtrace和demangle實現異常類Exception
實現原理是,首先獲取棧痕跡,然后demangle符號名稱。
5.1 backtrace相關函數
C++的異常類是沒有棧痕跡的,如果需要獲取棧痕跡,需要使用以下函數:
#include <execinfo.h>
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
(1)int backtrace (void **buffer, int size);
該函數用來獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針數組。參數size用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址。注意某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會使無法正確解析堆棧內容。
(2)char **backtrace_symbols (void *const *buffer, int size);
該函數將從backtrace函數獲取的信息轉化為一個字符串數組。參數buffer是從backtrace函數獲取的數組指針,size是該數組中的元素個數(backtrace的返回值),函數返回值是一個指向字符串數組的指針,它的大小同buffer相同。每個字符串包含了一個相對於buffer中對應元素的可打印信息。它包括函數名,函數的偏移地址和實際的返回地址。backtrace_symbols生成的字符串都是malloc出來的,但是不要最后一個一個的free,因為backtrace_symbols會根據backtrace給出的callstack層數,一次性的將malloc出來一塊內存釋放,所以,只需要在最后free返回指針就OK了。
(3)void backtrace_symbols_fd (void *const *buffer, int size, int fd);
該函數與backtrace_symbols函數具有相同的功能,不同的是它不會給調用者返回字符串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行。它不需要調用malloc函數,因此適用於有可能調用該函數會失敗的情況。
backtrace將當前程序的調用信息存儲在buffer中,backtrace_symbols則是將buffer翻譯為字符串。后者用到了malloc,所以需要手工釋放內存。
man手冊中提供了如下的代碼:
#include <execinfo.h>
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void myfunc3(void) { int j, nptrs; #define SIZE 100 void *buffer[100]; char **strings; nptrs = backtrace(buffer, SIZE); printf("backtrace() returned %d addresses\n", nptrs); /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO) would produce similar output to the following: */ strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { perror("backtrace_symbols"); exit(EXIT_FAILURE); } for (j = 0; j < nptrs; j++) printf("%s\n", strings[j]); free(strings); }
/* "static" means don't export the symbol... */ static void myfunc2(void) { myfunc3(); } void myfunc(int ncalls) { if (ncalls > 1) myfunc(ncalls - 1); else myfunc2(); } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "%s num-calls\n", argv[0]); exit(EXIT_FAILURE); } myfunc(atoi(argv[1])); exit(EXIT_SUCCESS); }
編譯並執行:
$ cc -rdynamic prog.c -o prog
$ ./prog 3
輸出如下:
./prog(myfunc3+0x1f) [0x8048783]
./prog(myfunc+0x21) [0x8048833]
./prog(myfunc+0x1a) [0x804882c]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb76174d3]
./prog() [0x80486d1]
5.2 實現異常類
因此我寫出以下的異常類,注意上面的打印結果經過了名字改編,所以我們使用abi::__cxa_demangle將名字還原出來。
Exception.h代碼如下:
#Exception.h
#ifndef EXCEPTION_H_
#define EXCEPTION_H_ #include <string> #include <exception> class Exception : public std::exception { public: explicit Exception(const char* what); explicit Exception(const std::string& what); virtual ~Exception() throw(); virtual const char* what() const throw(); const char* stackTrace() const throw(); private: void fillStackTrace(); //填充棧痕跡 std::string demangle(const char* symbol); //反名字改編 std::string message_; //異常信息 std::string stack_; //棧trace }; #endif // EXCEPTION_H_
Exception.cpp代碼如下:
#include "Exception.h" #include <cxxabi.h> #include <execinfo.h> #include <stdlib.h> #include <stdio.h> using namespace std; Exception::Exception(const char* msg) : message_(msg) { fillStackTrace(); } Exception::Exception(const string& msg) : message_(msg) { fillStackTrace(); } Exception::~Exception() throw () { } const char* Exception::what() const throw() { return message_.c_str(); } const char* Exception::stackTrace() const throw() { return stack_.c_str(); } //填充棧痕跡 void Exception::fillStackTrace() { const int len = 200; void* buffer[len]; int nptrs = ::backtrace(buffer, len); //列出當前函數調用關系 //將從backtrace函數獲取的信息轉化為一個字符串數組 char** strings = ::backtrace_symbols(buffer, nptrs); if (strings) { for (int i = 0; i < nptrs; ++i) { // TODO demangle funcion name with abi::__cxa_demangle //strings[i]代表某一層的調用痕跡 stack_.append(demangle(strings[i])); stack_.push_back('\n'); } free(strings); } } //反名字改編 string Exception::demangle(const char* symbol) { size_t size; int status; char temp[128]; char* demangled; //first, try to demangle a c++ name if (1 == sscanf(symbol, "%*[^(]%*[^_]%127[^)+]", temp)) { if (NULL != (demangled = abi::__cxa_demangle(temp, NULL, &size, &status))) { string result(demangled);
free(demangled); return result; } } //if that didn't work, try to get a regular c symbol if (1 == sscanf(symbol, "%127s", temp)) { return temp; } //if all else fails, just return the symbol return symbol; }
5.3 測試異常類
測試代碼如下:
#include "Exception.h" #include <stdio.h> using namespace std; class Bar { public: void test() { throw Exception("oops"); } }; void foo() { Bar b; b.test(); } int main() { try { foo(); } catch (const Exception& ex) { printf("reason: %s\n", ex.what()); printf("stack trace: %s\n", ex.stackTrace()); } }
打印結果如下:
stack trace: Exception::fillStackTrace()
Bar::test()
./a.out(main+0xf)
./a.out()
注意編譯的時候,加上-rdynamic選項
有了這個類,我們可以在程序中這樣處理異常:
try { // } catch (const Exception& ex) { fprintf(stderr, "reason: %s\n", ex.what()); fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); abort(); } catch (const std::exception& ex) { fprintf(stderr, "reason: %s\n", ex.what()); abort(); } catch (...) { fprintf(stderr, "unknown exception caught \n"); throw; // rethrow }
參考文檔:
http://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
http://www.bagualu.net/wordpress/archives/2312
http://blog.csdn.net/icefireelf/article/details/6591298
https://www.cnblogs.com/inevermore/p/4005489.html
https://stackoverflow.com/questions/4939636/function-to-mangle-demangle-functions
https://www.cnblogs.com/inevermore/p/4005489.html