前言

拿到一份陌生的 C 工程,想要了解整个工程的脉络,你会使用什么方法呢?

函数调用

一般都是通过函数调用关系来理清整个工程的运作流程,通常使用的软件是 source insight。从 main 函数开始,查看 main 函数调用了哪些函数,然后进入各个子函数。以此类推,逐渐展开整个脉络。我之前也是一直使用这种方法,我称之为“人肉整理”。这种方法是很耗时的,一个不大的工程就可能要整理大半天。从那时起,我就时常想,有没有自动化的软件来干这个事情?后来找到了一款:calltree,听名字就知道它的功能了,“调用树”,用来理清函数调用关系的。不过,该款软件早已停更,版本较老,用起来不太顺手。

cflow

后来又发现了一款:cflow。在 ubuntu 下直接使用命令就可以安装该软件,如下

sudo apt install cflow

cflow 能够分析 C 文件中的控制流,看个例子

$ cflow -T log.c
+-log_init() <void log_init (void) at log.c:193>+-InitializeCriticalSection()+-wget_console_init()+-wget_logger_set_func()+-wget_get_logger()+-write_debug_stderr() <void write_debug_stderr (const char *data, size_t len) at log.c:157>| \-write_debug() <void write_debug (FILE *fp, const char *data, size_t len) at log.c:138>|   \-write_out() <void write_out (FILE *default_fp, const char *data, size_t len, int with_timestamp, const char *colorstring, wget_console_color color_id) at log.c:55>|     +-strcmp()|     +-open()|     +-wget_buffer_init()|     +-isatty()|     +-fileno()|     +-wget_buffer_strcpy()|     +-gettime()|     +-localtime_r()|     +-wget_buffer_printf_append()|     +-wget_buffer_memcat()|     +-wget_buffer_strcat()|     +-fwrite()|     +-EnterCriticalSection()|     +-wget_console_set_fg_color()|     +-fflush()|     +-wget_console_reset_fg_color()|     +-LeaveCriticalSection()|     +-write()|     +-close()|     \-wget_buffer_deinit()
...

tree2dotx

为了更直观地展示函数调用关系,我们可以使用 xdot 工具。不过使用 xdot 工具地前提是我们必须要有一份用 xdot 格式表示节点地文件。这就需要使用另外一个工具 tree2dotx,可以从这里获取,将其存为 tree2dotx 文件,然后放入系统路径。
运行一下看看效果

$ cflow log.c | tree2dotx
digraph G{ranksep = 1;rankdir=LR;size="1920,1080";node [fontsize=16,fontcolor=blue,style=filled,fillcolor=Wheat,shape=box];"log_init" -> "InitializeCriticalSection";"log_init" -> "wget_console_init";"log_init" -> "wget_logger_set_func";"log_init" -> "wget_get_logger";"log_init" -> "write_debug_stderr";"write_debug_stderr" -> "write_debug";"write_debug" -> "write_out";"write_out" -> "strcmp";"write_out" -> "open";"write_out" -> "wget_buffer_init";"write_out" -> "isatty";"write_out" -> "fileno";"write_out" -> "wget_buffer_strcpy";"write_out" -> "gettime";"write_out" -> "localtime_r";
...
}

xdot

xdot 功能能够以图形的方式展示节点之间的关系,在 ubuntu 下使用 apt 命令就可以安装

sudo apt install xdot

运行看下效果

$ cflow log.c | tree2dotx > out.dot
$ xdot out.dot


可以非常直观的看到 log.c 文件中的函数调用关系,鼠标放在某个函数上,前后的箭头还会变成红色,用来指示被调用和调用关系。

优化

上述 tree2dotx 脚本其实是有点问题的,我对它做了一些优化,如下

  • 去重。经过 tree2dotx 处理过的节点,有重复的,导致连线翻倍,去重后效果如下,和上面对比是不是简洁多了

    命令如下
cflow log.c | tree2dotx | awk '!a[$0]++' > out.dot
  • 去除多余空格
    原始 tree2dotx 脚本在将函数调用关系转成节点时,有些函数后面多出了一个空格,将脚本中的 sed -e "s/<.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' ' 改成 sed -e "s/ <.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' ',就可以了。
  • 增加子节点
    这样就可以显示当前函数属于哪个 C 文件了

附件

优化后的 tree2dotx 完整代码

$ cat /usr/local/bin/tree2dotx
#!/bin/bash
#
# tree2dotx --- transfer a "tree"(such as the result of tree,calltree,cflow -b)
#                 to a picture discribed by DOT language(provided by Graphviz)
#
# Author: falcon <wuzhangjin@gmail.com>
# Update: 2007-11-14, 2015-3-19
# Usage:
#
#       tree -L 2 -d /path/to/a/directory | bash tree2dotx | dot -Tsvg -o tree.svg
#       cd /path/to/a/c/project/; calltree -gb -np -m *.c | bash tree2dotx | dot -Tsvg -o calltree.svg
#       cd /path/to/a/c/project/; cflow -b -m setup_rw_floppy kernel/blk_drv/floppy.c | bash tree2dotx | dot -Tsvg -o cflow.svg
## Set the picture size, direction(LR=Left2Right,TB=Top2Bottom) and shape(diamond, circle, box)
size="1920,1080"
direction="LR"
shape="box"# color, X11 color name: http://en.wikipedia.org/wiki/X11_color_names
fontcolor="blue"
fillcolor="Wheat"# fontsize
fontsize=16# Specify the symbols you not concern with space as decollator here
filterstr="";input=`cat`# output: dot, flame
output="dot"has_subgraph="0"
ordering="0"# Usage#grep -v ^$ | catfunction usage
{echo ""echo "  $0 "echo ""echo "     [ -f  \"filter1 filter2 ...\" ]"echo "     [ -s  size, ex: 1080,760; 1920,1080 ]"echo "     [ -d  direction, ex: LR; TB ]"echo "     -h  get help"echo ""
}function subgraph() {echo "$input" \| grep -e " at " \| sed 's/).* at /)/g;s/:.*//g;s/ //g' \| sed -r 's/^(.*)\(\)(.*)$/\tsubgraph "cluster_\2" { label="\2";\1;}/' \| sort -u
}while getopts "f:s:S:d:e:h:o:r:" opt;
docase $opt inf)filterstr=$OPTARG;;s)size=$OPTARG;;S)shape=$OPTARG;;d)direction=$OPTARG;;e)has_subgraph=$OPTARG;;o)output=$OPTARG;;r)ordering=$OPTARG;;h|?)usage $0;exit 1;;;esac
done# Transfer the tree result to a file described in DOT languageecho "$input" | \
grep -v ^$ | grep -v "^[0-9]* director" \
| sed -e "s/ <.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' ' \
| sed -e "s/ \[.*\].*//g" \
| awk '{if(NR==1) system("basename "$0); else printf("%s\n", $0);}' \
| awk -v fstr="$filterstr" '# function for filter the symbols you not concernfunction need_filter(node) {for ( i in farr ) {if (match(node,farr[i]" ") == 1 || match(node,"^"farr[i]"$") == 1) {return 1;}}return 0;}BEGIN{# Filternode array are used to record the symbols who have been filtered.oldnodedepth = -1; oldnode = ""; nodep[-1] = ""; filter[nodep[-1]] = 0;oldnodedepth_orig = -1; nodepre = 0; nodebase = 0; nodefirst = 0;output = "'$output'";#printf("output = %s\n", output);# Store the symbols to an array farrsplit(fstr,farr," ");# print some setting infoif (output == "dot") {printf("digraph G{\n");if(ordering == "1") {printf("ordering=out;\n");}printf("ranksep = 1;\n");printf("\trankdir='$direction';\n");printf("\tsize=\"'$size'\";\n");printf("\tnode [fontsize='$fontsize',fontcolor='$fontcolor',style=filled,fillcolor='$fillcolor',shape='$shape'];\n");}}{# Get the node, and its depth(nodedepth)# nodedepth = match($0, "[^| `]");nodedepth = match($0, "[[:digit:]|[:alpha:]]|[[:alnum:]]");node = substr($0,nodedepth);# printf("%d %d %s \n", nodedepth, oldnodedepth_orig, node);if (nodefirst == 1 && oldnodedepth_orig > 0) {nodefirst = 0;nodebase = nodedepth-oldnodedepth_orig;}if (nodedepth == 0)nodedepth=1;tmp = nodedepth;# printf("pre=%d base=%d np=%d oldnp=%d node=%s \n", nodepre, nodebase, tmp, oldnodedepth_orig, node);if (nodedepth != 0 && oldnodedepth_orig == -1) {nodepre = nodedepth-1;nodefirst = 1;nodedepth = 0;} else if (nodebase != 0) {nodedepth = int((nodedepth-nodepre)/nodebase);}# if whose depth is 1 less than him, who is his parentif (nodedepth-oldnodedepth == 1) {nodep[nodedepth-1] = oldnode;}# for debugging# printf("%d %s\n", nodedepth, node);# printf("\t\"%s\";\n",node);# print the vectorsif (oldnodedepth != -1) {# if need filter or whose parent have been filter, not print it, and set the flat of filter to 1if (need_filter(node) || filter[nodep[nodedepth-1]] == 1) {filter[node] = 1;#       printf("node = %s, filter[node] = %d\n", node, filter[node]);} else if (nodep[nodedepth-1] != "") {if (output == "dot") {printf("\t\"%s\" -> \"%s\";\n", nodep[nodedepth-1], node, nodep[nodedepth-1], node);} else {for (i = 0; i < nodedepth; i++)printf("%s;", nodep[i]);printf("%s 1\n", node);}#       printf("\t\"%s\" -> \"%s\"[label=\"%s>%s\"];\n", nodep[nodedepth-1], node, nodep[nodedepth-1], node);}}# save the old depth and the old nodeoldnodedepth_orig = tmp;oldnodedepth = nodedepth;oldnode = node;} END {#                if (output == "dot")
#           printf("}");}'echo ""
if [ $has_subgraph == "1" ]
thensubgraph
fi
echo "}"
cflow -d 3 wget.c | tree2dotx -e 1 -r 1 | awk '!a[$0]++' > out.dot && cat out.dot

tree2dotx -e 0/1 指定是否展示子图(函数所在文件)
tree2dotx -r 0/1 指定是否按照函数出现顺序展示图像

图像

还可以将 xdot 显示的图像输出为图片

dot -Tgif out.dot -o out.gif

参考

https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf

cflow——C语言函数调用关系生成器相关推荐

  1. 静态分析C语言生成函数调用关系的利器——cflow

    除了<静态分析C语言生成函数调用关系的利器--calltree>一文中介绍的calltree,我们还可以借助cflow辅助我们阅读理解代码.(转载请指明出于breaksoftware的cs ...

  2. c语言调用graphviz_c语言分析函数调用关系图(call graph)的几种方法

    一.基于 Doxygen或 lxr 的API形式的文档系统. 二.基于CodeViz, CodeViz是<Understanding The Linux Virtual Memory Manag ...

  3. [授权发表]源码分析:静态分析 C 程序函数调用关系

    故事缘由 工欲善其事,必先利其器.今天我们来玩转一个小工具,叫 Callgraph,它可以把 C 语言的函数调用树(或者说流程图)画出来. 传统的命令行工具 Cscope,Ctags 可以结合vim ...

  4. 《编写高质量代码:改善Objective-C程序的61个建议》——建议8:C语言与Objective-C语言的关系是充分而非必要条件...

    本节书摘来自华章出版社<编写高质量代码:改善Objective-C程序的61个建议>一 书中的第2章,作者:刘一道,更多章节内容可以访问云栖社区"华章计算机"公众号查看 ...

  5. C语言C6292错误,测试c语言函数调用性能因素

    标签: 函数调用:即调用函数调用被调用函数,调用函数压栈,被调用函数执行,调用函数出栈,调用函数继续执行的一个看似简单的过程,系统底层却做了大量操作. 操作: 1,               调用函 ...

  6. C语言函数调用栈(一)

    以下全文转载自:C语言函数调用栈(一) 程序的执行过程可看作连续的函数调用.当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行.函数调用过程通常使用堆栈实现,每个用户态 ...

  7. 超酷!!!成功使用doxygen+Graphviz+HtmlHelp 自动生成函数调用关系图

    使用Doxygen+Graphviz+HtmlHelp 生成函数调用关系图 在写这篇博客之前,首先感谢一下另外一篇博主的文章,Windows平台下Doxygen+GraphViz+HtmlHelp自动 ...

  8. 函数调用关系/结构图Callgraph

    文章目录 代码结构图 Callgraph安装 使用方法简介 运行结果 代码结构图   源码分析是程序员离不开的话题,无论是研究开源项目,还是平时做各类移植.开发,都避免不了对源码的深入解读.对于一个功 ...

  9. c语言中staloc是什么意思,C语言函数调用栈(三)

    6 调用栈实例分析 本节通过代码实例分析函数调用过程中栈帧的布局.形成和消亡. 6.1 栈帧的布局 示例代码如下: //StackReg.c #include //获取函数运行时寄存器%ebp和%es ...

  10. 自学golang【第一章:go语言基础知识】为什么要学习go语言?go语言与c语言的关系?go语言创始人?go语言的特性有哪些?go语言适合做什么?国内外有哪些企业或项目使用go语言?

    事先声明:本文部分内容参考了尹成的笔记,如果侵权请联系删除. 我是一名自学go语言的初学者,从今天开始我将会坚持更新go语言的相关知识,从入门到精通,如果大家有什么需要可以加我QQ:239479969 ...

最新文章

  1. html 字母列表通讯录,仿微信通讯录字母排序列表
  2. ASP.NET Web页面(.aspx)添加用户控件(.ascx)无显示的问题
  3. Day9 操作系统介绍
  4. java中override快捷键_【基础回溯1】面试又被 Java 基础难住了?推荐你看看这篇文章。...
  5. Python引用模块和查找模块路径
  6. 加密机工作原理_三相异步电动机的工作原理
  7. Asp.Net Core Blazor之容器部署
  8. 洛谷 P3367 【模板】并查集
  9. 如何在React Native中使用Redux Saga监视网络更改
  10. 与Serverless 的第一次亲密接触
  11. 【渝粤教育】电大中专营销策划原理与实务作业 题库
  12. java基础----对象的创建过程(初始化、析构、清理)
  13. uniapp文件路径转base64格式
  14. CC++期末课程设计——产品管理系统(源代码+详细注释)
  15. 简单获取apk的签名及shal码
  16. 华为手机html乱码,华为手机语言设置中文
  17. java获取三个月之前时间与当前时间
  18. oracle冲账语句_ORA-00xx问题 -oracle卸载不成功
  19. 噪音恐惧症_比恐惧强:公开的心理健康
  20. Python经典问题——身体指数BMI

热门文章

  1. php 获取文件名 行号,PHP – 找到文件中的字符串,然后显示它的行号
  2. java中spring的注解_Java代码中spring注解浅析
  3. echarts无数据时显示无数据_无服务器数据库竞技,哪家云服务落伍了?
  4. 那些你不可错过的Java博客
  5. Monitor HDU6514 二维差分入门学习
  6. Python规范:用用assert
  7. CPU制造工艺完整过程(图文)
  8. effective C++ 条款 48:认识template元编程
  9. 最小割最大流算法matlab,matlab练习程序(最大流/最小割)
  10. create 添加async和不添加的区别_六偏磷酸钠不可怕—谈谈食品添加剂中的用途