Valgrindのエラーメッセージだけを出力したい
最近、プライベートではなぜかCを書くことが多くて、その過程で今更ながらValgrindを使うようになった。
で、使っているうちにValgrindのエラーメッセージだけを表示したくなったので、そのやり方をメモ。 環境は以下のとおり。
一応明記しておくと、Valgrindのバージョンは3.10.0。 シェルはbashなので、それ以外のシェルの場合は適宜読み替えるように。
ネタバレ
--log-fd
オプションとリダイレクトを組み合わせればいい。
Valgrindの使い方
例えば以下のCコードsample.c
を考える。
#include <stdio.h> #include <stdlib.h> int main(void) { puts("hello"); malloc(42); // 特に意味もなく malloc して…… fputs("goodby\n", stderr); return 0; // free せずに終了 }
もちろんこのコードはコンパイルして実行することができる。
$ cc -Wall -g sample.c && ./a.out hello # 標準出力 goodby # 標準エラー出力
しかし、sample.c
は6行目で(無意味に)malloc
した領域を解放せずにプログラムを終了している。
Valgrindを介して実行すると、このようなメモリリークを検出できる。
--leak-check=full
オプションを付けると、メモリリーク箇所の詳細な情報もわかる
$ valgrind --leak-check=full ./a.out ==6865== Memcheck, a memory error detector ==6865== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==6865== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info ==6865== Command: ./a.out ==6865== hello goodby ==6865== ==6865== HEAP SUMMARY: ==6865== in use at exit: 42 bytes in 1 blocks ==6865== total heap usage: 1 allocs, 0 frees, 42 bytes allocated ==6865== ==6865== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==6865== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6865== by 0x400614: main (sample.c:6) ==6865== ==6865== LEAK SUMMARY: ==6865== definitely lost: 42 bytes in 1 blocks ==6865== indirectly lost: 0 bytes in 0 blocks ==6865== possibly lost: 0 bytes in 0 blocks ==6865== still reachable: 0 bytes in 0 blocks ==6865== suppressed: 0 bytes in 0 blocks ==6865== ==6865== For counts of detected and suppressed errors, rerun with: -v ==6865== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
definitely lost≒メモリリークしていると思えばいい。
今回はデバッグフラグを付けてコンパイルしているので、malloc
した場所(sample.c:6
)まで出力されている。
-q
オプションをつけるとエラーメッセージのみが出力される。
$ valgrind -q --leak-check=full ./a.out hello goodby ==7135== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==7135== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==7135== by 0x400614: main (sample.c:6) ==7135==
Valgrind自体は他にも色んな用途があるようだが、ここではとりあえずメモリリーク検出ツールとして扱う。
やりたいこと
make
やスクリプトでまとめてチェックするような場合、「プログラム自体の出力を全て抑制して」「Valgrindの出すエラー報告だけを表示」したくなる。
つまり以下のsimple_leak_check
コマンドが欲しい。
$ simple_leak_check ./a.out # hello も goodby も出力しない ==7135== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==7135== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==7135== by 0x400614: main (sample.c:6) ==7135==
「Valgrindのエラー報告だけ」という部分は、先述のValgrindの-q
オプションで解決済みなので、問題は前者である。
試した方法
ダメな方法: 単純なリダイレクト
Valgrindのメッセージは標準エラー出力に出力されるので、標準出力を/dev/null
にリダイレクトすればいい……とはいかない。
これだとsample.c
の7行目のような、プログラム中の標準エラー出力への出力が混じってしまう。
$ valgrind -q --leak-check=full ./a.out >/dev/null goodby # <- ./a.out 中の stderr への出力 ==7613== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==7613== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==7613== by 0x400614: main (sample.c:6) ==7613==
「メモリチェックしたいプログラムが出力するのがおかしい」と言いたくもなるが、この時チェックしていたのは自作のテストフレームワークという、本質的に出力も込みのプログラムだったので、なんとかしたいところ。
一応大丈夫な方法: --log-file
オプションの使用
Valgrindのメッセージをファイルに出す方法がある筈と思って調べたら、--log-file
オプションがあった。
標準出力と標準エラー出力を両方共/dev/null
へリダイレクトし、Valgrindのエラーメッセージは適当なファイルへリダイレクトしてから、後で表示すればよい。
$ valgrind -q --leak-check=full ./a.out \ > --log-file=/tmp/log.txt \ # Valgrind の出力をファイルに一時保存 > >/dev/null 2>/dev/nul # プログラム自体の出力は投げ捨てる $ cat /tmp/log.txt # 終了後に、保存しておいた出力を表示 ==8057== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==8057== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==8057== by 0x400614: main (sample.c:6) ==8057==
一応できた。
でもこの方法の何がイケてないって、/tmp/log.txt
を作ってるのがイケてない。
別にパフォーマンス云々やディスク云々を気にするわけではないが、なんか腑に落ちない。
もうちょっとスマートにできないか。
腑に落ちた方法: --log-fd
オプションの使用
ヘルプを眺めてたら、--log-file
オプションのすぐ近くに --log-fd
オプションがあることに気づく。
以下、抜粋。
$ valgrind --help (中略) --log-fd=<number> log messages to file descriptor [2=stderr]
--log-file
がファイル名を指定するのに対して、--log-fd
はファイルディスクリプタの番号を直接指定する。
最初は「なんだそりゃ」と思ったが、これとリダイレクトを組み合わせれば、中間ファイルを使わずに目的を達成できる。
$ valgrind --log-fd=3 \ # Valgrind の出力をファイルディスクリプタ3番に > -q --leak-check=full ./a.out 3>&2 \ > 3>&2 \ # 3番を標準エラー出力に向ける > >/dev/null 2>/dev/null # プログラム自体の出力は投げ捨てる ==8729== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==8729== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==8729== by 0x400614: main (sample.c:6) ==8729==
今度こそできた!
ここではファイルディスクリプタは3を使っているが、3以上ならなんでもいい(はず)。
1つ落とし穴があり、--log-fd
は実行するプログラム(ここでは./a.out
)よりも前に置かないと、何故か無視される。解せぬ。
オマケ: --error-exitcode
オプション
デフォルトではValgrindの終了ステータスはエラーの有無に関係なく0となっている。
これではスクリプトで処理するときに何かと不便だが、--error-exitcode
オプションで、メモリリークを1つ以上検出した際の終了ステータスを指定できる。
というわけで、目標だったsimple_leak_check
コマンドは次のようになる。
alias simple_leak_check= 'valgrind --log-fd=3 -q --leak-check=full\ 3>&2 >/dev/null 2>/dev/null'
まとめ
Valgrindのエラーメッセージのみを出力する方法を述べた。
正直、--log-file
オプションで中間ファイルを生成する方法でもいい気がするが、一生使う機会が無いと思っていた「3以上のファイルディスクリプタを使ったリダイレクト」が役に立つ形で使えたので、つい記事にまでしてしまった。
というか--log-fd
オプションのこれ以外の用途が思いつかないけど、何かあるのかしら。
ちなみに上のsample.c
は、コンパイル時に-O2
オプションを付けるとmalloc
呼出し自体が最適化によって削除される可能性があるので注意(?)*1