运行环境:虚拟机下的Ubuntu 11.04
结合Graphviz工具,使用CodeViz可以生成直观和漂亮的C/C++程序函数之间的调用关系图。
1、安装graphviz
在安装CodeViz之前,必须先安装它所依赖的工具dot,否则将无法完成./configure操作并提示以下错误信息:
checking for dot...not found FATAL: The program dot was not in your path. This is probably available for your distribution with the graphviz package. Install this before running configure again
既可以从http://www.graphviz.org/Download_linux_ubuntu.php 上下载最新版本的graphviz安装程序手动安装,也可以使用以下命令自动安装:
$ sudo apt-get install graphviz graphviz-dev graphviz-doc
程序简要说明:
graphviz - rich set of graph drawing tools graphviz-dev - transitional package for graphviz-dev rename graphviz-doc - additional documentation for graphviz
安装相关库:
$ sudo apt-get install libgv-*
相关库简要说明:
libgv-guile - Guile bindings for graphviz libgv-lua - Lua bindings for graphviz libgv-ocaml - OCaml bindings for graphviz libgv-perl - Perl bindings for graphviz libgv-php5 - Php5 bindings for graphviz libgv-python - Python bindings for graphviz libgv-ruby - Ruby bindings for graphviz libgv-tcl - Tcl bindings for graphviz
2、安装CodeViz
从http://www.skynet.ie/~mel/projects/codeviz/ 上下载CodeViz安装包codeviz-1.0.10.tar.gz以及从ftp://ftp.gnu.org/pub/gnu/gcc/gcc-3.4.6 上下载GCC源码包gcc-3.4.6.tar.gz,并把它们拷贝到Ubuntu下的同一目录下。
然后解压CodeViz安装包,并把gcc-3.4.6.tar.gz拷贝到codeviz-1.0.10/compiler目录下:
$ tar zvxf codeviz-1.0.10.tar.gz $ cd codeviz-1.0.10/ $ cp ../gcc-3.4.6.tar.gz compilers/ //也可以略过这一步,让ncftp(Ubuntu默认未安装)在make执行过程中自动下载gcc-3.4.6.tar.gz $ ./configure
Patched GCC默认安装在/usr/local/gccgraph目录下。
安装GCC时,需要安装下面的库
apt-get install libgmp10-dev libmpfr-dev libmpc-dev
在执行make之前,先通过以下命令创建一个链接文件asm:
$ sudo ln -sf /usr/include/asm-generic/ /usr/include/asm
否则,在make执行过程中会出现在/usr/include/linux/errno.h文件中找不到asm/errno.h文件的错误信息并中止编译。
接下来执行make和make install命令即可在/usr/local/gccgraph目录下创建打过补丁的GCC编译器以及在/usr/local/bin目录下创建两个perl脚本程序genfull和gengraph。
$ make $ sudo make install
3、CodeViz的使用举例
例子源代码如下:
/* test.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> void func(char *p) { p = (char *)malloc(20); } int main(void) { char *str = NULL; func(str); strcpy(str, "hello world"); printf("string is %s\n", str); return 0; }
使用刚生成的编译器编译test.c,会相应地生成一个test.c.cdepn文件。
$ /usr/local/gccgraph/bin/gcc test.c
test.c.cdepn文件信息:
F {func} {test.c:6} C {func} {test.c:7} {malloc} F {main} {test.c:11} C {main} {test.c:18} {printf} C {main} {test.c:16} {strcpy} C {main} {test.c:14} {func}
然后执行genfull脚本生成full.graph。
$ genfull
full.graph文件信息:
digraph fullgraph { node [ fontname=Helvetica, fontsize=12 ]; "func" [label="func\ntest.c:5:"]; "main" [label="main\ntest.c:10:"]; "func" -> "malloc" [label="test.c:7"]; "main" -> "func" [label="test.c:14"]; "main" -> "strcpy" [label="test.c:16"]; "main" -> "printf" [label="test.c:18"]; }
最后执行gengraph生成函数关系调用图。
$ gengraph -f main --output-type "png"
-f指定最顶层的函数,--output-type指定图片的输出格式。
至于genfull和gengraph的详细使用说明请参考man手册,使用以下命令查看:
$ sudo apt-get install perl-doc //须先安装perl-doc $ genfull --man $ gengraph --man 另外参考:http://blog.csdn.net/solstice/article/details/486788
CodeViz是《Understanding The Linux Virtual Memory Manager》(at Amazon,下载地址在页尾)的作者 Mel Gorman 写的一款分析C/C++源代码中函数调用关系的open source工具(类似的open source软件有 egypt、ncc)。其基本原理是给 GCC 打个补丁,让它在编译时每个源文件时 dump 出其中函数的 call graph,然后用 Perl 脚本收集并整理调用关系,转交给Graphviz绘制图形。
CodeViz 原本是作者用来分析 Linux virtual memory 的源码时写的一个小工具,现在已经基本支持 C++ 语言,最新的 1.0.9 版能在 Windows + Cygwin 下顺利地编译使用:)。需要注意的是:1) 下载 GCC 3.4.1 的源码 gcc-3.4.1.tar.gz 放到 codeviz-1.0.9/compilers,2) 安装 patch 程序(属于Utils类),3) 从 http://www.graphviz.org 下载并安装 Graphviz 2.6。
我用 CodeViz 分析《嵌入式实时操作系统 uC/OS-II (第二版)》中的第一个范例程序,步骤如下:
1. 想办法让 gcc 能编译uC/OS 2.52和范例程序的源码,每个C源文件生成对于的.c.cdepn文件。只要编译(参数 -c
)就行,无需连接。
2. 调用genfull生成full.graph,这个文件记录了所有函数在源码中的位置和它们之间的调用关系。
3. 使用gengraph生成我关心的函数的调用关系。
首先分析main():
1. gengraph --output-type gif -f main
分析main()的call graph,得到的图如下,看不出要领:
2. gengraph --output-type gif -f main -s OSInit
暂时不关心OSInit()的内部实现细节(参数 -s
),让它显示为一个节点。得到的图如下,有点乱,不过好多了:
3. gengraph --output-type gif -f main -s OSInit -i "OSCPUSaveSR;OSCPURestoreSR"
基本上每个函数都会有进入/退出临界区的代码,忽略之(参数 -i
)。得到的图如下,基本清楚了:
4. gengraph --output-type gif -f main -s "OSInit;OSSemCreate" -i "OSCPUSaveSR;OSCPURestoreSR" -k
OSSemCreate()的内部细节似乎也不用关心,不过保留中间文件sub.graph(参数 -k
),得到的图如下,
5. dot -Tgif -o main.gif sub.graph
修改sub.graph,使图形符合函数调用顺序,最后得到的图如下,有了这个都不用看代码了:)
接着分析OSTimeDly()的被调用关系:
gengraph --output-type gif -r -f OSTimeDly
看看哪些函数调用了OSTimeDly(),参数 -r ,Task()和TaskStart()都是用户编写的函数:
最后看看Task()直接调用了哪些函数:
gengraph --output-type gif -d 1 -f Task
只看从Task出发的第一层调用(参数 -d 1):
在分析源码的时候,把这些图形打印在手边,在上面做笔记,实在方便得很。