title | tags | author | slide |
---|---|---|---|
C/C++のコールグラフを作成する |
C++ Python clang Docker |
dameyodamedame |
false |
先日Cのコールグラフをpythonで書かせるコードを見たので、
https://qiita.com/name_ko/items/0bb30309074e10f1791f
そういうのってすでにあるライブラリを利用して作るともう少し汎用に出来るよという話を記事で書いてみました。
使うのはgccと人気を二分するclang
というコンパイラです(llvmというプロジェクトの一部になります)。このコンパイラはライブラリ形態で呼び出せるように実装されており、
https://clang.llvm.org/docs/index.html#using-clang-as-a-library
今回はその中からlibclangというライブラリを選択し、そのPythonバインディングを使用して、C/C++コードの静的解析を行い、そこからコールグラフを作成します。コンパイラ自身を使うので、コメント/マクロ/テンプレートなど厄介な部分も気にする必要がないという特徴があります。
なお、コールグラフの生成には誰かが書いた既存のコードを使います。
https://github.com/Vermeille/clang-callgraph
int add(int a, int b) {
return a+ b;
}
int main() {
return add(1, 2);
}
まずはこのコードのコールグラフを作成することを目標とします。このコードだとclang本体をインストールする必要がなく、pythonの実行環境さえあれば、簡単に解析可能です。
$ python3 -m venv env
$ . env/bin/activate
(env) $ pip install -U pip setuptools
...
(env) $ pip install libclang pyyaml
...
(env) $ git clone https://github.com/Vermeille/clang-callgraph.git
...
(env) $ cat hoge.cpp
int add(int a, int b) {
return a+ b;
}
int main() {
return add(1, 2);
}
(env) $ python clang-callgraph/clang-callgraph.py hoge.cpp --lookup 'main()'
reading source files...
hoge.cpp
main()
add(int, int)
(env) $ deactivate
$
サクッと出来ました。
なお、clang-callgraphのreadmeとはpipでインストールしているパッケージが違います(clang→libclang)。これはclangパッケージがclang-14のインストールを前提としていて、不便だったからです。libclangはバイナリを付けているので、簡単なものならclang本体(pythonパッケージではなく、コンパイラ本体)のインストールを必要としていません。
実は結構簡単なコードでも解析できません。
#include <iostream>
void hello() {
std::cout << "hello!" << std::endl;
}
int main() {
hello();
return 0;
}
先ほど作ったvenv環境でそのまま上記を解析してみると…
$ . env/bin/activate
(env) $ cat hoge2.cpp
#include <iostream>
void hello() {
std::cout << "hello!" << std::endl;
}
int main() {
hello();
return 0;
}
(env) $ python clang-callgraph/clang-callgraph.py hoge2.cpp --lookup 'main()'
reading source files...
hoge2.cpp
('diags',
[{'fixits': [],
'location': <SourceLocation file '/usr/include/wchar.h', line 35, column 10>,
'ranges': [<SourceRange start <SourceLocation file '/usr/include/wchar.h', line 35, column 10>, end <SourceLocation file '/usr/include/wchar.h', line 35, column 20>>],
'severity': 4,
'spelling': "'stddef.h' file not found"}])
matching:
(env) $ deactivate
$
エラーになってしまいます。見る限り、原因は'stddef.h'が見つからないことのようです。これは推測ですが、libclangをビルドしたときのstddef.hと私の環境のstddef.hの場所が違うのだと思います。これを合わせるのに最も確実な方法は、私の環境のlibclangを使うことです(なければclangごと入れる)。
コンパイラ本体をインストールするのは結構環境を変更することになるので、dockerコンテナ内に環境を作ります。
cat >Dockerfile <<EOF
FROM ubuntu:22.04
RUN \\
--mount=type=cache,target=/var/lib/apt,sharing=locked \\
--mount=type=cache,target=/var/cache/apt,sharing=locked \\
apt-get update && \\
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata \\
software-properties-common && \\
apt-get install -y clang python3-venv git
EOF
docker build --progress=plain -t hoge:22.04 .
元となるイメージにubuntu 22.04を使っているので、インストールされるclangは14になります。
docker run -i --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash -eux <<EOF
python3 -m venv env
. env/bin/activate
pip install -U pip setuptools
pip install pyyaml 'libclang<15'
git clone https://github.com/Vermeille/clang-callgraph.git
deactivate
EOF
ただし、この環境でも普通に動かすとこうなります。
$ docker run -it --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash
groups: cannot find name for group ID 1000
I have no name!@c1dd448ca453:~$ . env/bin/activate
(env) I have no name!@c1dd448ca453:~$ cat hoge2.cpp
#include <iostream>
void hello() {
std::cout << "hello!" << std::endl;
}
int main() {
hello();
return 0;
}
(env) I have no name!@c1dd448ca453:~$ python clang-callgraph/clang-callgraph.py hoge2.cpp --lookup 'main()'
reading source files...
hoge2.cpp
('diags',
[{'fixits': [],
'location': <SourceLocation file '/usr/include/wchar.h', line 35, column 10>,
'ranges': [<SourceRange start <SourceLocation file '/usr/include/wchar.h', line 35, column 10>, end <SourceLocation file '/usr/include/wchar.h', line 35, column 20>>],
'severity': 4,
'spelling': "'stddef.h' file not found"}])
matching:
(env) I have no name!@c1dd448ca453:~$ deactivate
I have no name!@c1dd448ca453:~$ exit
exit
$
同じエラーになってしまうということです。これは、libclangが結局同じものを読んでいるからです。
$ cd clang-callgraph
$ git apply <<EOF
diff --git a/clang-callgraph.py b/clang-callgraph.py
index 878bb9f..202bc39 100755
--- a/clang-callgraph.py
+++ b/clang-callgraph.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
from pprint import pprint
-from clang.cindex import CursorKind, Index, CompilationDatabase
+from clang.cindex import CursorKind, Index, CompilationDatabase, Config
+Config.set_library_file("/lib/x86_64-linux-gnu/libclang-14.so.14.0.0")
from collections import defaultdict
import sys
import json
EOF
$ cd ..
$ docker run -it --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash
groups: cannot find name for group ID 1000
I have no name!@a89450555a4b:~$ . env/bin/activate
(env) I have no name!@a89450555a4b:~$ python clang-callgraph/clang-callgraph.py hoge2.cpp --lookup 'main()'
reading source files...
hoge2.cpp
main()
hello()
std::basic_ostream::operator<<(std::basic_ostream<char>::__ostream_type &(*)(std::basic_ostream<char>::__ostream_type &))
std::operator<<<>(basic_ostream<char, std::char_traits<char>> &, const char *)
(env) I have no name!@a89450555a4b:~$ deactivate
I have no name!@a89450555a4b:~$ exit
exit
$
無事コールグラフを得ることが出来ました。
今回は単一ファイルのコンパイルオプションなしでしかやっていませんが、CMakeなどを使ったビルドでもちゃんとコールグラフを生成できるようなので、興味がある人はそちらも追ってみてください。
押すと表示されます
cat >Dockerfile <<EOF
FROM ubuntu:22.04
RUN \\
--mount=type=cache,target=/var/lib/apt,sharing=locked \\
--mount=type=cache,target=/var/cache/apt,sharing=locked \\
apt-get update && \\
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata \\
software-properties-common && \\
apt-get install -y clang python3-venv git
EOF
docker build --progress=plain -t hoge:22.04 .
docker run -i --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash -eux <<EOF
python3 -m venv env
. env/bin/activate
pip install -U pip setuptools
pip install pyyaml 'libclang<15'
git clone https://github.com/Vermeille/clang-callgraph.git
deactivate
EOF
cat >hoge2.cpp <<EOF
#include <iostream>
void hello() {
std::cout << "hello!" << std::endl;
}
int main() {
hello();
return 0;
}
EOF
docker run -i --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash -eux <<EOF
cd clang-callgraph
git apply <<EOF2
diff --git a/clang-callgraph.py b/clang-callgraph.py
index 878bb9f..202bc39 100755
--- a/clang-callgraph.py
+++ b/clang-callgraph.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
from pprint import pprint
-from clang.cindex import CursorKind, Index, CompilationDatabase
+from clang.cindex import CursorKind, Index, CompilationDatabase, Config
+Config.set_library_file("/lib/x86_64-linux-gnu/libclang-14.so.14.0.0")
from collections import defaultdict
import sys
import json
EOF2
cd ..
EOF
docker run -i --rm -u $(id -u):$(id -g) -v $(pwd):/home/ubuntu -w /home/ubuntu -e HOME=/home/ubuntu -e TZ=Asia/Tokyo hoge:22.04 bash -eux <<EOF
. env/bin/activate
python clang-callgraph/clang-callgraph.py hoge2.cpp --lookup 'main()'
deactivate
EOF
libclangを使ってコールグラフを得ることができた