使用带有多个 cpp 文件的 clang 的 -dot-callgraph 和一个 sed 命令生成调用图
Generating a call graph with clang's -dot-callgraph with multiple cpp files, and a sed command
我试过Doxygen,但是有点慢,而且生成了很多不相关的单个点文件,所以我正在追求clang方式生成调用图。
这个回答 发布了这个命令:
$ clang++ -S -emit-llvm main1.cpp -o - | opt -analyze -dot-callgraph
$ dot -Tpng -ocallgraph.png callgraph.dot
然后
$ 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=} != id' |
dot -Tpng -ocallgraph.png
我设法获取了 .dot 文件并用 c++filt 对它们进行了整理,但是这些符号是由很多“噪音”组成的,例如:
"{__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::deallocate(std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*, unsigned long)}"
"{void std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >(std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*)}"
"{void __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*)}"
doxygen 如何设法“简化”这些符号?除了STLfilt还有别的吗?
如何正确过滤与我的代码无关的符号,例如容器的分配器、构造函数?这个 sed 和 gawk 命令试图做什么?我试过了,但我真的看不出他们做了什么。
我设法做到了,但这并不简单,而且 clang 并没有真正提供过滤“嘈杂”符号的选项。
重要的是要知道 graphviz 不能神奇地优化图表的布局,因此最好为每个目标文件生成一个图表。
这是我想出的 python 过滤器,可以去除很多噪音。有很多东西不属于 std::,比如 sfml 或 nlohmann(一个重型模板化 json 库,会生成很多符号)。我没有使用正则表达式,因为它并不真正相关。这些过滤器应该根据您的代码、您使用的库以及您最终使用的标准库的哪些部分而有很大差异,因为“它一直都是模板”。
def filtered(s):
return not (
s.startswith("& std::__get_helper")
or s.startswith("__cx")
or s.startswith("__gnu_cxx::")
or s.startswith("bool nlohmann::")
or s.startswith("bool std::")
or s.startswith("decltype")
or s.startswith("int* std::")
or s.startswith("int** std::")
or s.startswith("nlohmann::")
or s.startswith("sf::")
or s.startswith("std::")
or s.startswith("void __gnu_cxx::")
or s.startswith("void format<")
or s.startswith("void nlohmann::")
or s.startswith("void std::")
or 'std::remove_reference' in s
or 'nlohmann::' in s
or '__gnu_cxx::' in s
or 'std::__copy_move' in s
or 'std::__niter' in s
or 'std::__miter' in s
or 'std::__get_helper' in s
or 'std::__uninitialized' in s
or 'sf::operator' in s
or s == 'sf::Vector2<float>::Vector2()'
or s == 'sf::Vector2<float>::Vector2(float, float)'
or s == 'sf::Vector2<float>::Vector2<int>(sf::Vector2<int> const&)'
or s == 'sf::Vector2<int>::Vector2()'
or s == 'sf::Vector2<int>::Vector2(int, int)'
)
其次,我还删除了未调用的符号,以及目标文件中不存在的对节点的调用。具体来说,我只是在生成的 DOT 文件中交叉检查节点和边
# filtering symbols I don't want
nodes_filtered = [(name, label) for (name, label) in nodes if filtered(label)]
# using a set() for further cross checking
nodes_filt_ids = set([name for (name, label) in nodes_filtered])
# we only keep edges with symbols (source and destination) BOTH present in the list of nodes
edge_filtered = [(a,b) for (a,b) in edges if a in nodes_filt_ids and b in nodes_filt_ids]
# we then build a set() of all the nodes from the list of edges
nodes_from_filtered_edges = set(sum(edge_filtered, ()))
# we then REFILTER AGAIN from the list of filtered edges
nodes_refiltered = [(name, label) for (name, label)
in nodes_filtered if name in nodes_from_filtered_edges]
第三,我使用了一个makefile来级联步骤。
object_file.ll: object_file.cpp 2dhelpers.h.gch
$(CC) -S -emit-llvm $< -o $@ $(args) $(inclflags)
object_file.ll.callgraph.dot: object_file.ll
opt $< -std-link-opts -dot-callgraph
object_file.cxxfilt.dot: object_file.ll.callgraph.dot
cat $< | llvm-cxxfilt > $@
object_file.cleaned.dot: object_file.cxxfilt.dot
python3 dot-parse.py $^
object_file.final.svg: object_file.cleaned.dot
dot -Tsvg $^ -o $@
重要的是要注意 llvm-cxxfilt
未损坏的符号比 c++filt
好一点,尽管它在我的情况下并不完美。
opt 写的 DOT 文件解析起来很简单,但你仍然可以使用 pydot
,虽然我发现它有点慢,如果你有很多符号(我有 2500 个符号,5000 个调用,减少到 116 和 151)
如果你真的想“组合”多个目标文件并使用一个图形,你绝对可以,使用 llvm-link
# linking llvm IR with this magic
all_objects.ll: file1.ll file2.ll file3.ll file4.ll file5.ll
llvm-link -S $^ -o all_objects.ll
只需将命令 #2 到 #5 应用于生成的 .ll 文件
在我的例子中,单个大图不容易阅读,因为多条边会交叉并形成“河流”。添加颜色并没有多大帮助。
我试过Doxygen,但是有点慢,而且生成了很多不相关的单个点文件,所以我正在追求clang方式生成调用图。
这个回答 发布了这个命令:
$ clang++ -S -emit-llvm main1.cpp -o - | opt -analyze -dot-callgraph
$ dot -Tpng -ocallgraph.png callgraph.dot
然后
$ 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=} != id' |
dot -Tpng -ocallgraph.png
我设法获取了 .dot 文件并用 c++filt 对它们进行了整理,但是这些符号是由很多“噪音”组成的,例如:
"{__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::deallocate(std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*, unsigned long)}"
"{void std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >(std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*)}"
"{void __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::destroy<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*)}"
doxygen 如何设法“简化”这些符号?除了STLfilt还有别的吗?
如何正确过滤与我的代码无关的符号,例如容器的分配器、构造函数?这个 sed 和 gawk 命令试图做什么?我试过了,但我真的看不出他们做了什么。
我设法做到了,但这并不简单,而且 clang 并没有真正提供过滤“嘈杂”符号的选项。
重要的是要知道 graphviz 不能神奇地优化图表的布局,因此最好为每个目标文件生成一个图表。
这是我想出的 python 过滤器,可以去除很多噪音。有很多东西不属于 std::,比如 sfml 或 nlohmann(一个重型模板化 json 库,会生成很多符号)。我没有使用正则表达式,因为它并不真正相关。这些过滤器应该根据您的代码、您使用的库以及您最终使用的标准库的哪些部分而有很大差异,因为“它一直都是模板”。
def filtered(s):
return not (
s.startswith("& std::__get_helper")
or s.startswith("__cx")
or s.startswith("__gnu_cxx::")
or s.startswith("bool nlohmann::")
or s.startswith("bool std::")
or s.startswith("decltype")
or s.startswith("int* std::")
or s.startswith("int** std::")
or s.startswith("nlohmann::")
or s.startswith("sf::")
or s.startswith("std::")
or s.startswith("void __gnu_cxx::")
or s.startswith("void format<")
or s.startswith("void nlohmann::")
or s.startswith("void std::")
or 'std::remove_reference' in s
or 'nlohmann::' in s
or '__gnu_cxx::' in s
or 'std::__copy_move' in s
or 'std::__niter' in s
or 'std::__miter' in s
or 'std::__get_helper' in s
or 'std::__uninitialized' in s
or 'sf::operator' in s
or s == 'sf::Vector2<float>::Vector2()'
or s == 'sf::Vector2<float>::Vector2(float, float)'
or s == 'sf::Vector2<float>::Vector2<int>(sf::Vector2<int> const&)'
or s == 'sf::Vector2<int>::Vector2()'
or s == 'sf::Vector2<int>::Vector2(int, int)'
)
其次,我还删除了未调用的符号,以及目标文件中不存在的对节点的调用。具体来说,我只是在生成的 DOT 文件中交叉检查节点和边
# filtering symbols I don't want
nodes_filtered = [(name, label) for (name, label) in nodes if filtered(label)]
# using a set() for further cross checking
nodes_filt_ids = set([name for (name, label) in nodes_filtered])
# we only keep edges with symbols (source and destination) BOTH present in the list of nodes
edge_filtered = [(a,b) for (a,b) in edges if a in nodes_filt_ids and b in nodes_filt_ids]
# we then build a set() of all the nodes from the list of edges
nodes_from_filtered_edges = set(sum(edge_filtered, ()))
# we then REFILTER AGAIN from the list of filtered edges
nodes_refiltered = [(name, label) for (name, label)
in nodes_filtered if name in nodes_from_filtered_edges]
第三,我使用了一个makefile来级联步骤。
object_file.ll: object_file.cpp 2dhelpers.h.gch
$(CC) -S -emit-llvm $< -o $@ $(args) $(inclflags)
object_file.ll.callgraph.dot: object_file.ll
opt $< -std-link-opts -dot-callgraph
object_file.cxxfilt.dot: object_file.ll.callgraph.dot
cat $< | llvm-cxxfilt > $@
object_file.cleaned.dot: object_file.cxxfilt.dot
python3 dot-parse.py $^
object_file.final.svg: object_file.cleaned.dot
dot -Tsvg $^ -o $@
重要的是要注意 llvm-cxxfilt
未损坏的符号比 c++filt
好一点,尽管它在我的情况下并不完美。
opt 写的 DOT 文件解析起来很简单,但你仍然可以使用 pydot
,虽然我发现它有点慢,如果你有很多符号(我有 2500 个符号,5000 个调用,减少到 116 和 151)
如果你真的想“组合”多个目标文件并使用一个图形,你绝对可以,使用 llvm-link
# linking llvm IR with this magic
all_objects.ll: file1.ll file2.ll file3.ll file4.ll file5.ll
llvm-link -S $^ -o all_objects.ll
只需将命令 #2 到 #5 应用于生成的 .ll 文件
在我的例子中,单个大图不容易阅读,因为多条边会交叉并形成“河流”。添加颜色并没有多大帮助。