Skip to content

Latest commit

 

History

History
303 lines (252 loc) · 9.87 KB

a1a4a33aafcb695b2c15.md

File metadata and controls

303 lines (252 loc) · 9.87 KB
title tags author slide
C/C++のコールグラフを作成する
C++ Python clang Docker
dameyodamedame
false

先日Cのコールグラフをpythonで書かせるコードを見たので、

https://qiita.com/name_ko/items/0bb30309074e10f1791f

そういうのってすでにあるライブラリを利用して作るともう少し汎用に出来るよという話を記事で書いてみました。

使うのはgccと人気を二分するclang

https://clang.llvm.org/

というコンパイラです(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パッケージではなく、コンパイラ本体)のインストールを必要としていません。

clang本体のインストールが必要なコード解析

対象コード

実は結構簡単なコードでも解析できません。

#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ごと入れる)。

clang本体のインストールをして解析

コンパイラ本体をインストールするのは結構環境を変更することになるので、dockerコンテナ内に環境を作ります。

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になります。

コンテナを生成しvenv環境を構築

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が結局同じものを読んでいるからです。

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を使ってコールグラフを得ることができた