巧用Graphviz和pvtrace等工具可视化C函数调用

from http://guiquanz.github.io/2012/10/15/linux_c_call_trace/

在分析复杂的C/C++软件时,如果有一个工具可以便捷的生成“函数调用关系图”,不是一件很好的事吗?如果你庆幸是一个Javaer或钟爱基于IDE(如Eclipse)的软件开发,应该会经常使用类似的工具。如果,你是*Nixer(*nix用户)呢?其实,我们一样有工具可用(地球村那么多hacker,你遇见的问题,多半是别人早就碰到了并给出了相应的解决方案)。

除了使用CodeViz、egypt和ncc,你可以尝试一下本文介绍的方案(核心的处理方式都差不多)。

实现原理

依赖于gcc的hook机制,在函数的入口及出口打上“标签”用于获取“调用者”函数符号地址信息(保存到文件中),然后通过addr2line(pvtrace内部实现依赖于此工具),根据给定的“地址”从可执行文件中查出对应的“函数名”。最后,生成满足graphviz组件dot语法的文件,用dot将其转为图形文件即可。

具体涉及的hook,如下:

1. 函数入口及出口hook函数原型

  
  
  
  
  1. void __cyg_profile_func_enter( void *, void * )
  2. __attribute__ ((no_instrument_function));
  3.  
  4. void __cyg_profile_func_exit( void *, void * )
  5. __attribute__ ((no_instrument_function));

通过实现以上原型的实例函数,完成函数调用信息采集。

2. 在调用main函数之前及其退出之后,设置特殊处理操作的hook函数原型

  
  
  
  
  1. void main_constructor( void )
  2. __attribute__ ((no_instrument_function, constructor));
  3.  
  4. void main_destructor( void )
  5. __attribute__ ((no_instrument_function, destructor));

通过实现以上原型的实例函数,生成及关闭用于保存函数调用关系信息的文件(trace.txt)。

具体的实现,可参考pvtrace源代码中的instrument.c文件。

更多细节,请查阅用Graphviz可视化函数调用一文。

安装pvtrace和Graphviz

1. 安装pvtrace

  
  
  
  
  1. $ mkdir -p ~/project1 && cd ~/project1
  2. $ wget http://www.mtjones.com/developerworks/pvtrace.zip
  3. $ unzip pvtrace.zip -d pvtrace
  4. $ cd pvtrace
  5. $ make
  6. $ sudo make install
  7.  
  8. # 查看pvtrace相关文件
  9. $ ls -1 pvtrace
  10. instrument.c
  11. Makefile
  12. stack.c
  13. stack.h
  14. symbols.c
  15. symbols.h
  16. trace.c

2. 安装graphviz

  
  
  
  
  1. $ sudo yum install graphviz

测试

在完成软件安装之后,编写一个测试程序(test.c),并进行测试。具体流程,如下:

1. 编辑测试文件test.c

  
  
  
  
  1. $ cd ~/project1
  2. $ cat << EOF > test.c
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5.  
  6. void test1()
  7. {
  8. printf("in test1.\n");
  9. }
  10.  
  11. void test2()
  12. {
  13. test1();
  14. printf("in test2.\n");
  15. }
  16.  
  17. void test3()
  18. {
  19. test1();
  20. test2();
  21.  
  22. printf("in test3.\n");
  23. }
  24.  
  25. int main(int argc, char *argv[])
  26. {
  27. printf("Hello wolrd.\n");
  28.  
  29. test1();
  30. test2();
  31. test3();
  32.  
  33. return 0;
  34. }
  35. EOF

2. 编译测试程序

  
  
  
  
  1. $ gcc -g -finstrument-functions test.c ./pvtrace/instrument.c -o test
  2.  
  3. 注意: 必须有`-g -finstrument-functions`选项,否则后续就采集不到信息了。

3. 执行程序,生成信息文件trace.txt

  
  
  
  
  1. $ ./test

4. 通过pvtrace、可执行文件及trace.txt,生成信息文件graph.dot

  
  
  
  
  1. $ pvtrace test

5. 通过dot工具将graph.dot,转为图像文件graph.png

  
  
  
  
  1. $ dot -Tpng graph.dot -o graph.png

6. 浏览生成的图片

最终生成的图形效果,如下:

巧用Graphviz和pvtrace等工具可视化C函数调用_第1张图片

为已有的项目生成函数调用图

可以通过以下步骤为已有的项目生成函数调用图:

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操作对应的函数调用关系图:

巧用Graphviz和pvtrace等工具可视化C函数调用_第2张图片

支持C++的扩展

目前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可视化函数调用。

你可能感兴趣的:(巧用Graphviz和pvtrace等工具可视化C函数调用)