在分析复杂的C/C++软件时,如果有一个工具可以便捷的生成“函数调用关系图”,不是一件很好的事吗?如果你庆幸是一个Javaer或钟爱基于IDE(如Eclipse)的软件开发,应该会经常使用类似的工具。如果,你是*Nixer(*nix用户)呢?其实,我们一样有工具可用(地球村那么多hacker,你遇见的问题,多半是别人早就碰到了并给出了相应的解决方案)。
除了使用CodeViz、egypt和ncc,你可以尝试一下本文介绍的方案(核心的处理方式都差不多)。
依赖于gcc的hook机制,在函数的入口及出口打上“标签”用于获取“调用者”函数符号地址信息(保存到文件中),然后通过addr2line(pvtrace内部实现依赖于此工具),根据给定的“地址”从可执行文件中查出对应的“函数名”。最后,生成满足graphviz组件dot语法的文件,用dot将其转为图形文件即可。
具体涉及的hook,如下:
1. 函数入口及出口hook函数原型
- void __cyg_profile_func_enter( void *, void * )
- __attribute__ ((no_instrument_function));
- void __cyg_profile_func_exit( void *, void * )
- __attribute__ ((no_instrument_function));
通过实现以上原型的实例函数,完成函数调用信息采集。
2. 在调用main函数之前及其退出之后,设置特殊处理操作的hook函数原型
- void main_constructor( void )
- __attribute__ ((no_instrument_function, constructor));
- void main_destructor( void )
- __attribute__ ((no_instrument_function, destructor));
通过实现以上原型的实例函数,生成及关闭用于保存函数调用关系信息的文件(trace.txt)。
具体的实现,可参考pvtrace源代码中的instrument.c
文件。
更多细节,请查阅用Graphviz可视化函数调用一文。
1. 安装pvtrace
- $ mkdir -p ~/project1 && cd ~/project1
- $ wget http://www.mtjones.com/developerworks/pvtrace.zip
- $ unzip pvtrace.zip -d pvtrace
- $ cd pvtrace
- $ make
- $ sudo make install
- # 查看pvtrace相关文件
- $ ls -1 pvtrace
- instrument.c
- Makefile
- stack.c
- stack.h
- symbols.c
- symbols.h
- trace.c
2. 安装graphviz
- $ sudo yum install graphviz
在完成软件安装之后,编写一个测试程序(test.c),并进行测试。具体流程,如下:
1. 编辑测试文件test.c
- $ cd ~/project1
- $ cat << EOF > test.c
- #include <stdio.h>
- #include <stdlib.h>
- void test1()
- {
- printf("in test1.\n");
- }
- void test2()
- {
- test1();
- printf("in test2.\n");
- }
- void test3()
- {
- test1();
- test2();
- printf("in test3.\n");
- }
- int main(int argc, char *argv[])
- {
- printf("Hello wolrd.\n");
- test1();
- test2();
- test3();
- return 0;
- }
- EOF
2. 编译测试程序
- $ gcc -g -finstrument-functions test.c ./pvtrace/instrument.c -o test
- 注意: 必须有`-g -finstrument-functions`选项,否则后续就采集不到信息了。
3. 执行程序,生成信息文件trace.txt
- $ ./test
4. 通过pvtrace、可执行文件及trace.txt,生成信息文件graph.dot
- $ pvtrace test
5. 通过dot工具将graph.dot,转为图像文件graph.png
- $ dot -Tpng graph.dot -o graph.png
6. 浏览生成的图片
最终生成的图形效果,如下:
可以通过以下步骤为已有的项目生成函数调用图:
1. 将pvtrace源代码中的instrument.c
文件拷贝到项目中;
2. 增加对instrument.c文件的编译
3. 修改编译
选项:增加-g -finstrument-functions
4. 修改连接
选项:将instrument.o
连接到可执行文件中
5. 执行你的程序
6. 用pvtrace及“你的可执行文件”处理trace.txt
7. 用dot生成函数调用关系图
以下是对redis-2.4.17版本的处理,然后生成redis-cli启动及一个set操作对应的函数调用关系图:
目前pvtrace不支持C++代码,如果有人希望改进,一种可行的改进思路,如下:
1. 修改instrument.c文件,支持C++环境的编译;
2. 通过c++filt
工具处理解析到的函数名标签,解析出实际的函数名: 为了支持继承、多态及函数重载等,C++编译时对函数名进行了特殊处理;
3. 采用合理的编码方式,确保步骤2中生成的函数名满足dot的语法(C++是用整个函数原型等信息来生成的函数签名的,所以步骤2中用c++filt
翻译出来的是函数原型(包括名字空间等信息));
4. 增加函数调用先后顺序的标识。
1. CodeViz: A CallGraph Visualiser;
2. egypt;
3. ncc。
1. IBM developerworks 上M. Tim Jones 专栏及mtjones的主页;
2. Graphviz各大组件(dot等)工具相关文档;
3. GCC实用工具addr2line说明;
4. 陈硕的博文“用CodeViz绘制函数调用关系图”;
1. M.Tim Jones的文章用Graphviz可视化函数调用。