Cflow 使用详解
朝歌 译
2014-11-8
目录
1、cflow 简介 ..................................................................................................................... 3
2、使用 cflow 分析程序的简要方法 .................................................................................. 3
3、两种类型的流图............................................................................................................. 5
4、各种输出格式 ................................................................................................................ 7
5、处理递归调用 ................................................................................................................ 8
6、控制符号类型 .............................................................................................................. 12
6.1、语法类 ................................................................................................................... 15
6.2、符号别名 ............................................................................................................... 16
6.3、GCC 初始化 .......................................................................................................... 16
7、运行预处理器 .............................................................................................................. 17
8、使用 ASCII 码来生成流图 ........................................................................................... 18
9、交叉引用输出 .............................................................................................................. 20
10、配置文件和变量 ........................................................................................................ 21
11、在 Makefiles 中使用 cflow ...................................................................................... 22
12、cflow 选项的完整列表 .............................................................................................. 23
13、GNU Emacs 使用 cflow .......................................................................................... 26
14、附录 wc 命令的源文件 .............................................................................................. 28
1、cflow 简介
cflow 工具用于分析 C 语言实现的源文件集并输出各个函数之间的依赖关系图。
cflow 可以生成两种类型的图:正向图和逆向图。正向图从 main()函数开始,递
归显示 main()函数调用的所有函数。相反,逆向图是一个子图的集合,使用递
归命令时,为每个函数列出它的调用者。
除了这两种输出模式,cflow 也可以为输入文件中的所有遇到的符号生成一个交
叉引用列表。
该程序还提供了对将出现在输出中出现的符号的详细控制,允许忽略用户不感兴
趣的符号。详细的输出格式也是可配置的。
2、使用 cflow 分析程序的简要方法
我们先从一个例子开始熟悉 GNU cflow 的用法。假设你已经有了一个 whoami
命令的简单实现,并且你想获得它的函数依赖关系图。下面的是程序:
/* whoami.c - a simple implementation of whoami utility */
#include
#include
#include
#include
int
who_am_i (void)
{
struct passwd *pw;
char *user = NULL;
pw = getpwuid (geteuid ());
if (pw)
user = pw->pw_name;
else if ((user = getenv ("USER")) == NULL)
{
fprintf (stderr, "I don't know!\n");
return 1;
}
printf ("%s\n", user);
return 0;
}
int
main (int argc, char **argv)
{
if (argc > 1)
{
fprintf (stderr, "usage: whoami\n");
return 1;
}
return who_am_i ();
}
运行 cflow 产生下面的输出:
$ cflow whoami.c
main() :
fprintf()
who_am_i() :
getpwuid()
geteuid()
getenv()
fprintf()
printf()
这是一个正向调用图显示了输入文件中的调用者-被调用者依赖关系。每一行以
一个函数名开始,紧跟后面有一对括号来指明这是一个函数。如果这个函数在某
一个输入文件中定义,这一行会使用一对尖括号,其中显示函数的原型和它被定
义的位置。这一行将会以:结尾来表示这个函数调用了其它函数。比如,这一行
main() :
显示 main 函数在源文件 whoami.c 中的第 25 行定义,原型是 int main (int
argc,char **argv),最后的冒号表示 main 函数调用了其他函数。
这一行后面的是被 main 函数调用的函数。每一行都会根据嵌套关系有相应的缩
进。
通常 cflow 会显示完整的函数原型。然而有时你希望忽略原型的一部分。一些选
项可以用来完成这个功能。使用--omit-symbol-names 选项来打印没有函数名的原
型。使用--omit-arguments 选项来忽略参数列表。这些选项可以被用做很多用途,
其中一个就是使结果图更加的紧凑。为了显示他们的作用,下面是使用上述两种
--omit-选项的结果:main() :
默认情况下,cflow 从 main()函数开始输出正向图。当分析一个完整的 C 程序集
的时候这是非常的便利的。但是有时候用户可能只想看到从特定函数出发的部分
图。使用—main(-m)选项,Cflow 允许选择这样的功能调用。因此,运行$cflow
--main who_am_i whoami.c,将会得到下面的结果:
who_am_i() :
getpwuid()
geteuid()
getenv()
fprintf()
printf()
3、两种类型的流图
在前面我们讨论了正向图,用于展示调用者-被调用者的依赖关系。另一种 cflow
输出是逆向图,列举被调用者-调用者的依赖关系。,为了生成逆向图,运行 cflow
的时候要使用--reverse(-r)选项。比如使用下面的例子:
$ cflow --reverse whoami.c
fprintf():
who_am_i() :
main()
main()
getenv():
who_am_i() :
main()
geteuid():
who_am_i() :
main()
getpwuid():
who_am_i() :
main()
main()
printf():
who_am_i() :
main()
who_am_i() :
main()
这个输出包含了几个子图,每一个字图描述了一个特定的函数的调用者。因此,
第一个子图说明函数 fprintf 被两个函数调用:who_am_i 和 main。同时他也被 main
函数直接调用。
第一个值得注意的地方是在输出中 who_am_i 重复出现了多次。这是一个详细的
输出,为了让输出显得更加简洁,可以使用--brief(-b)选项。比如:
$ cflow --brief --reverse whoami.c
fprintf():
who_am_i() :
main()
main() [see 3]
getenv():
who_am_i() : [see 2]
geteuid():
who_am_i() : [see 2]
getpwuid():
who_am_i() : [see 2]
main() [see 3]
printf():
who_am_i() : [see 2]
who_am_i() : [see 2]
在简要输出中,一旦某个给定的函数被写入,随后的关于该函数的调用实例中将
会只包含它的定义和第一次输出行的引用。
如果输出图比较大,查找需要的行数就会比较麻烦(除非你使用 Emacs 的
cflow-mode)。这种情况下可以使用特殊的选项--number (-n),这样就可以在输出
时显示每一行的顺序标号。使用这个选项,上面的输出将变成这样:
$ cflow --number --brief --reverse whoami.c
1 fprintf():
2 who_am_i() :
3 main()
4 main() [see 3]
5 getenv():
6 who_am_i() : [see 2]
7 geteuid():
8 who_am_i() : [see 2]
9 getpwuid():
10 who_am_i() : [see 2]
11 main() [see 3]
12 printf():
13 who_am_i() : [see 2]
14 who_am_i() : [see 2]
当然,--brief 和--number 选项对正向图和逆向图都起作用。
4、各种输出格式
前面所描述的输出格式被称为 GNU 类型。除此之外,cflow 也可以使用 POSIX 产
生格式化的输出。这种格式,输出的每一行都以一个参考数字开始,比如,最开
始是输出行的顺序号,后面跟随每一个嵌套层的固定长度的缩进。然后如果有的
话依次是函数的名字、冒号、函数的原型。紧跟在函数原型后面的是定义的位置
(包括文件名和行号)。函数顶一个位置都被尖括号围住。如果函数的定义没有
找到,该行将会以一个空的尖括号结尾。
使用格式化输出要么在命令行中通过--format=posix (-f posix)选项指定,要么设置
环境变量 POSIXLY_CORRECT 。
使用 POSIX 格式处理我们的样例文件,如下:
$ cflow --format=posix whoami.c
1 main: int (int argc,char **argv),
2 fprintf: <>
3 who_am_i: int (void),
4 getpwuid: <>
5 geteuid: <>
6 getenv: <>
7 fprintf: <>
8 printf: <>
是否在输出中要包含函数的参数列表现在并不清楚。默认情况下 cflow 将会全部
打印它们。然而一些程序使用 cflow 分析时希望省略参数列表,这可以使用
--omit-arguments 选项实现。
cflow 未来的版本中将会提供更多的输出格式,包括 XML 和 HTML 输出。目前你
可以使用 VCG 工具来创建典型的图。根据 xvcg 来转变输出格式可以使用
cflow2vcg 程序在 GPL 下都是可用的。
Cflow2vcg 期望使用 POSIX 格式图,每层嵌套的缩进为一个水平制表符,第 0 层
使用额外的 tab 字符,在函数声明中没有参数列表。这样用可兼容 cflow2vcg 产
生的输出格式,调用 cflow 如下:
cflow --format=posix --omit-arguments \
--level-indent='0=\t' --level-indent='1=\t' \
--level-indent=start='\t'
你可以使用下面的脚本来虚拟调用这三个工具:
#! /bin/sh
cflow --format=posix --omit-arguments \
--level-indent='0=\t' --level-indent='1=\t' \
--level-indent=start='\t' $* |
cflow2vcg | xvcg -
5、处理递归调用
有时候程序中包含调用自己的函数。GNU 输出格式为这种函数提供了特殊的表
示。在结束字符-冒号之前,会有一个标号‘(R)’作为递归函数的标志。随后递
归调用处会在行结尾使用‘(recursive: see refline)’标识出。这里的 refline 表示递