如何为C++代码生成调用图

108
我正在尝试生成一个调用图,以找出所有可能的执行路径,这些路径都会触发一个特定的函数(这样我就不必手动找出所有路径,因为有很多路径都会导致这个函数的执行)。例如:
path 1: A -> B -> C -> D
path 2: A -> B -> X -> Y -> D
path 3: A -> G -> M -> N -> O -> P -> S -> D
...
path n: ...

我已经尝试过CodeVizDoxygen。不知何故,两者的结果都只显示了目标函数D的被调用者,而没有其他信息。在我的情况下,D是一个类的成员函数,该类的对象将被包装在智能指针中。客户端将始终通过工厂获取智能指针对象以调用D。
我该如何实现这个功能呢?
9个回答

145
static void D() { }
static void Y() { D(); }
static void X() { Y(); }
static void C() { D(); X(); }
static void B() { C(); }
static void S() { D(); }
static void P() { S(); }
static void O() { P(); }
static void N() { O(); }
static void M() { N(); }
static void G() { M(); }
static void A() { B(); G(); }

int main() {
  A();
}

那么

$ clang++ -S -emit-llvm main1.cpp -o - | opt -analyze -dot-callgraph
$ dot -Tpng -ocallgraph.png callgraph.dot

产生一些漂亮的图片(因为 main 具有外部链接,可能会在该翻译单元之外调用,所以有一个“外部节点”):

Callgraph

您可能希望使用 c++filt 对其进行后处理,以便获取涉及的函数和类的非修饰名称。就像以下示例中所示:

#include <vector>

struct A { 
  A(int);
  void f(); // not defined, prevents inlining it!
};

int main() {
  std::vector<A> v;
  v.push_back(42);
  v[0].f();
}

$ clang++ -S -emit-llvm main1.cpp -o - |
   opt -analyze -std-link-opts -dot-callgraph
$ cat callgraph.dot | 
   c++filt | 
   sed 's,>,\\>,g; s,-\\>,->,g; s,<,\\<,g' | 
   gawk '/external node/{id=$1} $1 != id' | 
   dot -Tpng -ocallgraph.png    

产生这个美丽的东西(哦,我的天啊,没有优化的情况下大小太大了!)

Beauty

那个神秘无名的函数,Node0x884c4e0,是一个占位符,假定它会被调用任何定义不知道的函数。


34
你在多文件项目中使用过这个工具吗?看起来非常酷。 - dirvine
3
由于某种原因,我需要将 -n 选项传递给 c++filt,以便将名称还原。在这里提一下,以防其他人遇到同样的问题。 - Aky
1
当我尝试这样做时,出现了错误:Pass :: print未为pass实现:'将调用图打印到“dot”文件'!怎么回事?clang 3.8 - Arne
2
找到了:由于某种原因,我必须删除 -analyze 选项。另一个问题:我可以将输出文件名设置为除了 ./callgraph.dot 以外的其他名称吗? - Arne
3
我有第二个问题,如何在不同目录中运行多个文件的此命令? - Newbie
显示剩余13条评论

19
你可以通过使用Doxygen(使用dot选项生成图形)来实现这一点。

Enter image description here

使用Johannes Schaub - litb的main.cpp,它生成了以下内容:

Enter image description here

Doxygen/dot可能比Clang/opt更容易安装和运行。我自己没有成功安装它,所以我尝试寻找替代解决方案!

2
你能否添加一个如何运行Doxygen以获取你所包含的窗口的示例? - nimble_ninja
@nimble_ninja:难道Doxywizard配置对话框的截图不够吗? - jpo38
3
我不知道它是来自doxywizard。谢谢! - nimble_ninja
1
对于大型项目来说并不可行,运行了24小时,产生了几个G的HTML文档,但仍未完成...跳过这个。我只需要一些特定函数的调用图(完整的树形结构从/到/在main() <=> SQL_COMMIT()之间)。 - Gizmo

12
静态计算准确的C++调用图很困难,因为你需要一个精确的语言解析器、正确的名称查找和一个良好的指针分析器,以正确地遵守语言语义。Doxygen没有这些功能,我不知道为什么人们喜欢用它来做C++;你可以很容易地构造一个10行的C++示例,Doxygen会错误地分析它。
你可能最好运行一个动态收集调用图的时间分析器(这描述了我们的分析器),并简单地执行许多情况。这样的分析器将显示实际执行的调用图。
编辑:我突然想起Understand for C++,它声称可以构建调用图。我不知道他们使用什么解析器,或者他们是否正确地进行详细分析;我对他们的产品几乎没有具体经验。我的几次接触表明它没有进行指针分析。
我对Schaub的答案印象深刻,他使用Clang;我希望Clang拥有所有正确的元素。

很遗憾,我不知道可能触发该函数的所有用例 :(. 实际上,我的最终目标是找出使用该函数进行调试目的的确切用例列表。我能够使用代码索引工具找到直接调用者,但需要找出所有执行路径以进行进一步分析。 - shiouming
那么你真正想要的是方法被调用的执行条件?那么你需要一个完整、准确的调用图,以及工具在调用图中各个节点上沿着控制流行走的能力,收集条件表达式,直到找到所需的方法。我不知道有哪些现成的工具可以做到这一点(这条评论比问题晚了7年),你可能需要一个定制的分析引擎来完成这个任务。Clang 可能会被用于此;我们的 DMS 工具包也可以用于此。 - Ira Baxter

8
你可以使用CppDepend,它可以生成多种图形
  • 依赖关系图
  • 调用图
  • 类继承图
  • 耦合图
  • 路径图
  • 所有路径图
  • 循环图

enter image description here


4
为了使clang++命令能够找到标准头文件,例如mpi.h,需要使用两个额外的选项-### -fsyntax-only,即完整命令应如下所示:
clang++ -### -fsyntax-only -S -emit-llvm main1.cpp -o - | opt -analyze -dot-callgraph

这个可行吗?使用clang++的-###选项,它不会输出任何LLVM IR之类的东西,只会显示编译过程中将调用的命令。 - Thomson

0

Scitools Understand 是一个非常棒的工具,比我所知道的任何其他逆向工程工具都要好,并且生成高质量的图表。

但请注意,它相当昂贵,试用版的 butterfly call graph 仅限于一级调用(在我看来,我认为他们这样做并没有帮助自己...)


0

doxygen + graphviz 可以解决大多数问题,当我们想要生成调用图并交给人力处理时。


1
你说的“调用图,接下来交给人力”是什么意思?(似乎无法理解) - undefined

0
"C++ Bsc Analyzer" 可以展示调用图,通过读取 bscmake 工具生成的文件。

0
GNU cflow 是一个用于生成文本风格调用图的工具,它支持多个文件。通过运行命令 cflow --tree --number main.c a.c b.c 可以生成调用图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接