从 ANSI C 代码获取控制流图
Posted
技术标签:
【中文标题】从 ANSI C 代码获取控制流图【英文标题】:Getting Control Flow Graph from ANSI C code 【发布时间】:2013-05-06 07:23:58 【问题描述】:我正在构建用于测试 ansi c 应用程序的工具。只需加载代码、查看控制流图、运行测试、标记所有被命中的顶点。我正在尝试通过解析代码自己构建 CFG。不幸的是,如果代码是嵌套的,它就会变得一团糟。 GCC 提供了从编译代码中获取 CFG 的能力。我可能会为其输出编写解析器,但我需要行号来设置断点。使用-fdump-tree-cfg
或-fdump-tree-vcg
输出控制流图时,有没有办法获取行号?
【问题讨论】:
【参考方案1】:对于 C 程序的控制流图,您可以查看现有的 C 语言 Python 解析器:
PyCParser pycparser pyclibrary(pyclibrary 的分支) joern CoFlo C/C++ 控制流图生成器和分析器调用图是与控制流图密切相关的构造。 有几种方法可用于为 C 代码创建调用图(函数依赖关系)。 这可能有助于推进控制流图的生成。 在 C 中创建依赖图的方法:
使用cflow:
cflow +pycflow2dot +dot (GPL, BSD) cflow 是健壮的,因为它可以处理无法编译的代码,例如缺少包括。如果大量使用预处理器指令,则可能需要--cpp
选项来预处理代码。
cflow + cflow2dot + dot (GPL v2, GPL v3, Eclipse Public License (EPL) v1) (注意 cflow2dot 在工作之前需要一些路径修复)
cflow +cflow2dot.bash(GPL v2,?)
cflow +cflow2vcg (GPL v2 , GPL v2)
enhanced cflow (GPL v2) 带有从图表中排除符号的列表
使用cscope:
cscope (BSD)
cscope +callgraphviz +dot +xdot
cscope +vim CCTree (C 调用树浏览器)
cscope +ccglue
cscope +CodeQuery 用于 C、C++、Python 和 Java
cscope +Python html producer
cscope +calltree.sh
ncc(类似 cflow)
KCachegrind(KDE 依赖查看器)
Calltree
不幸的是,以下工具要求代码是可编译的,因为它们依赖于 gcc 的输出:
CodeViz (GPL v2) (弱点:需要可编译源,因为它使用 gcc 转储 cdepn 文件) gcc +egypt +dot (GPL v*, Perl = GPL | Artistic license, EPL v1) (egypt
使用gcc
生成RTL
,因此对于任何有错误的源代码都失败了,甚至万一您只想专注于较大项目中的单个文件。因此,与更强大的基于cflow
的工具链相比,它并不是很有用。请注意,埃及默认情况下很好地支持从图中排除库调用,以让它更干净。
此外,可以使用crowfood
创建 C/C++ 的文件依赖关系图。
【讨论】:
调用图不是我需要的。我需要在代码中可视化分支。我必须向用户显示代码中的所有循环和决策点。我为 VCG 制作了自己的解析器,但我会检查您发布的工具。【参考方案2】:所以我做了更多的研究,得到节点的行号并不难。只需将lineno
选项添加到这些选项之一即可获得它。所以使用-fdump-tree-cfg-lineno
或-fdump-tree-vcg-lineno
。我花了一些时间来检查这些数字是否可靠。如果是 VCG 格式的图形,每个节点的标签包含 两个数字。这些是此节点表示的代码部分的开始和结束的行号。
【讨论】:
【参考方案3】:动态分析方法
在这个答案中,我描述了一些动态分析方法。
动态方法实际运行程序以确定调用图。
与动态方法相反的是静态方法,它试图在不运行程序的情况下仅从源代码中确定它。
动态方法的优点:
捕获函数指针和虚拟 C++ 调用。这些在任何重要的软件中都大量存在。动态方法的缺点:
您必须运行该程序,这可能很慢,或者需要您没有的设置,例如交叉编译 仅显示实际调用的函数。例如,可以根据命令行参数调用或不调用某些函数。KcacheGrind
https://kcachegrind.github.io/html/Home.html
测试程序:
int f2(int i) return i + 2;
int f1(int i) return f2(2) + i + 1;
int f0(int i) return f1(1) + f2(2);
int pointed(int i) return i;
int not_called(int i) return 0;
int main(int argc, char **argv)
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
用法:
sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
您现在处于一个很棒的 GUI 程序中,其中包含许多有趣的性能数据。
在右下角,选择“调用图”选项卡。这显示了一个交互式调用图,当您单击函数时,该图与其他窗口中的性能指标相关。
要导出图表,请右键单击它并选择“导出图表”。导出的 PNG 如下所示:
从中我们可以看出:
根节点是_start
,这是实际的ELF入口点,包含glibc初始化样板
f0
、f1
和 f2
按预期相互调用
pointed
也会显示出来,尽管我们使用函数指针调用它。如果我们传递了命令行参数,它可能不会被调用。
not_called
未显示,因为它没有在运行中被调用,因为我们没有传递额外的命令行参数。
valgrind
的酷之处在于它不需要任何特殊的编译选项。
因此,即使您没有源代码,只有可执行文件,您也可以使用它。
valgrind
设法通过轻量级“虚拟机”运行您的代码来做到这一点。
在 Ubuntu 18.04 上测试。
gcc -finstrument-functions
+ etrace
https://github.com/elcritch/etrace
-finstrument-functions
adds callbacks,etrace解析ELF文件并实现所有回调。
不幸的是,我无法让它工作:Why doesn't `-finstrument-functions` work for me?
声明的输出格式为:
\-- main
| \-- Crumble_make_apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
除了特定的硬件跟踪支持之外,这可能是最有效的方法,但缺点是您必须重新编译代码。
【讨论】:
原来的问题说我有源代码,重新编译完全没问题,因为它是对源代码进行动态分析的工具。以上是关于从 ANSI C 代码获取控制流图的主要内容,如果未能解决你的问题,请参考以下文章