[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


gcov: テスト・カバレッジ・プログラム

gcovは、 GNU CCと組み合わせることによって、 プログラムのコード・カバレッジをテストすることができるツールです。 この章では、 gcovのバージョン1.5について説明します。

gcovの紹介

gcovは、 テスト・カバレッジ・プログラムです。 GNU CCとともに使用してプログラムの分析を行えば、 より効率的、 かつ、 より高速に動作するコードを生成することができます。 gcovは、 コードのどの部分に最適化を適用するのが最も効果的であるかを発見するのを支援してくれる、 プロファイリング・ツールとして使用することができます。 また、 gcovを、 別のプロファイリング・ツールであるgprofとともに使用して、 コードのどの部分が最も多く計算時間を消費しているかを評価することもできます。 プロファイリング・ツールは、 コードのパフォーマンス分析を支援します。 gcovgprofのようなプロファイラを使用することで、 以下のような基本的なパフォーマンスの統計値を知ることができます。

コードのコンパイル時にこうしたことを知っていれば、 最適化すべきモジュールを判断するために、 各モジュールを調べることができます。 gcovは、 最適化を行うべき部分の決定を支援してくれます。 ソフトウェアの開発者はまた、 テスト・スィート(testsuite)と組み合わせてカバレッジ・テストを利用することによって、 ソフトウェアが本当にリリースできる状態にあるかどうかを確認します。 テスト・スィートによって、 プログラムが期待通りの動作をするか検証することができます。 一方、 カバレッジ・プログラムでは、 プログラムのどれだけの部分がテスト・スィートによる動作確認を受けたかをテストします。 こうして開発者は、 より良いテストを行い、 かつ、 より良い最終製品に仕上げるためには、 どのようなテスト・ケースをテスト・スィートに追加すべきかを決定することができるのです。 gcovを使用することを考えているならば、 最適化をせずにコードをコンパイルしなければなりません。 最適化を行うと、 数行のコードが1つの関数にまとめられることがあり、 計算時間を大きく使用するコードが存在する「ホット・スポット」を探すために必要な情報が 提供されなくなることが考えられるからです。 同様に、 gcovは統計値を(最小分割単位である)行ごとに蓄積するため、 1行に1文のみを記述するプログラム・スタイルを使った場合に、 最も良く機能します。 ループやその他の制御構造に展開されるような複雑なマクロを使用すると、 統計値の有用性は減少します。 統計値は、 マクロの呼び出される行についてのみレポートされることになります。 複雑なマクロが関数のように動作するのであれば、 インライン関数に置き替えることで、 この問題を解決することができます。 gcovは、 `sourcefile.gcov'と呼ばれるログファイルを生成します。 このログファイルにおいて、 ソース・ファイル`sourcefile.c'の各行が何回実行されたかが示されます。 このログファイルは、 gprofと組み合わせて使用することによって、 プログラムのパフォーマンスの微調整に役立てることができます。 gprofは、 gcovから得られる情報と合わせて使用することができる、 タイミング情報を提供してくれます。 gcovは、 GNU CCによってコンパイルされたコードに対してのみ動作します。 gcovは、 他のどのようなプロファイリング・メカニズム、 テスト・カバレッジ・メカニズムとも互換性がありません。

gcovの起動

gcov [-b] [-v] [-n] [-l] [-f] [-o directory] sourcefile

-b
分岐の頻度に関する情報を出力ファイルの中に、 また、 分岐に関する要約情報を標準出力にそれぞれ出力します。 このオプションによって、 プログラム中の各分岐部分がどの程度の頻度で使用されたかを知ることができます。
-v
gcovのバージョン番号を (標準エラー・ストリームに)表示します。
-n
gcov出力ファイルを生成しません。
-l
インクルードされたソース・ファイルに対応する長いファイル名を作成します。 例えば、 ヘッダ・ファイル`x.h'がコードを含んでおり、 ファイル`a.c'の中でインクルードされている場合、 ファイル`a.c'に対して実行されたgcovは、 `x.h.gcov'という名前ではなく `a.c.x.h.gcov'という名前の出力ファイルを生成します。 `x.h'が複数のソース・ファイルの中でインクルードされている場合に、 役に立ちます。
-f
ファイル・レベルの要約情報に加えて、 各関数ごとの要約情報を出力します。
-o
オブジェクト・ファイルが存在するディレクトリです。 gcovは、 このディレクトリの中で.bb.bbg、 および、 .daの各ファイルを探索します。

gcovを使用する場合、 まず最初に、 GNU CCが持っている2つの特別なオプション `-fprofile-arcs -ftest-coverage'を指定して プログラムをコンパイルしなければなりません。 このオプションが指定されると、 コンパイラは、 gcovが必要とする追加情報(基本的にはプログラムのフロー・グラフ)を生成します。 また、 gcovが必要とする特別のプロファイリング情報を生成するための追加コードを オブジェクト・ファイルの中に組み込みます。 こうした追加ファイルは、 ソース・コードが存在するディレクトリの中に生成されます。 プログラムを実行すると、 プロファイル出力が生成されます。 `-fprofile-arcs'を指定してコンパイルされた各ソース・ファイルについては、 付随する.daファイルがソース・ディレクトリの中に生成されます。 プログラムのソース・ファイル名を引数としてgcovを実行すると、 各行の実行頻度が付加されたコードのリストが出力されます。 例えば、 プログラム名が`tmp.c'である場合、 gcovの基本機能を使用すると、 以下のような結果が得られます。

$ gcc -fprofile-arcs -ftest-coverage tmp.c
$ a.out
$ gcov tmp.c
 87.50% of 8 source lines executed in file tmp.c
Creating tmp.c.gcov.

ファイル`tmp.c.gcov'が、 gcovからの出力を保持しています。 以下に例を示します。

                main()
                {
           1      int i, total;
                
           1      total = 0;
                
          11      for (i = 0; i < 10; i++)
          10        total += i;
                
           1      if (total != 45)
      ######        printf ("Failure\n");
                  else
           1        printf ("Success\n");
           1    }

`-b'オプションを使用すると、 出力結果は以下のようになります。

$ gcov -b tmp.c
 87.50% of 8 source lines executed in file tmp.c
 80.00% of 5 branches executed in file tmp.c
 80.00% of 5 branches taken at least once in file tmp.c
 50.00% of 2 calls executed in file tmp.c
Creating tmp.c.gcov.

以下に、 結果として生成されるファイル`tmp.c.gcov'の例を示します。

                main()
                {
           1      int i, total;
                
           1      total = 0;
                
          11      for (i = 0; i < 10; i++)
branch 0 taken = 91%
branch 1 taken = 100%
branch 2 taken = 100%
          10        total += i;
                
           1      if (total != 45)
branch 0 taken = 100%
      ######        printf ("Failure\n");
call 0 never executed
branch 1 never executed
                  else
           1        printf ("Success\n");
call 0 returns = 100%
           1    }

各基本ブロックについて、 基本ブロックの最終行の後に、 その基本ブロックを終了させる分岐(branch)または呼び出し(call)を表わす行が出力されます。 複数の基本ブロックが該当行で終了する場合は、 単一のソース行に対して複数の分岐や呼び出しがリストされることもあります。 この場合、 分岐や呼び出しにはそれぞれの番号が与えられます。 こうした分岐や呼び出しを、 ソースの構成要素にマップする簡単な方法はありません。 しかし一般的には、 最も小さい番号の付いた分岐や呼び出しが、 ソース行の最も左に位置する構成要素に一致します。 分岐については、 少なくとも1回実行されたことがあれば、 その分岐部分が選択された回数を 分岐自体の実行回数で割ったパーセンテージが出力されます。 1回も実行されたことがなければ、 "never executed"というメッセージが出力されます。 呼び出しについては、 少なくとも1回実行されたことがあれば、 呼び出しから復帰した回数を 呼び出し自体が実行された回数で割ったパーセンテージが出力されます。 通常この数値は100%ですが、 exitlongjmpが実行されるために、 呼び出されると必ず復帰するわけではない場合には、 これよりも少ない数値になることもあります。 実行回数のカウントは累積されていきます。 プログラムが、 .daファイルを削除せずに再実行されると、 ソースの各行が実行された回数のカウントは、 前回までの実行回数に加算されます。 これは、 いくつかの点において役立つ可能性があります。 例えば、 テスト検証スィートの一部として、 プログラムの複数回の実行にわたってデータを累積するのに利用できます。 または、 プログラムを何度も実行する場合に、 さらに正確な長期的情報を提供するために利用できます。 .daファイルの中のデータは、 プログラムが終了する直前に保存されます。 `-fprofile-arcs'を指定してコンパイルされた各ソース・ファイルについては、 プロファイリング・コードが、 まず最初に既存の.daファイルを読み込むことを試みます。 ファイルが実行可能ファイルと一致しない (基本ブロック・カウント数が異なる) 場合、 ファイルの内容は無視されます。 その後、 新しい実行カウントを加算して、 最後にデータをファイルに書き込みます。

gcovとGCCの最適化の併用

コードの最適化に役立てる目的でgcovを使用する場合は、 まず最初に、 GNU CCの2つの特別なオプションである `-fprofile-arcs -ftest-coverage'を指定してプログラムをコンパイルしなければなりません。 これ以外にも、 GNU CCの任意のオプションを指定することができます。 ただし、 プログラムの中のすべての行が実行されたことを証明したい場合には、 最適化を指定してコンパイルを行うべきではありません。 マシンによっては、 オプティマイザ(最適化処理部)が、 単純なコード行を別のコード行と統合してしまう結果、 単純なコード行が除去されてしまう可能性があります。 例えば、 以下のようなコードは、 マシンによっては、 単一の命令としてコンパイルされる可能性があります。

if (a != b)
  c = 1;
else
  c = 0;

この場合、 それぞれの行に対応して個別のコードが存在するわけではないので、 gcovが各行について個別の実行カウントを計算することはできません。 このため、 このプログラムを最適化してコンパイルすると、 gcovの出力結果は以下のようになります。

      100  if (a != b)
      100    c = 1;
      100  else
      100    c = 0;

この出力結果は、 最適化によって統合されたこのコード・ブロックが100回実行されたことを示しています。 ある意味でこの結果は正しいのです。 というのは、 この4行全体を表わす命令がただ1つ存在するだけだからです。 しかし、 この出力結果は、 結果が0であった回数と、 結果が1であった回数を示してはいません。

gcovデータ・ファイルの簡単な説明

gcovは、 プロファイリングを行うのに3つのファイルを使用します。 各ファイルの名前は、 元のソース・ファイルの拡張子を、 それぞれ.bb.bbg.daに変更したものです。 どのファイルも、 ソース・ファイルと同じディレクトリに置かれ、 プラットフォームに依存しない方法で保存されたデータを保持しています。 .bbファイルと.bbgファイルは、 ソース・ファイルが、 GNU CCの`-ftest-coverage'オプションを指定してコンパイルされたときに、 生成されます。 .bbファイルは、 (ヘッダを含む)ソース・ファイルのリスト、 ソース・ファイルの中の関数、 および、 ソース・ファイルの中の各基本ブロックに対応する行番号を保持しています。 .bbファイルのフォーマットは、 ファイルの中の各基本ブロックの行番号に相当する4バイト整数のリストから構成されています。 各リストは行番号0で終わります。 行番号-1は、 その後ろに (4バイト境界に整列させるためにパディングが行われ、 その後に別の-1が続く) ソース・ファイル名が続いていることを指定するために使用されます。 さらに、 行番号-2は、 その後ろに (同様に4バイト境界に整列させるためにパディングが行われ、 その後に別の-2が続く) 関数名が続いていることを指定するために使用されます。 .bbgファイルは、 ソース・ファイルに対応するプログラム・フロー・グラフを再構成するために使用されます。 .bbgファイルには、 各関数のプログラム・フロー・アーク (ある基本ブロックから別の基本ブロックへ移行する可能性のある分岐) のリストが保持されます。 これは、 .bbファイルとの組み合わせによって、 gcovによるプログラム・フローの再構成を可能にします。 .bbgファイルのフォーマットは、 以下のようになります。

        number of basic blocks for function #0 (4-byte number)
        total number of arcs for function #0 (4-byte number)
        count of arcs in basic block #0 (4-byte number)
        destination basic block of arc #0 (4-byte number)
        flag bits (4-byte number)
        destination basic block of arc #1 (4-byte number)
        flag bits (4-byte number)
        ...
        destination basic block of arc #N (4-byte number)
        flag bits (4-byte number)
        count of arcs in basic block #1 (4-byte number)
        destination basic block of arc #0 (4-byte number)
        flag bits (4-byte number)
        ...

(4バイトの数字として格納されている)-1は、 各関数の持つ基本ブロックのリストを区切ることと、 ファイルが正しく読み込まれたことを証明することを目的として使用されています。 .daファイルは、 GNU CCの`-fprofile-arcs'オプションを指定してビルドされた オブジェクト・ファイルを含むプログラムが実行されたときに、 生成されます。 このオプションを指定してコンパイルされた個々のソース・ファイルについて、 個別に.daファイルが作成されます。 .daファイルの名前は、 結果として生成されるオブジェクト・ファイルの中に絶対パス名として格納されます。 このパス名は、 ソース・ファイル名の拡張子を.daに変更したものです。 .daファイルのフォーマットはかなり単純です。 最初の8バイトの数字は、 ファイルの中にあるカウントの数で、 その後に (8バイトの数として格納された) カウントが続きます。 各カウントは、 プログラムの中の各アークが実行された回数に相当します。 カウントは累積値であり、 プログラムが実行されるたびに、 その実行時における新しいカウント値を、 既存の.daファイルに加算することを試みます。 .daファイルの中のアーク数が、 現在のプログラムのアーク数と一致しない場合は、 その.daファイルの内容を無視して、 単に上書きしてしまいます。 この3つのファイルは、 gcov-io.hの中の関数群を使用して、 整数を格納します。 このヘッダの中の関数群は、 データをストリームに格納したり、 ストリームからデータを取り出すための、 機種に依存しないメカニズムを提供しています。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]