我正在构建用于测试ansi c应用程序的工具。只需加载代码,查看控制流图,运行测试,标记所有被击中的顶点。我正在尝试通过解析代码来自己构建CFG。不幸的是,如果嵌套代码,它会搞砸。 GCC提供从编译代码中获取CFG的能力。我可能会为其输出编写解析器,但我需要行号来设置断点。在使用-fdump-tree-cfg
或-fdump-tree-vcg
输出控制流图时,是否可以获取行号?
答案 0 :(得分:15)
对于C程序的控制流程图,您可以查看C:
的现有Python解析器调用图是控制流图的密切相关的构造。 有几种方法可用于为C代码创建调用图(函数依赖性)。 这可能有助于推进控制流图生成。 在C中创建依赖图的方法:
使用cflow:
--cpp
选项来预处理代码。使用cscope:
ncc(cflow like)
遗憾的是,以下工具要求代码可编译,因为它们依赖于gcc的输出:
egypt
使用gcc
生成RTL
因此,任何错误的源代码都会失败,或者即使您只想关注来自较大项目的单个文件。因此,与基于cflow
的更强大的工具链相比,它并不是非常有用。埃及默认支持从图表中排除库调用,以使其更清晰。此外,可以使用crowfood
创建C / C ++的文件依赖关系图。
答案 1 :(得分:7)
所以我做了一些研究,并不难获得节点的行号。只需将lineno
选项添加到其中一个选项即可获得它。因此,请使用-fdump-tree-cfg-lineno
或-fdump-tree-vcg-lineno
。我花了一些时间来检查这些数字是否可靠。如果 VCG 格式的图表中每个节点的标签包含两个数字。这些是由该节点表示的代码部分的开始和结束的行号。
答案 2 :(得分:1)
动态分析方法
在此答案中,我描述了一些动态分析方法。
动态方法实际上运行程序来确定调用图。
与动态方法相反的是静态方法,它们尝试仅从源代码中确定它而不运行程序。
动态方法的优点:
动态方法的缺点:
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
除了特定的硬件跟踪支持之外,这也是最有效的方法,但缺点是必须重新编译代码。