前言
这个命令功能单一,但是非常强大,可以用来还原C++编译后的函数名,为什么C++的函数名需要单独的命令来还原,因为他们看起来都是这样 _ZNK4Json5ValueixEPKc
、这样 _Z41__static_initialization_and_destruction_0ii
或者这样的 _ZN6apsara5pangu15ScopedChunkInfoINS0_12RafChunkInfoEED1Ev
,仅通过这一串字母很难知道原函数的名字是什么,参数类型就更难分析了,实际上C++在编译函数时有一套命名函数的规则,每种参数使用什么字母表示都是有约定的,但是通过学习这些约定来还原函数太麻烦了,还好有人编写了 c++filt
命令可以让我们直接得到编译前的函数名,真好……
C++编译后的函数名
C++
编译后的函数名字非常古怪,相比而言 C
语言编译后的函数看起来就正常许多了,extern "C"
、函数重载、name mangling
这些知识点都与 C++
这个奇怪的函数名有些关系,extern "C"
的作用简而言之就是告诉编译器和链接器被“我”修饰的变量和函数需要按照 C
语言方式进行编译和链接,这样做是由于 C++
支持函数重载,而 C
语言不支持,结果导致函数被 C++
编译后在符号库中的名字和被 C
语言编译后的名字是不一样的,程序编译和连接就会出现问题,此类问题一般出现在 C++
代码调用 C
语言写的库函数的时候。
而 name mangling
就是实现 C++
函数重载的一种技术或者叫做方式,要求同名的 C++
函数参数个数不同或参数类型不同,如果只有返回值类型不同,那么两个函数被认为是相同的函数,无法成功通过编译。接下来我们就来看几个例子,看看 C++
编译后的函数名有什么变化。
C++和C语言编译后的函数名对比
我们来写一段相同的代码,分别使用 gcc
和 g++
进行编译,从代码到可执行文件需要经历“预处理、编译、汇编、链接”4个步骤,接下来为了看到编译后函数名的不同,我们只进行前两步,生成汇编代码,再来比较不同。
gcc编译simple.c文件
1 | // simple.c |
gcc simple.c -S
生成汇编代码文件simple.s内容
1 | .file "simple.c" |
g++编译simple.cpp文件
1 | // simple.cpp |
g++ simple.cpp -S
生成汇编代码文件simple.s内容
1 | .file "simple.cpp" |
虽然只有几行代码,可是生成汇编文件之后变成了50多行,我们只需要关注 myadd()
这个函数编译之后变成了什么就可以了,汇编代码虽然不好读,但是查找一个函数名应该没问题的,对照着上面的代码我们发现,myadd()
这个函数通过 gcc
编译之后的函数名还是 myadd
,而通过 g++
编译之后的函数名变成了 _Z5myaddii
,可以明显感觉到最后的两个字母 i
代表的是参数 int
,使用 c++filt
命令还原如下:
1 | $ c++filt _Z5myaddii |
C++函数重载编译后的函数名对比
我们还是在刚才的代码的基础上增加一个参数类型不同的 myadd
函数,修改后的代码如下:
1 | int myadd(int a, int b) |
g++ simple.cpp -S
生成汇编代码文件simple.s内容为:
1 | .file "simple.cpp" |
这次一共3个函数,生成的汇编代码更长,但是我们一眼就能看见汇编代码中包含 _Z5myaddii
和 _Z5myaddff
两个函数,这就是函数重载的产物,两个参数类型不同的同名函数编译之后生成了不同的名字,_Z5myaddff
函数末尾的两个 f
应该指的就是参数类型 float
。
使用c++filt定位问题示例
c++filt的作用就是还原函数名字,它可以帮我们查找动态链接库中缺少的函数,还原崩溃堆栈中一大串的函数名字母等等,下面来看一个崩溃堆栈的例子,代码内容尽量简写,只为了说明问题,现实情况可能要复杂的多。
首先定义一个打印函数堆栈的函数,参考之前的总结《linux环境下C++代码打印函数堆栈调用情况》,代码如下:
1 |
|
再写一段隐藏着崩溃问题的代码:
1 |
|
编译运行,果然崩溃了:
1 | $ g++ simple.cpp --std=c++11 |
这时崩溃的堆栈中发现了一个特别长的函数 _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc
,使用 c++filt
命令来还原函数:
1 | $ c++filt _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc |
从函数名来看是一个与字符串相关的 compare
函数,查看代码发现是 s == "20200517"
这一句的问题,所以说能确切的知道函数名对我们查找问题来说还是挺有帮助的。
总结
c++filt
命令可以还原C++
为实现函数重载采用name mangling
搞出来的奇奇怪怪的函数名- 注册信号回调函数方式:
signal(SIGSEGV, show_stack);
,SIGSEGV
代表无效的内存引用 - 注意
C
语言和C++
在编译后函数命名方式的不同,C
语言不支持严格意义的重载,C++支持
阳光、空气、水,这些真的是好东西,当你真的快要失去它们才意识的到的话就有些晚了…