怎么用codeviz 1.0.9 版在 windows + cygwin 绘制函数调用关系图
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) 从 下载并安装 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()都是用户编写的函数:
函数调用关系图可以提供哪些与测试有关的信息
绘制函数调用关系图对理解大型程序大有帮助。我想大家都有过一边读源码(并在头脑中维护一个调用栈),一边在纸上画函数调用关系,然后整理成图的经历。如果运气好一点,借助调试器的单步跟踪功能和call stack窗口,能节约一些脑力。
不过如果要分析的是脚本语言的代码,那多半只好老老实实用第一种方法了。如果在读代码之前,手边就有一份调用图,岂不妙哉?下面举出我知道的几种免费的分析C/C++函数调用关系的工具。
函数调用关系图(call graph)是图(graph),而且是有向图,多半还是无环图(无圈图)——如果代码中没有直接或间接的递归的话。Graphviz是专门绘制有向图和无向图的工具,所以很多call graph分析工具都以它为后端(back end)。那么前端呢?就看各家各显神通了。
调用图的分析分析大致可分为“静态”和“动态”两种,所谓静态分析是指在不运行待分析的程序的前提下进行分析,那么动态分析自然就是记录程序实际运行时的函数调用情况了。
静态分析又有两种方法,一是分析源码,二是分析编译后的目标文件。
分析源码获得的调用图的质量取决于分析工具对编程语言的理解程度,比如能不能找出正确的C++重载函数。Doxygen是源码文档化工具,也能绘制调用图,它似乎是自己分析源码获得函数调用关系的。GNU cflow也是类似的工具,不过它似乎偏重分析流程图(flowchart)。
对编程语言的理解程度最好的当然是编译器了,所以有人想出给编译器打补丁,让它在编译时顺便记录函数调用关系。CodeViz(其灵感来自Martin Devera (Devik) 的工具)就属于此类,它(1.0.9版)给GCC 3.4.1打了个补丁。
另外一个工具egypt的思路更巧妙,不用大动干戈地给编译器打补丁,而是让编译器自己dump出调用关系,然后分析分析,交给Graphviz去绘图。不过也有人另起炉灶,自己写个C语言编译器(ncc),专门分析调用图,勇气可嘉。不如要是对C++语言也这么干,成本不免太高了。分析C++的调用图,还是借助编译器比较实在。
vscode配置文件的关系
简单来说,vscode是一款文本编辑器,而不是ide。这就是说,vscode只提供编辑的环境而不提供编译的环境,如果想要用vscode来集成开发环境,则必须经过以下几步:
安装必须的编译器(比如mingw,python,texlive等)
配置相应编译环境(添加环境变量等)
安装匹配的vscode的拓展插件
通过拓展插件提供的“属性”将外部编译器链接到vscode
同时,一个文本编辑器肯定不会提供运行程序的功能,vscode也是如此。要运行程序,有如下几种方法:
vscode的一些拓展插件中会提供配置好的终端和运行程序的命令,我们可以直接使用;
vscode中有专门运行程序的拓展插件code-runner,可以安装后使用;
vscode提供了对数种终端的接口,我们可以在vscode中像使用cmd小黑框那样使用终端,由此我们可以在vscode终端中运行程序;
这样,我们大致了解了使用vscode编译、运行程序的方法。
vscode的配置
首先,我们要明确vscode的配置的最小单元是文件夹,即可以使用打开文件夹功能我们可以把不同的文件夹链接到不同的外部编译器、实现不同的快捷任务、快速进行debug和详细设定拓展插件的功能等等。
因此,我们先速览vscode的配置结构,如下图:
可以看到,
vscode下有全局设置settings.json;之后是工作区,和工作区设置settings.json,一个工作区包含了多个文件夹;
每个文件夹中有相同的.vscode文件夹,这个文件夹中的三个文件settings.json,tasks.json,launch.json,它们就是配置的核心。
每个文件夹通过配置可以编译运行不同语言的文件:c++,python和html,同时可以通过同一个工作区直接访问,这就是vscode的集成性和方便性。