我正在构建用于测试 ansi c 应用程序的工具。只需加载代码、查看控制流图、运行测试、标记所有被命中的顶点。我正在尝试通过解析代码自己构建 CFG。不幸的是,如果代码是嵌套的,它就会变得一团糟。GCC 提供了从编译代码中获取 CFG 的能力。我可能会为其输出编写解析器,但我需要行号来设置断点。-fdump-tree-cfg
用or输出控制流图时有没有办法获取行号-fdump-tree-vcg
?
3 回答
对于 C 程序的控制流程图,您可以查看现有的 C 语言 Python 解析器:
- PyCParser
- pycparser
- pyclibrary(pyclibrary的分支)
- 乔恩
- 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)
增强的 cflow (GPL v2),带有从图表中排除符号的列表
使用cscope:
示波器 (BSD)
cscope + callgraphviz +dot +xdot
cscope +vim CCTree (C Call-Tree Explorer)
cscope + ccglue
用于 C、C++、Python 和 Java 的cscope + CodeQuery
cscope + Python html 生成器
cscope + calltree.sh
ncc(类似 cflow)
KCachegrind(KDE 依赖查看器)
不幸的是,以下工具要求代码是可编译的,因为它们依赖于 gcc 的输出:
- CodeViz (GPL v2) (弱点:需要可编译的源代码,因为它使用 gcc 转储 cdepn 文件)
- gcc + egypt +dot (GPL v*, Perl = GPL | Artistic license, EPL v1) (
egypt
用于gcc
生产RTL
,因此对于任何有错误的源代码都失败了,或者即使您只想专注于更大项目中的单个文件. 因此,与更健壮的基于工具链相比,它不是很有用cflow
。请注意,埃及默认情况下很好地支持从图中排除库调用,以使其更清洁。
此外,可以使用crowfood
.
所以我做了更多的研究,得到节点的行号并不难。只需将lineno
选项添加到这些选项之一即可。所以使用-fdump-tree-cfg-lineno
or -fdump-tree-vcg-lineno
。我花了一些时间检查这些数字是否可靠。如果是VCG格式的图形,每个节点的标签包含两个数字。这些是此节点表示的代码部分的开始和结束的行号。
动态分析方法
在这个答案中,我描述了一些动态分析方法。
动态方法实际运行程序以确定调用图。
与动态方法相反的是静态方法,它试图在不运行程序的情况下仅从源代码中确定它。
动态方法的优点:
- 捕获函数指针和虚拟 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
+ 跟踪
https://github.com/elcritch/etrace
-finstrument-functions
添加回调,etrace 解析 ELF 文件并实现所有回调。
但不幸的是,我无法让它工作:为什么 `-finstrument-functions` 对我不起作用?
声称的输出格式为:
\-- 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
除了特定的硬件跟踪支持之外,这可能是最有效的方法,但缺点是您必须重新编译代码。