C言語/C++ コードを解析してインタラクティブなコールグラフ表示 by lctags
lctags で解析した C言語/C++ コードの情報を基に、 インタラクティブなコールグラフ表示に対応しました。
従来からコールグラフ表示を対応していましたが、 それは事前に設定したコールグラフの深さ以内の全コールを辿るグラフを静的に生成するもので、 この方法だと次の問題がありました。
- 辿りたい関数コール以外のパスまで展開されてしまう
- すぐに巨大なグラフになってしまう
- 設定された深さの全コールを解析&グラフ作成するため時間がかかる
この問題を解決するため、 インタラクティブに関数コールを展開可能なコールグラフ表示に対応しました。
このコールグラフは次の機能を持ちます。
- 動的に関数コールを展開/格納可能
- 最低限のコール情報しかアクセスしないため反応が早い
- 関数ポインタを利用した関数コールを展開可能
- ブラウザで選択した関数定義を emacs で表示
lctags 全般の紹介は次を参照してください。
- C/C++ ソースコードタグシステム lctags の紹介
サンプル
https://ifritjp.github.io/sample/lctags/func-call-graph.html
ノードをクリックしてください。
このサンプルはコール情報をデータとして持っていますが、 実際にはノード展開毎に lctags にコール情報を問い合わせています。
構成
コールグラフは D3.js を利用してブラウザ上に svg で表示します。 D3.js と svg に対応したブラウザを用意してください。 イマドキのブラウザなら大抵対応しています。
このコールグラフは、ブラウザで表示するため HTTPD を必要とします。 lctags では emacs を HTTPD とします。
emacs との連携機能を省いた python 版の HTTPD を作成しました。記事の末尾を参照してください。
使用方法
ここではコールグラフを表示するための手順を説明します。
emacs の設定
emacs を HTTPD として利用するため、 simple-httpd(https://github.com/skeeto/emacs-http-server) を emacs に組込んでください。
simple-httpd は emacs の package-install に対応しているので、簡単に組込めます。
組込み後、次を実行すれば emacs が HTTPD となります。
(httpd-start)
プロジェクトの登録
コールグラフを表示したいプロジェクトのソースを、emacs で開いてください。
ここで C-c l を実行すると、次のバッファが表示されます。
l: list
d: def-at s: def-search r: ref-at c: call-at C: callee-at
i: inc I: incSrc f: file
g: graph
c: setCookie r: caller e: callee s: symbol i: inc
I: incSrc
G: generate
m: dump-member-at e: convert-enumName-at E: form-enum
i: insert
c: insert-to-call-function
h: highlight
h: highlight-at r: rescan g: grep c: clear
r: refactoring
s: sub-routine r: rename
m: misc
e: expand-macro g: grep-cursor
u: update
u: this file d: this directory A: all
ここで g c を入力してください。 これによって、現在のプロジェクトが HTTPD に登録されます。
ブラウザでアクセス
D3.js と svg に対応したブラウザで、以降の手順に沿って操作してください。
アクセス URL
次の URL にブラウザでアクセスしてください。
http://xxx.xxx.xxx.xxx:8080/lctags
ここで、 xxx.xxx.xxx.xxx は emacs を動かしている PC のアドレスです。 emacs を動かしている PC とブラウザを動かしている PC が同じ場合は、 localhost で OK です。
8080 は HTTPD のポートです。
simple-httpd のデフォルトポートは 8080 ですが、 設定によっては別のポートを利用している可能性があります。
上記 URL にアクセスすると、 入力フォームとプロジェクトのパスを示したリンクが表示されているはずです。 ここでは、パスのリンクをクリックしてください。
上記の手順で複数のプロジェクトを登録している場合は、 このリンクがプロジェクト分表示されます。
ディレクトリ、ファイル、関数選択
プロジェクトを選択すると、 そのプロジェクトのディレクトリリストが表示されます。 コールグラフを確認したいソースを含むディレクトリを選択してください。
ディレクトリを選択すると、ディレクトリに含まれるソースファイルリストが表示されます。 確認したい関数を含むソーフファイルを選択してください。
ソースファイルを選択すると、ソースファイル内に含まれる関数リストが表示されます。 確認したい関数を選択してください。
コールグラフ
関数を選択すると、コールグラフ表示画面になります。
最初は選択した関数のノードが左端に表示されます。 このノードをクリックすることで、その関数からコールしている関数が展開されます。
辿りたい関数のノードをクリックしていくことで、所望のコールグラフを得られます。
このコールグラフは次の機能を持ちます。
-
動的に関数コールを展開/格納可能
- ノードをクリックで展開し、再度クリックで格納します。
- 展開したノードは赤く表示されます。
- 格納したノードは緑で表示されます。
-
関数名の色を状態に応じて変更
- コールグラフ上に同じ関数が存在する場合、その関数名を緑で表示します。
- 同じ関数が存在し、かつ展開済みの場合、その関数名を青で表示します。
- 関数が関数ポインタの場合、その関数名の背景を赤で表示します。
- なお、色の変化はノードを展開した時に反映します。
-
関数のノードを右クリックすると、その関数の定義箇所を emacs で表示
- 右クリックしたノードの関数が外部関数だった場合は、定義箇所不明なため表示しません。
-
ツリーのリンクを右クリックすると、そのリンクの箇所を emacs で表示
- リンクの候補が複数ある場合は、最初に見つかった箇所を表示します。
- たとえば、 callee モードで複数関数呼び出ししている場合、 その中の 1 つを表示します。
-
コールグラフの空白部分をドラッグした時の動きを切り替え可能
-
move
- モードをすることで、コールグラフを移動
-
expandResion
- 選択したノードを展開する
-
closeResion
- 選択したノードを格納する
-
関数ポインタの動的関数コール
関数ポインタを利用した動的関数コールでは、 具体的にどの関数がコールされるかが分かりません。 もちろん、ソースコードを静的解析することである程度は 動的に実行される関数を特定することは可能です。 しかし、それには非常に多くの解析時間を要します。
そこでこのコールグラフでは、 lctags による動的関数コール解析ではなく、 ユーザによる動的関数コール特定機能を提供しています。
具体的には、 ユーザが lctags.conf で次の関数をカスタマイズすることで、 動的関数コールのコール先関数を特定することができます。
function conf:getIndirectFuncList( symbol )
return {}
end
この関数は、動的引数コールしている関数型の typedef 名を引数 symbol に持ちます。
この symbol に応じて、コール先の関数名配列を返すことで、 それを動的関数コールの呼び出し先関数として処理します。
例えば次のようなソースで動的関数コールしている場合、
typedef void (test_indirect_t)(void);
static void test_indirect( void )
{
}
void sub( test_indirect_t * pFunc) {
pFunc();
}
static void foo()
{
sub( test_indirect );
}
foo() のコールグラフは次のようなコールグラフとなります。
foo --> sub --> test_indirect_t
このとき test_indirect_t のノードをクリックすると、 動的関数コールの呼び出し先を特定するために getIndirectFuncList() が呼び出されます。
そして getIndirectFuncList( symbol, mode ) の symbol には、 ::test_indirect_t が与えられます。 test_indirect_t の関数型に対応する関数名は test_indirect なので、 次のようにすることで動的関数コールの呼び出し先を指定できます。 mode 引数は、 "callee", "caller" 等のアクセスモードを示します。
function conf:getIndirectFuncList( symbol, mode )
if symbol == "::test_indirect_t" then
return { "test_indirect" }
end
return {}
end
これにより、次のようにコールグラフが展開されます。
foo --> sub --> test_indirect_t --> test_indirect
この動的関数コール特定機能はテスト段階のため、 関数仕様等を変更する可能性が高いです。
なお、lctags.conf はプロジェクトディレクトリ内で次のコマンドを実行することで、 雛形が作成されます。
lctags copyConf
D3.js のレイアウトについて
今回はコールグラフに D3.js の tree レイアウトを利用しました。
tree レイアウトによって、関数コールの構造が直感的に分かると思います。
当初は force レイアウトを利用しようと思っていたのですが、 プロトタイプを作成してみると複雑な関数コールではリンクが絡み合ってしまい、 使い物になりませんでした。
force レイアウトは見た目が面白いのですが、 関数コールのような複雑な関係を持つデータの可視化には向いていないようでした。
ただ、force レイアウトでは、 ループしている関数コールなどが直感的に分かるという利点もあるため、 複雑なレイアウトでも絡み合わない制御が出来れば、 tree レイアウト以上に良い結果を得られると思います。
force レイアウトのプロトタイプは、lctags に含めてあります。 興味のある方は動かしてみてください。
python 版 HTTPD について
コールグラフ確認サーバを立ててチームでコールグラフ機能を共有する場合、 emacs が HTTPD だと色々と不便です。
そこで、 emacs 連携機能を省いた python 版の HTTPD を作成しました。
$ python httpd.py [-lctags=path] port dbpath
httpd.py は src/lisp/httpd.py に格納しています。 httpd.py は 2.x 系 python を利用します。