从 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初始化样板 f0f1f2 按预期相互调用 pointed 也会显示出来,尽管我们使用函数指针调用它。如果我们传递了命令行参数,它可能不会被调用。 not_called 未显示,因为它没有在运行中被调用,因为我们没有传递额外的命令行参数。

valgrind 的酷之处在于它不需要任何特殊的编译选项。

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

valgrind 设法通过轻量级“虚拟机”运行您的代码来做到这一点。

在 Ubuntu 18.04 上测试。

gcc -finstrument-functions + etrace

https://github.com/elcritch/etrace

-finstrument-functionsadds 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 代码获取控制流图的主要内容,如果未能解决你的问题,请参考以下文章

软件测试复习----程序流图控制流图及注意点

软件测试复习----程序流图控制流图及注意点

软件测试复习----程序流图控制流图及注意点

Soot生成代码控制流图

软件测试 homework3 控制流图和主路径覆盖

Soot生成控制流图