从ANSI C代码获取控制流图

我正在构建用于测试ansi c应用程序的工具。 只需加载代码,查看控制流图,运行测试,标记所有被击中的顶点。 我正在尝试通过解析代码来自己构建CFG。 不幸的是,如果代码嵌套,它会搞砸。 GCC提供从编译代码中获取CFG的能力。 我可能会为其输出编写解析器,但我需要行号来设置断点。 使用-fdump-tree-cfg-fdump-tree-vcg输出控制流图时,是否可以获取行号?

对于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公共许可证(EPL)v1)(注意cflow2dot在运行之前需要一些路径修复)
    • cflow + cflow2dot.bash (GPL v2 ,?)
    • cflow + cflow2vcg (GPL v2,GPL v2)
    • 增强型cflow (GPL v2),带有列表以从图中排除符号
  • 使用cscope :

    • cscope(BSD)
    • cscope + callgraphviz + dot + xdot
    • cscope + vim CCTree (C Call-Tree Explorer)
    • cscope + ccglue
    • cscope + CodeQuery for C,C ++,Python和Java
    • cscope + Python html制作人
    • 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 ++的文件依赖关系图。

所以我做了一些研究,并不难获得节点的行号。 只需将lineno选项添加到其中一个选项即可获得它。 所以使用-fdump-tree-cfg-lineno-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. file. valgrind --tool=callgrind ./main # Open a GUI tool to visualize callgrind data. kcachegrind callgrind.out.1234 

您现在被置于一个非常棒的GUI程序中,其中包含许多有趣的性能数据。

在右下角,选择“调用图”选项卡。 这会显示一个交互式调用图,当您单击这些函数时,它会与其他窗口中的性能指标相关联。

要导出图形,请右键单击它并选择“导出图形”。 导出的PNG如下所示:

从那我们可以看出:

  • 根节点是_start ,它是实际的ELF入口点,并包含glibc初始化样板
  • f0f1f2按预期彼此调用
  • 尽管我们用函数指针调用它,但也会显示指向。 如果我们通过命令行参数,它可能没有被调用。
  • not_called未显示,因为它没有在运行中被调用,因为我们没有传递额外的命令行参数。

关于valgrind的一个很酷的事情是它不需要任何特殊的编译选项。

因此,即使您没有源代码,也只能使用可执行文件,您可以使用它。

valgrind通过轻量级“虚拟机”运行代码来实现这一目标。

在Ubuntu 18.04上测试过。

gcc -finstrument-functions + etrace

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 

可能是除了特定硬件跟踪支持之外最有效的方法,但有一个缺点,你必须重新编译代码。