绘制函数调用图(call graph)(2):cflow + graphviz
call graph cflow graphviz 函数调用图 函数调用关系图 更多
个人分类: call graph
https://blog.csdn.net/benkaoya/article/details/79751000
专栏导读
本专栏第一篇文章「专栏开篇」列出了专栏的完整目录,按目录顺序阅读,有助于你的理解。
前言
cflow是一款静态分析C语言代码的工具,通过它可以生成函数调用关系。
- 官网:https://www.gnu.org/software/cflow/
- 下载:http://ftp.gnu.org/gnu/cflow/
- 手册:https://www.gnu.org/software/cflow/manual/index.html
如果你英文可以,上面的手册就是很好的使用教程,本文只是简单的介绍下如何使用cflow,旨在让新手快速入门cflow而已。
安装cflow
在linux下安装cflow很简单,如下是我在Ubuntu下的安装命令:
# sudo apt-get install cflow
#
# cflow --version
cflow (GNU cflow) 1.4
Copyright (C) 2005, 2006, 2009, 2010, 2011 2009 Sergey Poznyakoff
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Sergey Poznyakoff.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
通过以下命令查看使用说明:
# cflow --help
- 1
由于cflow以来gawk,你的环境有可能是mawk,所以还得安装下gawk:
# sudo apt-get install gawk
- 1
使用cflow分析函数调用关系
举个简单的例子,以下是源文件whoami.c内容(源码来自官网手册):
/* whoami.c - a simple implementation of whoami utility */
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
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 ();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
运行cflow将生成以下输出:
# cflow whoami.c
main() <int main (int argc, char **argv) at whoami.c:24>:
fprintf()
who_am_i() <int who_am_i (void) at whoami.c:7>:
getpwuid()
geteuid()
getenv()
fprintf()
printf()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
cflow默认是分析main函数,可以通过 -m 选项分析其他函数:
# cflow -m who_am_i whoami.c
who_am_i() <int who_am_i (void) at whoami.c:7>:
getpwuid()
geteuid()
getenv()
fprintf()
printf()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
cflow只能以ASCII文本的形式输出函数调用关系,不能输出图片格式,对于大型项目的代码来说,庞杂的文本输出简直“惨不忍睹,无法直视”。需要其他工具的辅助,才能将本文格式转化为可读性更强的图片格式,大致步骤如下:
- cflow工具:输出文本格式的函数调用关系;
- tree2dotx脚本:将文本格式转化为dot格式;
- graphviz工具:将dot格式转化为图片格式;
从文本文件转为dot文件
将cflow输出的文本文件转化为dot格式的工具有tree2dotx,是否有其他工具,有待研究。通过以下命令下载tree2dotx脚本:
# wget -c https://github.com/tinyclub/linux-0.11-lab/raw/master/tools/tree2dotx -O /usr/bin/tree2dotx
# chmod +x /usr/bin/tree2dotx
- 1
- 2
将cflow输出的文本文件转化为dot格式:
# cflow whoami.c | tree2dotx > out.dot
- 1
从dot文件转为图片文件
安装graphviz:
# sudo apt-get install graphviz
- 1
将dot格式转化为图片格式:
# dot -Tgif out.dot -o out.gif
- 1
最终生成的图像如下所示:
可以看出,连系统函数printf都显示出来了,这往往不是我们所关心的,有没有什么办法可以忽略这些系统函数呢?我还没找到方法,有知道的诚盼您的留言指教。
其他补充
- 问:cflow能同时分析一个源文件中多个函数的call graph吗?
- 答:cflow默认只分析main函数的call graph,如果main不存在,将分析该文件的所有函数。可以通过 -m 选项分析指定的函数,如果指定的函数不存在,也会分析该文件的所有函数。可以利用这个特点,通过 -m 指定一个空的函数名,让cflow分析所有函数的call graph,如:
# cflow -m= file1.c
- 1
- 问:cflow可以同时分析多个源文件吗?
- 答:可以,使用如下两种命令都可以:
# cflow -m= file1.c file2.c
# cflow -m= *.c
- 1
- 2
需要注意的是,如果多个源文件出现同名函数,cflow会警告,并且只分析其中一个main函数。
- 问:cflow可以分析整个目录(包括子目录)的源文件吗?
- 答:通过 cflow –help 查看帮助,我没找到有这方面的选项。
总结
做个简单的总结:
- 使用cflow工具,分析源码,得到文本格式的函数调用关系;
- 使用tree2dotx脚本,将文本格式转化为dot格式;
- 使用graphviz工具,可视化函数调用,将dot格式转化为图片格式;
https://blog.csdn.net/Wind4study/article/details/53366419
1. 安装
sudo apt-get install cflow
2.使用
cflow [options...] [file]...
例:
cflow main.c
生成main.c文件例的函数调用关系
cflow -x main.c
生成交叉引用表,查看函数调用的位置和文件
cflow -o call_tree.txt main.c
生成调用关系并输出到call_tree.txt文件
cflow -d 5 -o call_tree.txt main.c
指定输出的最大调用深度位5
其他选项:用 cflow --help查看
通用选项:
-d, --depth=NUMBER 设置流程图的绘制深度
-f, --format=NAME
使用指定的输出格式名。有效名称是‘gnu’(默认)和‘posix’
-i, --include=CLASSES 包含指定的符号类(见下)。在 CLASSES
之前放上 ^ 或 - 将它们从输出中省去
-o, --output=FILE 设置输出文件名(默认为
-,即标准输出)
-r, --reverse * 打印反向调用树
-x, --xref 仅生成交叉引用列表
--include 参数的符号类
_ 以下划线开始的符号名
s 静态符号
t typedefs(仅针对交叉引用)
x 所有的数据符号(外部的和静态的)
句法分析控制:
-a, --ansi * 仅接受 ANSI C 标准的源码
-D, --define=NAME[=DEFN] 将 NAME 预定义为一个宏
-I, --include-dir=DIR 将 DIR
目录添加至可被头文件搜索到的目录列表
-m, --main=NAME 假定主函数是个叫 NAME 的函数
-p, --pushdown=NUMBER 设置初始标识栈大小为 NUMBER
--preprocess[=COMMAND], --cpp[=COMMAND]
* 执行指定的预处理命令
-s, --symbol=SYMBOL:[=]TYPE Register SYMBOL with given TYPE, or define an
alias (if := is used). Valid types are: keyword
(or kw), modifier, qualifier, identifier, type,
wrapper. Any unambiguous abbreviation of the above
is also accepted
-S, --use-indentation * 依赖缩进风格
-U, --undefine=NAME 取消前面所有的 NAME 预定义
输出控制:
-b, --brief * 简洁输出
--emacs * 为与 GNU Emacs
联合使用而显示额外的格式输出
-l, --print-level * 打印调用关系树的嵌套结构
--level-indent=ELEMENT 控制图显示
-n, --number * 打印行号
--omit-arguments * 不在函数声明部分打印参数列表
--omit-symbol-names * 不在声明字符串中打印符号名
-T, --tree * 绘制 ASCII 形式的树
输出信息选项:
--debug[=NUMBER] 设定调试级别
-v, --verbose * 详细的错误诊断报告
-?, --help 显示此帮助列表
--usage 显示一份简洁的用法信息
-V, --version 打印程序版本
选项完整形式所必须用的或是可选的参数,在使用选项缩写形式时也是必须的或是可选的。
*
每个带有星号标记的选项是相反操作,就像前面带有‘no-’的长选项名一样。例如,--no-cpp
就是取消 --cpp 选项用的。
其他工具:
ctags:
为所有函数创建索引
cxref:
生成交叉引用表