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

我尝试生成一个调用图,用它来找出所有可能的执行路径,这些路径会触及到一个特定的函数(这样我就不必手动计算出所有的路径,因为有许多路径可以通向这个函数)。例如:

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: ...

我试过 Codeviz 和 Doxy,不知怎么的,两个结果都只显示了目标函数 D 的调用。在我的例子中,D 是类的成员函数,其对象将包装在智能指针中。客户端总是通过工厂获取智能指针对象,以便调用 D。

有人知道怎么做吗?

76874 次浏览
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();
}

Then

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

Yields some shiny picture (there is an "external node", because main has external linkage and might be called from outside that translation unit too):

Callgraph

You may want to postprocess this with c++filt, so that you can get the unmangled names of the functions and classes involved. Like in the following

#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

Yields this beauty (oh my, the size without optimizations turned on was too big!)

Beauty

That mystical unnamed function, Node0x884c4e0, is a placeholder assumed to be called by any function whose definition is not known.

Statically computing an accurate C++ call graph is hard, because you need a precise langauge parser, correct name lookup, and a good points-to analyzer that honors the language semantics properly. Doxygen doesn't have any of these, I don't know why people claim to like it for C++; it is easy to construct a 10 line C++ example that Doxygen erroneously analyzes).

You might be better off running a timing profiler which collects a call graph dynamically (this describes ours) and simply exercise a lot of cases. Such profilers will show you the actual call graph exercised.

EDIT: I suddenly remembered Understand for C++, which claims to construct call graphs. I don't know what they use for a parser, or whether they do the detailed analysis right; I have very little specific experience with their product. My few encounters suggests it does not do points-to analysis.

I am impressed by Schaub's answer, using Clang; I would expect Clang to have all the elements right.

In order for the clang++ command to find standard header files like mpi.h two additional options should be used -### -fsyntax-only, i.e. the full command should look as:

clang++ -### -fsyntax-only -S -emit-llvm main1.cpp -o - | opt -analyze -dot-callgraph

The "C++ Bsc Analyzer" can display call graphs - by reading the file generated by the bscmake utility.

You can achieve that by using doxygen (with option to use dot for graphs generation).

enter image description here

With Johannes Schaub - litb main.cpp, it generates this:

enter image description here

doxygen/dot are probably easier than clang/opt to install and run. I did not manage to install it myself and that's why I tried to find an alternative solution!

You can use CppDepend, it can generates many kinds of graphs

  • Dependency Graph
  • Call Graph
  • Class Inheritance Graph
  • Coupling Graph
  • Path Graph
  • All Paths Graph
  • Cycle Graph

enter image description here

doxygen + graphviz could solve most problems when we wanna generate call graph,next handed to manpower.

Scitools Understand is a fantastic tool, better than everything I know for reverse engineering, and generates high quality graphs.

But note it is quite expensive and that the trial version has its butterfly call graph limited to only one level of call (IMHO I believe they don't help themselves doing so…)