转: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

mangle和demangle的更多相关文章

  1. 关于ROS的MANGLE和ip route-rules-lookup的关系和区别

    mangle打出来的mark routing的优先级和lookup打出来的优先级是一样的.(你可以这么理解,lookup根据目的地址打出来的标签,其实跟在ip firewall-mangle打出来的是 ...

  2. 借助backtrace和demangle实现异常类Exception

    C++的异常类是没有栈痕迹的,如果需要获取栈痕迹,需要使用以下函数: #include <execinfo.h> int backtrace(void **buffer, int size ...

  3. c++ de-mangle 反编译器命名工具:c++filt

    nm *.so | c++filt c++filt  symblo

  4. ROS中Mangle解析

    http://blog.csdn.net/bluecy/article/details/8192307

  5. 链接(extern、static关键词\头文件\静态库\共享库)

    原文链接:http://www.orlion.ga/781/ 一. 多目标文件的链接 假设有两个文件:stack.c: /* stack.c */ char stack[512]; int top = ...

  6. Linux的nm查看动态和静态库中的符号

    功能 列出.o .a .so中的符号信息,包括诸如符号的值,符号类型及符号名称等.所谓符号,通常指定义出的函数,全局变量等等. 使用 nm [option(s)] [file(s)] 有用的optio ...

  7. ar命令和nm命令(建库!)

    ar 命令详解 今天,跟着我们的技术大牛学了不少东西,首先就是这个ar命令啦. 当我们的程序中有经常使用的模块,而且这些模块在其他程序中也会用到,为了实现代码重用减少软件开发周期,我们可以将它们生成库 ...

  8. calling c++ from golang with swig--windows dll 二

    Name mangling && Name demangling 在讲述golang如何利用swig调用windows dll之前,需要了解一个概念:Name Mangling (或者 ...

  9. Linux程序分析工具介绍—ldd,nm

    原文链接:http://blog.csdn.net/statdm/article/details/7759100 本文要介绍的ldd和nm是linux下,两个用来分析程序很实用的工具.ldd是用来分析 ...

随机推荐

  1. 用 profvis 进行性能分析

    Rprof( ) 函数提供了有用的信息帮助我们找到代码瓶颈,进而提升代码性能.RStudio 也发布了一个增强版的分析工具 profvis( ),它还提供了用于分析 R 代码的交互式可视化功能(htt ...

  2. Strategy(策略)

    意图: 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换.本模式使得算法可独立于使用它的客户而变化. 适用性: 许多相关的类仅仅是行为有异.“策略”提供了一种用多个行为中的一个行为来配置 ...

  3. Eclipse.导出可执行的jar(包含第3方jar)

    1.不包含 第三方jar 的情况: (1)项目右键--> Export... --> 选择"JAR file"(记得有看到有的博客上写的是选择"Runnabl ...

  4. 使用ARouter遇到的坑

    跨模块跳转不能跳转 需要被跳转的模块或者说使用了ARouter注解的模块都要加上这个 dependencies{    annotationProcessor rootProject.ext.arou ...

  5. [java]java String.split()函数的用法分析

    转自:http://swiftlet.net/archives/709 一.在java.lang包中有String.split()方法的原型是: public String[] split(Strin ...

  6. m_Orchestrate learning system---网站的语言选择功能(中文英文)

    m_Orchestrate learning system---网站的语言选择功能(中文英文) 一.总结 一句话总结:有两种方法,一是session+js端代码,而是session+php端代码. 推 ...

  7. springboot模糊查询

    在学习MyBatis过程中想实现模糊查询,可惜失败了.后来上百度上查了一下,算是解决了.记录一下MyBatis实现模糊查询的几种方式. 数据库表名为test_student,初始化了几条记录,如图: ...

  8. C++ 类型转换的特殊用法

    C++ 类型转换的特殊用法 下面是ossimLsrRay.h中的一个例子 /*! * CASTING OPERATOR: ossimEcefRay() * Looks like a construct ...

  9. POJ 3278 Catch That Cow bfs 难度:1

    http://poj.org/problem?id=3278 从n出发,向两边转移,为了不使数字无限制扩大,限制在2*k以内, 注意不能限制在k以内,否则就缺少不断使用-1得到的一些结果 #inclu ...

  10. translate函数

    translate函数: select translate('ab23cd1', '.0123456789' || 'ab23cd1', '.0123456789') from dual 截图: