各类分析函数调用关系图的工具

各类分析函数调用关系图的工具
作者: falcon   发表日期: 2008-04-28 16:19   复制链接

描述: calltree.jpg
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_6c7244308afde09.jpg');" src="http://oss.lzu.edu.cn/blog/attachment/7_7_6c7244308afde09.jpg" width=200 οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0> 描述: calltree1.jpg
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_1081065f3acede8.jpg');" height=300 src="http://oss.lzu.edu.cn/blog/attachment/7_7_1081065f3acede8.jpg" οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0> 描述: kprof_noargument.jpg
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_e81b038602d8bba.jpg');" height=300 src="http://oss.lzu.edu.cn/blog/attachment/7_7_e81b038602d8bba.jpg" οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0> 描述: kprof_twoargument.jpg
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_840c23150d7d9ba.jpg');" src="http://oss.lzu.edu.cn/blog/attachment/7_7_840c23150d7d9ba.jpg" οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0> 描述: calltree2.jpg(generated by the new tree2dot.sh)
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_6cfa5834d6f5101.jpg');" height=300 src="http://oss.lzu.edu.cn/blog/attachment/7_7_6cfa5834d6f5101.jpg" οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0> 描述: mkgraph.jpg(a picture generated by mkgraph.sh and gprof)
图片:
=200) window.open('http://oss.lzu.edu.cn/blog/attachment/7_7_00d0b289a836501.jpg');" height=300 src="http://oss.lzu.edu.cn/blog/attachment/7_7_00d0b289a836501.jpg" οnlοad="if(this.width > 200)this.width = 200;if(this.height > 300) this.height = 300;" border=0>
by falcon
2008-04-28

    今天突然有个网友加我QQ,说他正在用gprof分析一个项目的源代码,想打印出出该项目的函数调用关系图,不料它参考的资料[1]中用于打印函数调用关系图的Mkgraph脚本已经无法下载了,所以想问我要一份。可我当初看到这篇资料时也因为也没下到,所以给它推荐了另外一个同样能够产生函数调用关系图的工具——calltree。不过刚才又突然想起,其实还有另外一个工具可以代替Mkgraph的,那就是kprof。为了方便大家日后分析源代码,这里一并再把几个相关工具介绍一下。

    先列出我当前用的这几个工具的版本和它们的下载地址:

    calltree  2.3
    下载地点 http://linux.softpedia.com/progDownload/calltree-Download-971.html
    或者
    http://mirror.lzu.edu.cn/software/calltree/calltree-2.3.tar.bz2
    gprof 2.18.0.20080103   在ubuntu/debian下直接安装即可
    http://citeseer.ist.psu.edu/graham82gprof.html
    kprof 1.4.3 (在ubuntu/debian下直接用apt-get安装)
    http://kprof.sourceforge.net/
    graphviz (在ubuntu/debian下直接用apt-get安装即可,需要它的一个dot工具)
    http://www.graphviz.org/

1. introduction

    对于一个C语言编写的项目,它的框架可以反应为一棵函数调用树。如果在分析项目之前,能够得到这样一颗调用树,那么就可以了解项目的整体框架;如果在项目运行之后,能够跟踪到该次运行过程中的函数调用,那么将有利于分析某些测试条件下项目的执行流程;而如果在项目运行过程中(比如调试项目时)能够跟踪出某个位置之前的函数调用,那么将有利于确定潜在bug可能存在的位置。

    对于这三种情况,虽然没有任何一个工具能够完全满足,不过"聪明"和"乐于奉献"的程序员们还是分别贡献了不同的工具:

    无须运行项目本身,calltree就能够根据整个项目的源代码产生一棵函数调用树,并可把该调用树导出为dot格式的图形。因此可以说calltree能够在不运行项目的条件下对项目进行函数级别的分析。

    gprof则能够在项目运行之后,把该次运行过程中的函数调用以文本的形式反应出来,不过善于思考的人们总是喜欢更美好的生活,于是kprof产生了,它不仅可以辅助gprof更好的分析程序代码级别的运行情况,而且能够导出当前执行过程中的函数调用树,并同样可以把调用树导出为dot格式的图形。

    gdb(Gnu DeBugger),这个应该很熟悉吧,它是一个调试工具。它提供专门的backtrace命令来跟踪程序执行到某个位置(比如指定的断点处)之前的函数调用。不过这个目前还是文本输出的,感兴趣的可以hack一下gdb,给它加上漂亮的输出。
   
    上面提到了DOT格式的图形。这个DOT[2]是什么呢?是graphviz[3]定义的一种图形描述语言,它可以通过graphviz提供的dot工具(安装graphviz之后就有了)把用DOT描述的图形转化为各种其他格式的图形。虽然有一些专门的DOT图形浏览工具,如dotty,不过这个东西不怎么好用,所以还是建议通过dot工具转换为比较常见的图片格式,如svg,jpg,gif,png,ps,它还可以转换成dia格式,进而可以通过“超级牛力”的dia绘图工具来进行进一步的编辑。

2. demo

    下面来介绍这几个工具的具体用法,更多细节请参考它们自己的文档。
    为了方便演示,这里写一个非常“糟糕的”但是却对这次演示很有用的代码。

Code:
/**  * test.c --  * a demo program for using calltree, gprof&kprof, gdb&backtrace command  * */ void a(void), b(void), c(void), d(void), e(void); void a(void) {     b (); } void b(void) {     c (); } void c(void) {    d (); e (); } void d(void) {     ; } void e(void) {     ; } int main (int argc, char **argv) {     if (argc < 2) {         a ();     } else {         b ();     } }
[Ctrl+A Select All]

    对这个代码而言,非常容易看出其调用关系,即:
    当main的参数个数少于2个时,调用关系为 a -> b -> c -> d,e
    当main的参数个数大于3个时,则调用关系为b -> c -> d,e
    不过,如果一个项目包含几十个文件或者几千行甚至上万行代码,这个调用关系恐怕就没这么容易看出来了,所以还得借助后面的工具。

2.1 不用运行程序就可以打印整个项目的函数调用关系图: calltree

    下载calltree后自己先编译安装好,放到/usr/bin下面。然后通过"calltree -help"查看该工具的帮助,这里通过使用-mb参数打印以main为树根的函数调用关系图。

$ calltree -mb test.c
main:
|   a
|   |   b
|   |   |   c
|   |   |   |   d
|   |   |   |   e
|   b
|   |   c
|   |   |   d
|   |   |   e


    从这个结果可以非常方便的看出函数调用关系,不过还是不够美观哦,所以加上-dot参数,产生一个dot图形吧。

$ calltree -mb test.c -dot > test.dot


    okay,现在得到了一个关系调用图,即test.dot,因为这个格式不太常用,我们给它转换成jpg,见附图calltree.jpg。

$ dot -Tjpg test.dot -o calltree.jpg



    不过貌似函数d和e没有打印出来,所以这个应该说是值得改进一下。还好我之前专门写了一个脚本,可以产生完整的输出,这个脚本见附件tree2dot.sh.tar.gz,具体原理见资料[5],附图calltree1.jpg是这个脚本产生的。这里简单介绍它的用法:

先通过脚本tree2dot.sh得到一个DOT图形
$ calltree -mb test.c | ./tree2dot.sh > test.dot
然后用dot转换为jpg格式
$ dot -Tjpg test.dot -o calltree1.jpg


    需要补充一下的是,calltree本身支持过滤掉某些字符,包括想列出的(通过listfile或者list)以及想忽略的(igorefile),例如如果仅仅想列出c函数的调用关系,则可以:

$ calltree list=c -b -np test.c
c:
|   d
|   e


    如果仅仅想导出这个调用图,通过加上-dot参数则没有作用,它还是会打印出所有的函数调用关系,所以这个时候tree2dot.sh又起作用:

$ calltree list=c -b -np test.c | ./tree2dot.sh



2.2 打印项目当次运行过程中的函数调用关系图: gprof & kprof

    首先通过gcc加上-pg参数编译程序(如果编译和链接分开,都需要加上该参数)。这个参数就是为了产生一些用于gprof&kprof的信息。gprof只有字符界面,而kprof提供了图形界面,下面仅介绍kprof,因为它和gprof相比,可以产生图形化的函数调用关系。

$ gcc -pg -o test test.c



    编译完以后,运行一下就可以产生一个名为gmon.out的文件,它记录了该项目当次运行过程中的相关信息,包括函数调用关系。

$ ./test
$ls gmon.out
gmon.out



    这样我们就可以用kprof来得到这个项目在这次运行过程中的函数调用关系图了( 实际上指定./test是为了告诉kprof,gmon.out和test在同一个目录下,kprof会去找gmon.out)。

$ kprof -f ./test



    启动kprof以后找到Graph View标签,可以看到一个函数调用关系图。如果要把这个图导出来,找到Tools菜单,点击Generate Call Graph就可以导出一个DOT图形,我们命名为kprof_noargument.dot,然后我们就可以类似2.1用dot工具把它转换为其他格式,得到的效果图如kprof_noargument.jpg。

$ dot -Tjpg kprof_noargument.dot -o kprof_noargument.jpg



    在上面,我们直接键入了./test执行它,如果给它传递上两个参数呢,这个时候argc等于二,在main中就不会再调用a函数,而是调用c函数,这样的话,函数调用关系图就不一样了,这次得到的结果图如kprof_twoargument.jpg。

带上两个参数运行test
$ ./test 1 2
通过kprof来查看调用关系图,并导出一个名为kprof_twoargument.dot的图形
$ kprof -f ./test
把DOT图形转换为jpg格式
$ dot -Tjpg kprof_twoargument.dot -o kprof_twoargument.jpg



    这里没有提到gprof,因为它只产生一些不太好看的文本调用关系图,所以没有演示,不过它还是有很大作用的,具体参考一下资料[4]吧。
    结合2.1和2.2,我们可以发现calltree和kprof两者都能够得到项目的函数调用关系图,不过前者能够得到整个项目的函数调用关系,而后者则能够得到某次运行过程中的函数调用关系,各有不同作用。通过前者我们可以了解整个项目的框架;而通过后者,我们可以找出一个项目在某些测试条件下的执行路径,从而更好地辅助源代码的分析。
    有时候,这两种结果还是无法满足我们的要求,比如在调试过程中,我们设置了一个断点,并想了解一下这之前执行过哪些函数,进而找出潜在的bug可能出现的位置。

2.3 项目调试过程中打印某个位置(如断点)之前的函数调用关系图:gdb & backtrace command

    为了能够用gdb调试程序,编译时请使用-g选项。

$ gcc -g -o test test.c



    通过gdb的backtrace命令打印程序执行到某个位置之前的函数调用信息。

$ gdb ./test
...
(gdb) set args 1 2         //这里设置为两个参数,所以选择了路径b->c->d,e
(gdb) l
6
7       void a(void) {  b (); }
8       void b(void) {  c (); }
9       void c(void) {  d (); e (); }
10      void d(void) {  ; }
11      void e(void) {  ; }
12
13      int main (int argc, char **argv)
14      {
15              if (argc < 2) {
(gdb) break c
Breakpoint 1 at 0x804833c: file test.c, line 9.
(gdb) r
Starting program: /home/falcon/Programming/test 1 2

Breakpoint 1, c () at test.c:9
9       void c(void) {  d (); e (); }
(gdb) backtrace
#0  c () at test.c:9
#1  0x08048334 in b () at test.c:8
#2  0x08048380 in main (argc=3, argv=0xbf924e24) at test.c:18



    在上面的调试过程中,我们首先通过set命令设置了两个参数,选择了main函数的第二个分支,并在c函数的入口设置了一个断点,然后运行程序直到该断点处,之后通过backtrace命令打印出之前的函数调用信息。通过最后几行,我们看到c最后被调用,之前是b,再之前是main。

    到这里,开头提到的三种情况都介绍完了。不过呢,除了上面这些跟函数关系紧密的工具外,还有一个叫cscope[6]的工具,结合它和vim编辑器,在我们阅读源代码的过程中,可以利用它提供的":cs find d function"命令打印出函数function调用的所有函数,从而帮助我们了解某个函数内部的函数调用关系。当然该工具还有更丰富的用法,具体参考资料[6]。
    除了这些分析应用程序的工具外,还有一个叫KFT[7]的工具可以用来分析linux内核。作为linux内核的一个补丁,它能够跟踪内核中某个系统调用的函数调用关系图,通过KFT提供的一个kd工具,可以得到一个文本格式的函数调用关系图,结合我上面用到的tree2dot.sh(建议用资料[5]中的tree2dot.sh),可以得到一个图形输出。

    更多相关资料见后面。有任何建议和疑问,欢迎回帖交流,也可以直接给我发邮件。

补充:

1.  应网友要求,刚写了个mkgraph.sh脚本,这个脚本没有做足够的测试,这个可以结合gprof使用,具体用法如下:

带-pg编译程序
$ gcc -pg -o test test.c
先运行程序
$ ./test
让mkgraph.sh可以运行
$ chmod +x mkgraph.sh
通过gprof产生函数调用图并导出为dot图形
$ gprof ./test gmon.out -bq | ./mkgraph.sh > mkgraph.dot
把DOT图转换为jpg格式
$ dot -Tjpg mkgraph.dot -o mkgraph.jpg


图的结果见附图mkgraph.jpg,这个还没有进行足够的测试,如果发现问题,欢迎回复。

2. 考虑到原来的tree2dot脚本有些问题,同一个函数调用绘制了多条路径,不妥,所以需要调整一下。请大家下载当前的tree2dot版本,该版本的结果为calltree2.jpg。


参考资料

[1] 使用Gnu gprof进行Linux平台下的程序分析
[2] The DOT Language
http://www.graphviz.org/doc/info/lang.html
[3] Graphviz - Graph Visualization Software
http://www.graphviz.org/
[4] Coverage Measurement and Profiling
http://www.linuxjournal.com/article/6758
[5] 用Graphviz进行可视化操作──绘制函数调用关系图
[6] cscope
http://cscope.sourceforge.net/
[7] KFT(Kernel Function Tracing)
http://elinux.org/Kernel_Function_Trace
ftp://dslab.lzu.edu.cn/pub/kft
[8] Call Graph -- Gprof
http://sourceware.org/binutils/docs-2.17/gprof/Call-Graph.html#Call-Graph



描述: tree2dot.sh
附件: tree2dot.sh.tar.gz (1 K)

描述: mkgraph.sh(a shell script for generating the dot graph with gprof, by falcon)
附件: mkgraph.sh.tar.gz (1 K)

描述: tree2dot.sh.tar.gz(new)
附件: tree2dot.sh.tar.gz (1 K)

阅读全文(164) | 回复(4) | 推送
欢迎到 falcon 的个人主页看更多内容



  共4条回复
falcon 发表于 2008-04-28 22:24 #1

如果感觉输出的图不太好看,自己看看参考资料[2],修改一下dot图形里头的一些配置参数,比如图片大小、文字大小和颜色等。
返回
falcon 发表于 2008-05-03 15:06 #2

在x86-64位的机器上编译calltree的时候会出现提示这样的错误而无法运行的情况:

RULES/rules1.top:234: incs/Dcc.x86_64-linux: 没有该文件或目录
RULES/rules.top:39: RULES/x86_64-linux-cc.rul: 没有该文件或目录
RULES/rules.cnf:56: incs/x86_64-linux-cc/Inull: 没有该文件或目录
RULES/rules.cnf:57: incs/x86_64-linux-cc/rules.cnf: 没有该文件或目录
p incs/x86_64-linux-cc
make: p:命令未找到
make: [incs/x86_64-linux-cc/Inull] 错误 127 (忽略)
/bin/sh: cannot create incs/x86_64-linux-cc/Inull: Directory nonexistent
make: *** [incs/x86_64-linux-cc/Inull] 错误 2



问题是calltree的这个版本里头还有X86064位对应的Makefile规则文件,可以直接把i686-linux-cc.rul复制为x86_64-linux-cc.rul

$ cd /path/to/calltree-2.3
$ cp RULES/i686-linux-cc.rul RULES/x86_64-linux-cc.rul



再make就没有问题了。
返回
falcon 发表于 2008-05-03 15:19 #3

这里有一个比较完整的gprof2dot的脚本:
http://www.graphviz.org/Misc/gprof2dot.awk

另外这里也有一篇介绍“分析函数调用关系图”的blog:

分析函数调用关系图(call graph)的几种方法
http://www.bbreader.com/item.bb?id=477574
返回
falcon 发表于 2008-05-03 15:23 #4

另外一篇,用CodeViz绘制函数调用关系图(call graph)

http://dev.csdn.net/article/77/77273.shtm


返回
http://oss.lzu.edu.cn/blog/article.php?tid_1636.html

你可能感兴趣的:(各类分析函数调用关系图的工具)