ansic アプリケーションをテストするためのツールを構築しています。コードをロードし、制御フロー グラフを表示し、テストを実行し、ヒットしたすべての頂点をマークするだけです。コードの解析から自分で CFG を構築しようとしています。残念ながら、コードがネストされているとめちゃくちゃになります。GCC は、コンパイルされたコードから CFG を取得する機能を提供します。出力用のパーサーを作成することもできますが、ブレークポイントを設定するには行番号が必要です。またはで制御フロー グラフを出力するときに行番号を取得する方法はあります-fdump-tree-cfg
か-fdump-tree-vcg
?
3 に答える
C プログラムの制御フロー グラフについては、C 用の既存の Python パーサーを参照できます。
コール グラフは、フロー グラフを制御するために密接に関連する構造です。C コードのコール グラフ (関数の依存関係) を作成する方法はいくつかあります。これは、制御フロー グラフの生成を進めるための助けになるかもしれません。C で依存関係グラフを作成する方法:
cflow の使用:
cflow + pycflow2dot + dot (GPL、BSD) cflow は、インクルードの欠落など、コンパイルできないコードを処理できるため、堅牢です。プリプロセッサ ディレクティブが頻繁に使用される場合は
--cpp
、コードを前処理するオプションが必要になる場合があります。cflow + cflow2dot + dot (GPL v2、GPL v3、Eclipse Public License (EPL) v1) (cflow2dot が機能する前にパスを修正する必要があることに注意してください)
cflow + cflow2dot.bash (GPL v2、?)
cflow + cflow2vcg (GPL v2、GPL v2)
グラフからシンボルを除外するためのリストを備えた強化された cflow (GPL v2)
cscope の使用:
cscope (BSD)
cscope + callgraphviz +ドット +xdot
cscope +vim CCTree (C 呼び出しツリー エクスプローラー)
cscope + ccglue
C、C++、Python、Java 用のcscope + CodeQuery
cscope + Python html プロデューサー
cscope + calltree.sh
ncc (cflow のような)
KCachegrind (KDE 依存ビューアー)
次のツールは、gcc からの出力に依存しているため、残念ながらコードがコンパイル可能である必要があります。
- CodeViz (GPL v2) (弱点: gcc を使用して cdepn ファイルをダンプするため、コンパイル可能なソースが必要)
- gcc + egypt +dot (GPL v*, Perl = GPL | Artistic license, EPL v1) (
egypt
を使用gcc
して生成するRTL
ため、バグのあるソース コードに対して失敗したり、より大きなプロジェクトから単一のファイルに集中したい場合でも失敗します. したがって、より堅牢な に基づくツールチェーンと比較して、あまり有用ではありませんcflow
. egypt は、デフォルトで、グラフをきれいにするために、グラフからライブラリ呼び出しを除外することを適切にサポートしていることに注意してください.
また、C/C++ のファイル依存グラフは で作成できますcrowfood
。
そのため、さらに調査を行ったところ、ノードの行番号を取得するのは難しくありません。lineno
これらのオプションの 1 つにオプションを追加するだけで取得できます。したがって、-fdump-tree-cfg-lineno
またはを使用します-fdump-tree-vcg-lineno
。それらの数値が信頼できるかどうかを確認するのに時間がかかりました。VCG形式のグラフの場合、各ノードのラベルには2 つの数字が含まれます。これらは、このノードによって表されるコード部分の開始と終了の行番号です。
動的解析方法
この回答では、いくつかの動的分析方法について説明します。
動的メソッドは、実際にプログラムを実行して呼び出しグラフを決定します。
動的メソッドの反対は静的メソッドで、プログラムを実行せずにソースだけから判断しようとします。
動的メソッドの利点:
- 関数ポインターと仮想 C++ 呼び出しをキャッチします。これらは、自明ではないソフトウェアに多数存在します。
動的メソッドの欠点:
- プログラムを実行する必要がありますが、これは遅い可能性があります。または、クロスコンパイルなど、持っていないセットアップが必要です。
- 実際に呼び出された関数のみが表示されます。たとえば、コマンド ライン引数に応じて、一部の関数が呼び出される場合と呼び出されない場合があります。
KcacheGrind
https://kcachegrind.github.io/html/Home.html
テストプログラム:
int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }
int main(int argc, char **argv) {
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
}
使用法:
sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
これで、多くの興味深いパフォーマンス データを含む素晴らしい GUI プログラム内に取り残されました。
右下にある「コールグラフ」タブを選択します。これは、関数をクリックすると、他のウィンドウのパフォーマンス メトリックに相関する対話型の呼び出しグラフを示します。
グラフをエクスポートするには、グラフを右クリックして [グラフのエクスポート] を選択します。エクスポートされた PNG は次のようになります。
そこから、次のことがわかります。
- ルート ノードは
_start
であり、これは実際の ELF エントリ ポイントであり、glibc 初期化ボイラープレートが含まれています。 f0
、期待どおりf1
にf2
相互に呼び出されますpointed
関数ポインターで呼び出したにもかかわらず、 も表示されます。コマンドライン引数を渡した場合、呼び出されなかった可能性があります。not_called
追加のコマンドライン引数を渡さなかったため、実行中に呼び出されなかったため、表示されていません。
素晴らしい点valgrind
は、特別なコンパイル オプションを必要としないことです。
したがって、ソース コードがなくても、実行可能ファイルだけを使用できます。
valgrind
軽量の「仮想マシン」を介してコードを実行することで、なんとかそれを行うことができます。
Ubuntu 18.04 でテスト済み。
gcc -finstrument-functions
+ エトレース
https://github.com/elcritch/etrace
-finstrument-functions
コールバックを追加し、etrace は ELF ファイルを解析し、すべてのコールバックを実装します。
残念ながら、私はそれを機能させることができませんでした:なぜ `-finstrument-functions` が機能しないのですか?
要求された出力の形式は次のとおりです。
\-- main
| \-- Crumble_make_apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
特定のハードウェア トレースのサポート以外ではおそらく最も効率的な方法ですが、コードを再コンパイルする必要があるという欠点があります。