Windows環境でclang(LLVM)でC++ソースコードからいい感じのコールグラフを作成するまでのメモ
普段はMSVCを使っていて、コールグラフを作る場合はdoxygenなどを利用しているが、どうしても精度は低くなってしまう。
clang(LLVM)でも作れるらしいとのことで、試行錯誤してみた。
前提
下記をインスールして、PATHを通しているものとする。
- LLVM 6.0.1 Pre-build
- Graphviz 2.38
基本的なやり方
こちら(https://yutopp.hateblo.jp/entry/2014/05/03/004049) などを参考にすると、以下のようなコマンドでコールグラフを作成することができるようだ。
clang++ -S -emit-llvm main.cpp -o - | opt -analyze -dot-callgraph
dot -Tpng callgraph.dot > callgraph.f.png
clangでコンパイルする
MSVCでコンパイルしているソースコード(プロジェクト)だと、まずclangでコンパイルできるようにする必要がある。
方法としては
- Visual Studio で調べたコマンドラインを、
clang-cl ...
で実行さる - Visual Studio で調べたコマンドラインを、
clang-cl -v ...
で実行させて、clang
のコマンドラインに変換する
あるC++ソースでコールグラフを作成した結果
イマイチなコールグラフを調整する。
作成されたコールグラフはいろいろと問題がある。
- 作成されたコールグラフの関数名はマングリングされている
- 仮想的なノード("external code" / "Node????????")が邪魔
- 組み込み関数("llvm*"/ "__*")が邪魔
デマングルする
デマングリングしないととても見れたものではない。 .dotファイルをUndname.exe
などでデマングリングさせる。
しかし、.dotファイル内のラベルでは<
と>
は特殊文字らしいので、エスケープさせる必要がある。
sed
を使えばいいが、Windowsらしくpowershell
でエスケープ処理をする。
面倒なことに、opt
では既にエスケープされている場合がある(ラムダとか)ので、デマングル前に一旦エスケープを外す。
REM {$_ -replace "\<","<" -replace "\>",">" }
powershell.exe -Command "Get-Content callgraph.dot | %% { $_ -replace \"\\^<\",\"^<\" -replace \"\\^>\",\"^>\" } " > callgraph1.dot
REM { $_ -replace "<","\<" -replace ">","\>" -replace "-\\>","->" }
Undname.exe callgraph1.dot | powershell.exe -Command "$input | %% { $_ -replace \"\\^<\",\"^<\" -replace \"\\^>\",\"^>\" -replace \"^<\",\"\^<\" -replace \"^>\",\"\^>\" -replace \"-\\^>\",\"-^>\" } " > callgraph_demangled.dot
邪魔なノードを消す
DOT言語で特定のノードを消す(非表示にする)には、[style=invis]
を指定すると消すことができるようだが、opt
の引数や後処理などでうまいこと消すことができなかった。
結局、LLVMのソースコードに手を入れた。
--- CallPrinter.cpp.bak 2017-06-06 20:49:48.000000000 +0900
+++ CallPrinter.cpp 2018-09-09 23:18:40.844019700 +0900
@@ -33,6 +33,15 @@
return "external node";
}
+
+ /// isNodeHidden - If the function returns true, the given node is not
+ /// displayed in the graph.
+ static bool isNodeHidden(CallGraphNode *Node){
+ if (Function *Func = Node->getFunction())
+ return Func->isIntrinsic() || Func->getName().startswith("__");
+ else
+ return true;
+ }
};
struct AnalysisCallGraphWrapperPassTraits {
改善した結果
最終的なコマンド(バッチファイル)
@setlocal
@set LLVM=D:\tmp\llvm-6.0.1.build\RelWithDebInfo
@set PATH=%LLVM%\bin;%PATH%
@set DOT=C:\usr\graphviz\bin\dot
@set CXXFILT="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\undname.exe"
@set PS=powershell.exe
@clang -cc1 -S -emit-llvm -D_DEBUG -D_MT -D_DLL -fcxx-exceptions -fexceptions -fms-volatile -v -D WIN32 -D _DEBUG -D _CONSOLE -D LLVM=C:/usr/LLVM -D _UNICODE -D UNICODE -internal-isystem "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Tools\\MSVC\\14.15.26726\\include" -internal-isystem "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.17134.0\\ucrt" -internal-isystem "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\shared" -internal-isystem "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\um" -internal-isystem "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\winrt" -O0 -Wno-error -fms-extensions -fms-compatibility -fms-compatibility-version=19.15.26726 -std=c++2a -fdelayed-template-parsing -fdiagnostics-show-option -fcolor-diagnostics -isystemC:/usr/llvm/include -o - -x c++ main.cpp | opt -dot-callgraph
@REM デマングル前に、エスケープされている"\<"を"<"に置換する
@%PS% -Command "Get-Content callgraph.dot | %% { $_ -replace \"\\^<\",\"^<\" -replace \"\\^>\",\"^>\" } " > callgraph1.dot
@REM { $_ -replace "<","\<" -replace ">","\>" -replace "-\\>","->" }
%CXXFILT% callgraph1.dot | %PS% -Command "$input | %% { $_ -replace \"\\^<\",\"^<\" -replace \"\\^>\",\"^>\" -replace \"^<\",\"\^<\" -replace \"^>\",\"\^>\" -replace \"-\\^>\",\"-^>\" } " > callgraph_demangled.dot
@%DOT% -x -Tsvg -ocallgraph.svg callgraph_demangled.dot
@endlocal
残課題
- デマングルを
LLVM
の中で出来るようにする。(UnDecorateSymbolName
関数を呼びだけでいいと思うけど。) - ラムダ関数がデマングルできていない。
- STL内の関数を省けるようにしたい。
- powershellの使い方が下手。