codeviz
本文为codeviz README译文,点击 此处查看原文
1. 这些内容从何而来?
codeviz的网站现在已经关闭了,很难找到源代码下载。在这里找到并下载源代码。我所做的唯一更改就是将这个头添加到这个文件中。
2. README
这是CodeViz脚本集的README。这些工具用于为C和C++程序创建调用图,以便可视化函数流。它们是在GPL下授权的,有关详细信息,请参阅COPYING文件。
3. 简介
在每个人编程生涯的某个阶段,他们都需要阅读其他程序员编写的大量代码。程序理解的一个重要部分是构建一个从高级视图构建程序结构的图片,调用图在构建这个片段时可以提供非常宝贵的帮助。如果最初的程序员使用的是合理的函数名,这一点尤其有用。
该项目提供生成调用图的能力,以帮助理解代码。它使用一组高度模块化的collection方法,可以适应任何语言,尽管目前只支持C和c++。每种collection方法都有不同的优点和缺点。
4. 安装
cd codeviz
cp ./lib/* -rv /usr/lib/ (或您首选的perl库路径)
cp ./bin/* /usr/local/bin
或者直接从包bin/
目录运行脚本,因为只要库安装在一个全局perl目录中,或者位于与bin/
相同级别的lib/
目录中,就可以找到库。
这些图是使用dot呈现的,dot是GraphViz项目的一部分。为您的发行版安装这个软件包,或者直接从http://www.graphviz.org获得它。
5. 脚本
genfull
—— 使用它生成项目的完整调用图。这将是相当大的,可能应该削减到gengraph
大小。有许多collection方法可用,但默认方法是cdepn
。运行genfull --man
获取完整的man页面。不要费心自己把输出 full.graph 到 dot 中,因为它不太可能在合理的时间内绘制。gengraph
—— 这将为给定的一组函数生成一个小的子图和postscript文件。运行gengraph --man
了解详细信息。
6. 为 genfull 生成 cdepn 文件
如果您感兴趣的源文件的full.graph
已经创建,您可以跳过本节。参见./graphs
来看一个full.graph
是否可用。
cobjdump
和cppobjdump
(分别适用于C和c++)将生成足够的调用图,但是缺少一些信息。例如,一个函数声明的源文件未知,宏和内联函数将完全丢失。理想情况下,应该使用cdepn
方法,但是它需要gcc和g++的一个补丁版本才能工作。这些补丁和一些脚本可以在compilers/
目录中找到。
gcc和g++的补丁版本为编译的每个c和c++文件输出.cdepn
文件。这个.cdepn
文件包含诸如函数何时被调用、函数在何处声明等信息。CodeViz的早期版本支持多个gcc版本,但是这个版本只支持gcc 4.6.2。
首先,必须下载源tar。对于那些有比阅读gcc安装文档更好的事情要做的人,只需执行以下操作:
cd compilers
ncftpget ftp://ftp.gnu.org/pub/gnu/gcc/gcc-4.6.2/gcc-4.6.2.tar.gz
./install_gcc-4.6.2.sh <optional install path>
这个脚本将解压缩gcc,打补丁并将其安装到提供的路径。如果没有指定路径,则将安装到$HOME/gcc-graph
。我通常使用命令./install_gcc-4.6.2.sh /usr/local/gcc-graph
将它安装到/usr/local/gcc-graph
。
如果您真的想手工修补,只需阅读脚本中的每个步骤。不过有一个步骤需要注意。
现在,我们假定gcc和g++的补丁版本现在在$HOME/gcc-graph/
中。大多数项目将使用变量CC来决定使用哪个版本的gcc。使用补丁程序最方便的方法是使用类似于 make CC=$HOME/gcc-graph/bin/gcc CXX=$HOME/gcc-graph/bin/g++
。
或者,调整您的path,gcc-graph将出现在正常gcc之前。在编译每个源文件时,将创建相应的cdepn文件。
在构建Linux内核的情况下,命令将是;
make CC=$HOME/gcc-graph/bin/gcc bzImage
make CC=$HOME/gcc-graph/bin/gcc modules
如果Makefile正确地使用CC或CXX宏来指示要使用的编译器,那么类似的方法也适用于其他项目。如果它是您自己类型的一个Makefile,或者它没有使用适当的宏,那么您可能必须自己编辑Makefile,或者调整你的path以将gcc-graph放在首位。例如,使用bash,可以执行操作PATH=$HOME/gcc-graph/bin:$PATH
。构建时,观察编译器输出,以确保正在创建.cdepn
文件。
7. 为 genfull 生成 nccout 文件
使用一个补丁版本的gcc的另一种方法是使用ncc,这是专门为代码浏览设计的一个C编译器。它自带了自己的导航工具,非常值得一看。
CodeViz使用cncc
collection方法支持ncc(就像cdepn用于gcc一样),并且只支持C。ncc collection方法的真正重要之处在于它可以遍历函数指针。如果你下载并安装了ncc,如果是C代码、并且函数指针很常见,则使用cncc collection方法。
一旦安装了ncc,在构建Linux内核的情况下,命令将是:
make -i CC='ncc -ncoo -ncfabs' bzImage
make -i CC='ncc -ncoo -ncfabs' modules
find . -name \*.nccout | xargs cat > code.map.nccout
8. 生成 full.graph
下载部分的tar提供了一些full.graph文件。如果没有你想要的一个full.graph文件,请继续阅读。
为了创建一个full.graph,使用脚本genfull
。运行genfull --help
查看所有选项,但最简单的方法是在顶层源目录中不带参数运行这个脚本,在这之后,一个编译和一个文件full.graph将在顶级源目录中创建。
而它应该是有可能放置full.graph到dot中并查看postscript文件,建议您不要尝试。完整的图非常大,不太可能在合理的时间内绘制。确实应该使用gengraph
程序来创建更小的图。
9. 关于 full.graph 可能存在的问题
在更复杂的代码中,这个full.graph可能并不完美。例如,如果模块之间有重复的函数名,或者编译了多个二进制文件,那么genfull就不会对它们进行区分,这可能会导致命名冲突。如果你认为这是一个问题,你可以采取两个步骤。
首先,将cdepn
生成的图与cobjdump
生成的图进行比较。由于cobjdump
正在分析一个二进制文件,所以这个图不太可能是错的,它只是没有关于内联函数或宏的信息。对于linux内核,这个测试应该是这样的:
genfull -g cobjdump -o full.graph-objdump
genfull -g cdepn -o full.graph-cdepn
gengraph -t -d 5 -g full.graph-objdump -f kswapd -o kswapd-objdump.ps
gengraph -t -d 5 -g full.graph-cdepn -f kswapd -o kswapd-cdepn.ps
这将生成函数kswapd()的两个full.graph和两个调用图,可以进行比较,以确保cdepn
图是准确的。类似的方法也可以用于其他项目。
可能出现的第二个问题是函数名在模块之间重复。在这种情况下,最好的做法是使用-s
开关到genfull
来限制检查树的哪些分支。例如,在linux内核中,mm/
和drivers/char/drm
中有一个alloc_pages()
函数。如果只检查VM,那么命名冲突将是一个问题,genfull可以被调用为: genfull -s "mm include/linux drivers/block arch/i386"
。
这将涵盖大多数感兴趣的函数。在其他项目中,这将是不同库相互冲突的一种情况。例如,对于avifile,没有参数的genfull
会造成可怕的混乱。相反,必须使用-s开关为项目的每个部分生成一个full.graph。例如,player将被绘制:genfull -s "player" -o full.graph-player
。每个库都是单独绘制的。
10. 生成调用图
脚本gengraph基于full.graph文件为一个指定的函数生成一个调用图。gengraph --man
将提供您所需的所有信息。需要注意的最重要的选项是-g
,它决定使用什么collection方法。脚本完成后,将提供一个postscript文件,可以使用任何postscript查看器查看该文件。默认情况下,输出文件名将会是 functionname.ps
如果生成一个图需要很长时间,通常最好使用-d
先将深度限制在合理的范围内。我们基于内核2.4.20绘制alloc_pages()的图作为一个示例。
10.1 gengraph -f alloc_pages
结果:花了太长时间,按下ctrl-c键,受到一定深度的限制,无法了解发生了什么。
10.2 gengraph -d 10 -f alloc_pages
结果:输出图比较大,主要是内核库存函数无关紧要。使用-t
选项省略通常不感兴趣的函数。对于其他项目,编辑gengraph脚本并转到“subgenerate_trimlist”行,这个函数有一个函数列表,使用-t
选项来“修剪”:
10.3 gengraph -t -d 10 -f alloc_pages
结果:输出图仍然是巨大的,但是浏览一下这个图就会发现,调用“shrink_cache()”会在它下面生成一个巨大的图,看起来与页面分配没有直接关系。让我们只显示那个函数,而不是用-s
选项遍历它
10.4 gengraph -t -d 10 -s “shrink_cache” -f alloc_pages
结果:大大减小了图大小。剩下的图的大部分包含两个函数“try_to_free_pages_zone()”和“_free_pages_ok”。我们会
不遍历try_to_free_pages_zone(),并将使用-i
选项完全忽略_free_pages_ok()
10.5 gengraph -t -d 10 -s “shrink_cache try_to_free_pages_zone” -i “__free_pages_ok” -f alloc_pages
结果:完美,显示了一个很好的图,它清楚地显示了与页面分配相关的重要函数。稍后,可以单独绘制这个图中没有遍历的分支。
底线是,第一个图通常太大,需要削减。如何结合使用代码的经验和常识来缩减它。我发现,通常先将深度限制为4,然后开始忽略当前显然不感兴趣的函数,然后再遍历它们,这样做很有帮助。
11 基于正则表达式生成图
支持基于正则表达式选择要绘图、显示和忽略的函数。表达式的格式与perl
相同,只是没有//'s
。例如,要生成一个在内核中看起来像alloc函数的图,这是可行的:
gengraph -t -d 4 --func-re "^.?.?alloc(_page)?$" -i "pmd_alloc" -o allocs.ps
注意,使用--func-re
时,使用-o
开关或dot将无法创建抱怨输出文件名错误的图,这一点非常重要。
12 后期处理选项
genfull和gengraph都支持使用后期处理步骤。目前,支持两个。第一个是单个函数的堆栈使用情况。这是x86特有的,因为它依赖于对象文件,而不管使用什么collection方法。这主要有利于Linux内核,因为普通应用程序可以扩展它们的堆栈,并且不需要像以前那样担心堆栈的使用。第二个模块显示了函数对之间gengraph的累积使用情况。这对于显示系统调用和较低层函数之间的使用情况,以确定堆栈使用过多的位置非常方便。
有关使用后期处理选项的更多信息,请参阅genfull和gengraph的手册页。
13 守护进程/客户支持
对于大型输入图,生成调用图的最长操作是读取输入文件。相比之下,要在authors机器上生成一个小图形,读取输入图形需要4秒,生成输出文件需要0.1秒。要解决这个问题,如果指定了-q(--daemon)
开关,gengraph可以作为守护进程运行。如果你想知道它在做什么,就用-v。
gengraph -q -g /usr/src/linux-2.4.20-clean/full.graph
当它返回时,守护进程正在运行。要使用守护进程生成图形,请运行:
gengraph -q -t -d 2 -f alloc_pages
注意-q开关的使用,它表示gengraph应该作为客户机运行到守护进程实例。如果您感到无聊,请比较正常gengraph与作为客户机使用时的运行时间差异:-)。要停止守护进程,请执行以下操作:
echo QUIT > /tmp/codeviz.pipe
守护进程将关闭并清理。