15

ansic アプリケーションをテストするためのツールを構築しています。コードをロードし、制御フロー グラフを表示し、テストを実行し、ヒットしたすべての頂点をマークするだけです。コードの解析から自分で CFG を構築しようとしています。残念ながら、コードがネストされているとめちゃくちゃになります。GCC は、コンパイルされたコードから CFG を取得する機能を提供します。出力用のパーサーを作成することもできますが、ブレークポイントを設定するには行番号が必要です。またはで制御フロー グラフを出力するときに行番号を取得する方法はあります-fdump-tree-cfg-fdump-tree-vcg?

4

3 に答える 3

20

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

于 2013-07-24T20:34:31.813 に答える
8

そのため、さらに調査を行ったところ、ノードの行番号を取得するのは難しくありません。linenoこれらのオプションの 1 つにオプションを追加するだけで取得できます。したがって、-fdump-tree-cfg-linenoまたはを使用します-fdump-tree-vcg-linenoそれらの数値が信頼できるかどうかを確認するのに時間がかかりました。VCG形式のグラフの場合、各ノードのラベルには2 つの数字が含まれます。これらは、このノードによって表されるコード部分の開始と終了の行番号です。

于 2013-05-09T06:15:52.027 に答える
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、期待どおりf1f2相互に呼び出されます
  • 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

特定のハードウェア トレースのサポート以外ではおそらく最も効率的な方法ですが、コードを再コンパイルする必要があるという欠点があります。

于 2018-09-21T05:45:34.910 に答える