concov のドキュメントを書こうと思ったけれど、何から書くか困ったので、とりあえずその前に gcov の使い方とはまりどころを書いてみます。
基本的な使い方
こういうコードがあるとする。
/* test.c */ #include <stdio.h> int foo(int x, int y) { return x + y; } int bar(int x, int y) { return x - y; } int main(void) { printf("%d\n", foo(2, 3)); printf("%d\n", foo(3, 4)); return 0; }
コンパイルする。-coverage をつけると gcov 用のオブジェクトファイルが生成される *1 。
$ gcc -coverage -o test test.c
すると .gcno という拡張子のファイルが生成される。これには (たぶん) カバレッジのログとソースコードの行番号とかを対応させるための情報が入ってる。
$ ls test test.c test.gcno
実行すると、
$ ./test 5 7
カバレッジのログを記録した .gcda というファイルができる。
$ ls test test.c test.gcda test.gcno
gcov を実行する。
$ gcov test.gcda File 'test.c' Lines executed:75.00% of 8 test.c:creating 'test.c.gcov'
カバレッジが 75.00% なんだなーとわかる。詳しい結果は test.c.gcov に保存されている。
-: 0:Source:test.c -: 0:Graph:test.gcno -: 0:Data:test.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:/* test.c */ -: 2:#include <stdio.h> -: 3: 2: 4:int foo(int x, int y) { 2: 5: return x + y; -: 6:} -: 7: #####: 8:int bar(int x, int y) { #####: 9: return x - y; -: 10:} -: 11: 1: 12:int main(void) { 1: 13: printf("%d\n", foo(2, 3)); 1: 14: printf("%d\n", foo(3, 4)); 1: 15: return 0; -: 16:}
foo が 2 回呼ばれてるとか、bar が呼ばれてないとかわかる。
ソースコードとオブジェクトが別のディレクトリにある場合
gcov がファイルを見つけられなくてエラーになることがしばしばあります。
例えば、ソースは src/ に、オブジェクトは obj/ に置く場合。
/* src/foo.c */ int foo(int x, int y) { return x + y; }
/* src/bar.c */ int bar(int x, int y) { return x - y; }
/* src/main.c */ #include <stdio.h> int foo(int, int); int bar(int, int); int main(void) { printf("%d\n", foo(2, 3)); printf("%d\n", foo(3, 4)); return 0; }
.o を obj/ 以下に出力するようにコンパイルする。-coverage はコンパイル時にもリンク時にもつける。
$ gcc -coverage -c -o obj/foo.o src/foo.c $ gcc -coverage -c -o obj/bar.o src/bar.c $ gcc -coverage -c -o obj/main.o src/main.c $ gcc -coverage -o test obj/foo.o obj/bar.o obj/main.o
.gcno も obj/ 以下にできる。
$ ls obj bar.gcno bar.o foo.gcno foo.o main.gcno main.o
実行すると、
$ ./test 5 7
.gcda が obj/ 以下にできる。
$ ls obj bar.gcda bar.o foo.gcno main.gcda main.o bar.gcno foo.gcda foo.o main.gcno
gcov にかけるとグラフファイルが見えないといわれる。
$ gcov obj/foo.gcda foo.gcno:cannot open graph file
ここで cd obj すると、foo.gcno は見つかるので一応動くけど foo.c が見つからないので foo.c.gcov の表示が変になるなど、泥沼にはまります。
どうすればいいかというと、
とします。
$ gcov obj/foo.gcda -o obj File 'src/foo.c' Lines executed:100.00% of 2 src/foo.c:creating 'foo.c.gcov'
カレントディレクトリに foo.c.gcov が出来ます。
-: 0:Source:src/foo.c -: 0:Graph:obj/foo.gcno -: 0:Data:obj/foo.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:/* src/foo.c */ 2: 2:int foo(int x, int y) { 2: 3: return x + y; -: 4:}
同じ名前のソースコードが別のディレクトリにある場合
例えば src/core-module/foo.c と src/sub-module/foo.c があった場合、どちらもカバレッジの出力が foo.c.gcov になって、知らないうちに上書きする可能性があります。
どうすればいいかというと、
- gcov に -p オプションを渡す
と、src#core-module#foo.c.gcov だの src#sub-module#foo.c.gcov だのといった、ソースのパスをエンコードした怪しいファイル名にしてくれます。
同じソースファイルがいろんなソースファイルに #include されている場合
例えば bar.c と baz.c から #include "foo.h" している場合。
/* foo.h */ static int foo(int x, int y) { return x + y; }
/* bar.c */ #include "foo.h" int bar(int x, int y) { return foo(x, y); }
/* baz.c */ #include "foo.h" int baz(int x, int y) { return foo(x, y); }
これで bar.c のカバレッジを見ようとすると
$ gcov bar.gcda File 'foo.h' Lines executed:100.00% of 2 foo.h:creating 'foo.h.gcov' File 'bar.c' Lines executed:100.00% of 2 bar.c:creating 'bar.c.gcov'
となり、その後で baz.c のカバレッジを見ようとすると
$ gcov baz.gcda File 'foo.h' Lines executed:100.00% of 2 foo.h:creating 'foo.h.gcov' File 'baz.c' Lines executed:100.00% of 2 baz.c:creating 'baz.c.gcov'
となります。foo.h.gcov が書きつぶされてます。これは -p オプションをつけても回避できません。
どうすればいいかというと、
- gcov に -l オプションを渡す
とします。
$ gcov bar.gcda -l File 'foo.h' Lines executed:100.00% of 2 foo.h:creating 'bar.gcda##foo.h.gcov' File 'bar.c' Lines executed:100.00% of 2 bar.c:creating 'bar.gcda##bar.c.gcov' $ gcov baz.gcda -l File 'foo.h' Lines executed:100.00% of 2 foo.h:creating 'baz.gcda##foo.h.gcov' File 'baz.c' Lines executed:100.00% of 2 baz.c:creating 'baz.gcda##baz.c.gcov'
というように、gcda ファイルの名前が最初につくので、上書きすることはなくなります。-l と -p を合わせて使うこともできます。
まとめ
gcov を使うときのポイント。
$ gcc -coverage -o test test.c
$ gcov foo.gcda
$ gcov foo.gcda -o obj
$ gcov foo.gcda -p
$ gcov foo.gcda -l
以上は concov と関係なく使える知識だと思います。ちなみに concov で使う場合は、全部の .gcda を処理して .gcov を生成して、それらを回収して集計してデータベースに突っ込むので、.gcov の上書きが発生するとデータの欠損につながります。よってとりあえず -p -l をつけるといいです。