結果だけでなく過程も見てください

たい焼きさんの日々の奮闘を綴る日記です。

Valgrindの結果の見方、日本語訳、など役に立つことまとめ

勉強がてらメモ。
適宜更新していこう。うん。

Valgrindって?

デバッグやプロファイラなどの複数のツールから構成されるツール群の総称。
メモリリークチェックツールとして有名だと思いますが、
それはValgrindツール群の中のMemcheckというツールによる機能だったのですね。

どんなツールがあるのか?マニュアルを見ると・・・

Memcheck
言わずと入れたメモリエラー検出ツール。特にC/C++に向いている。リークチェックやバッファーオーバーランなどね。
Cachegrind
プログラムのキャッシュグラフ、または分岐予測をしてシミュレートする。プログラムを速くするために使うツール。
Callgrind
関数呼び出しをトレースしたりできる。Cachegrindをかぶる部分もあるが、Cachegrindとは別の情報も収集できる。
Helgrind
マルチスレッドのプログラム向け。スレッドエラーを検出できる。
DRD
こちらもマルチスレッドのエラー検出ができる。Helgrindと似ているがアプローチが少々違うようでHelgrindとは異なる問題を検出できるそうな。
Massif
ヒーププロファイラ。メモリ使用量を抑えたい人向け。
DHAT
ヒーププロファイラ。メモリ確保したブロックのライフタイムなどの問題を検出するものらしい。
SGcheck
実験段階のツール。機能はMemcheckと同じだけど、こちらはスタック領域やグローバル領域の配列などに適用できるらしい!使えそう。
BBV
実験段階のツール。基本ブロックベクター生成ツール(なにそれ)

あと2つあまり使われないツールがあるらしい。

Lackey
ツールのサンプルコード
Nulgrind
ベンチマークやサンプル向けのもの

筆者はMemcheckくらいしか使ってことがないので
いまいち概要を読んでもピンときませんがなんだか使えそうな気がしますね!

このエントリでは主にデフォルトのオプションであるMemcheckを使っていこうと思います。

Valgrindのインストール

筆者のシステムはCentOS 6.6。
Valgrindが入っていない場合は,yumを使ってインストールしましょう。

yum -y install valgrind

インターネットに接続できないけどCentOS 6のDVDがある人は
以下のエントリを参考にしてDVDメディアをyumリポジトリに追加しましょう。
便利ですよ。シェルスクリプトをコピペして実行するだけ!1分で終わるよ!
http://d.hatena.ne.jp/taiyakisun/20141201/1417452753


で、以下を実行するとDVDメディアからvalgrindがインストールされます。

yum --disablerepo=\* --enablerepo=centos-media -y install valgrind

この他gccgcc-c++もインストールしておくと良いです。

モジュールのコンパイル

シンボル名や行数が欲しい場合以下に気を付けてコンパイルしよう。

  • できれば-gオプションを付けてコンパイルしよう
  • 最適化はしないほうがよい。「-O0」を使おう

結果の見方など

実行するときは基本的にコレ。

valgrind --leak-check=full プログラム プログラムの引数

結果の例はこちら。

==25832== Invalid read of size 4
==25832==    at 0x8048724: BandMatrix::ReSize(int, int, int) (bogon.cpp:45)
==25832==    by 0x80487AF: main (bogon.cpp:66)
==25832==  Address 0xBFFFF74C is not stack'd, malloc'd or free'd

25832はプロセスID

Memcheck

Invalid read of size 4 - 読んではいけない範囲を4byte分読んだ

こういうときに起こる。

int *p = (int *)malloc( sizeof(int) * 10 );
printf( "%d\n", p[11] );
Invalid write of size 4 - 書き込んではいけない範囲を4byte分書いた

こういうときに起こる。

int *p = (int *)malloc( sizeof(int) * 10 );
p[10] = 5;
上記のInvalid readとかwriteのあとに出ている結果(アドレス)について
int *p = (int *)malloc( sizeof(int) * 10 );
printf( "%d\n", p[11] );

プロセスIDは7885である。

==7885== Invalid read of size 4
→読んではいけない範囲を4byte分読んだ

==7885==    at 0x400580: leakfunc (in /home/valtest/a.out)
→アドレス0x400580の関数leakfunc()で発生。

==7885==    by 0x4005A5: main (in /home/valtest/a.out)
→さらにそのleakfunc()はアドレス0x4005A5のmain()でコールされている。

==7885== Address 0x51c306c is 4 bytes after a block of size 40 alloc'd
==7885== at 0x4C27A2E: malloc (vg_replace_malloc.c:270)
==7885== by 0x400565: leakfunc (in /home/valtest/a.out)
==7885== by 0x4005A5: main (in /home/valtest/a.out)
→あなたが読んだアドレスは40byte分メモリ確保された位置から4byte分後ろである。
 sizeof(int)*10確保してp[11]でアクセスしたから…わかりますね?
 after、alloc'dがポイント。


afterがあるということはbeforeもあるよ。

printf( "%d\n", p[-2] );

==7942== Address 0x51c3038 is 8 bytes before a block of size 40 alloc'd
説明は省略します。

不正free()
int *p = (int *)malloc( sizeof(int) * 10 );
free( p );
free( p );

pのアドレスは0x51c3040である。

==7891== Invalid free() / delete / delete[] / realloc()
==7891==    at 0x4C27430: free (vg_replace_malloc.c:446)
→解放してはいけないアドレスをfree()もしくはdelete([])、realloc()した。

==7891==    by 0x4005ED: leakfunc (in /home/valtest/a.out)
→そのfree()はアドレス0x4005EDのleakfunc()で発生。

==7891==    by 0x4005FD: main (in /home/valtest/a.out)
→さらにそのleakfunc()はアドレス0x4005FDのmain()でコールされている。

==7891== Address 0x51c3040 is 0 bytes inside a block of size 40 free'd
==7891== at 0x4C27430: free (vg_replace_malloc.c:446)
==7891== by 0x4005E1: leakfunc (in /home/valtest/a.out)
==7891== by 0x4005FD: main (in /home/valtest/a.out)
→不正なfree()に渡されたアドレスは、過去に確保された40byte分に含まれている部分であり、
 すでにfreeされている。そのfreeしたアドレスのメモリ領域の先頭から0byteの位置(つまり先頭)
である。
 解放済の領域をもう一度解放してまっせということだね。

初期化していない変数を参照
こういうときに起こる。
int a;  /* 初期化してない */
printf( "uninitialized value : %d\n", a );

==7977== Use of uninitialised value of size 8
==7977==    at 0x4E72A9B: _itoa_word (in /lib64/libc-2.12.so)
==7977==    by 0x4E75652: vfprintf (in /lib64/libc-2.12.so)
==7977==    by 0x4E7E189: printf (in /lib64/libc-2.12.so)
==7977==    by 0x4005A7: leakfunc (in /home/valtest/a.out)
==7977==    by 0x4005C2: main (in /home/valtest/a.out)
→intなのになんで8なんだ?

==7977== Conditional jump or move depends on uninitialised value(s)
==7977==    at 0x4E72AA5: _itoa_word (in /lib64/libc-2.12.so)
==7977==    by 0x4E75652: vfprintf (in /lib64/libc-2.12.so)
==7977==    by 0x4E7E189: printf (in /lib64/libc-2.12.so)
==7977==    by 0x4005A7: leakfunc (in /home/valtest/a.out)
==7977==    by 0x4005C2: main (in /home/valtest/a.out)
HEAP, LEAKサマリーの見方
==7913== HEAP SUMMARY:
==7913==     in use at exit: 0 bytes in 0 blocks
→プログラム終了時使用されているヒープは0byte、つまりリークなし!

==7913==   total heap usage: 1 allocs, 2 frees, 40 bytes allocated
→確保した回数、解放した回数、確保されたメモリ量。

==7913== All heap blocks were freed -- no leaks are possible
→確保されたメモリはすべて解放されておりリークはない。
==7919== HEAP SUMMARY:
==7919==     in use at exit: 40 bytes in 1 blocks
==7919==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
→プログラム終了時に1ブロック(40byte)分解放してない。

==7919== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==7919==    at 0x4C27A2E: malloc (vg_replace_malloc.c:270)
==7919==    by 0x400565: leakfunc (in /home/valtest/a.out)
==7919==    by 0x4005BE: main (in /home/valtest/a.out)
→「definitely lost」は確実に解放漏れがあるという意味。

==7919== LEAK SUMMARY:
==7919==    definitely lost: 40 bytes in 1 blocks
==7919==    indirectly lost: 0 bytes in 0 blocks
==7919==      possibly lost: 0 bytes in 0 blocks
==7919==    still reachable: 0 bytes in 0 blocks
==7919==         suppressed: 0 bytes in 0 blocks
→リーク状況まとめ!

SGCheck

Memcheckは主にヒープ領域の不正アクセスを検知するために使用します。
SGCheckはスタック領域、グローバル変数、staticな変数なんかの不正アクセスを検知するものです。
すごいですね。どうやってるんでしょう。

なお、このツールはまだ実験段階だとかなんとか。

コンパイル

このツールも、基本的に実行ファイルの再コンパイルは不要ですが、
「-gオプション」を指定してデバッグ情報付でコンパイルしないと正しく情報が出ません。
ご注意を。

サンプルコードとコンパイル
#include <stdio.h>

int main()
{
    int z, y, i, a[10], c, d;

    for ( i=0; i <= 10; ++i )
    {
        a[i] = 42;
        printf( "a[%d] = %d\n", i, a[i] );
    }

    return 0;
}

ビルドは「gcc -g ./上記のソースコードのファイル」で。

不正アクセスは、配列a(サイズ10)の、11番目にアクセスしている箇所です。

不正書き込み
a[i] = 42;
不正読み込み
printf( "a[%d] = %d\n", i, a[i] );
エラーメッセージ
==2582== Invalid write of size 4
==2582==    at 0x400546: main (sgcheck.c:9)
==2582==  Address 0x1fff000588 expected vs actual:
==2582==  Expected: stack array "a" of size 40 in this frame
==2582==  Actual:   unknown
==2582==  Actual:   is 0 after Expected

→配列aはサイズ40だが、4byte分不正領域に書き込んでいる、という意味になります。

==2599== Invalid read of size 4
==2599==    at 0x400553: main (sgcheck.c:10)
==2599==  Address 0x1fff000588 expected vs actual:
==2599==  Expected: stack array "a" of size 40 in this frame
==2599==  Actual:   unknown
==2599==  Actual:   is 0 after Expected

→配列aはサイズ40だが、4byte分不正領域に読み込んでいる、という意味になります。

グローバル変数gの場合もほとんど一緒ですが、微妙にエラーメッセージが変わります。
こんな感じです。

==2599==  Expected: stack array "a" of size 40 in this frame
 ↓
==2700==  Expected: global array "g" of size 40 in object with soname "NONE"

静的(static)変数sの場合はこう!
ELFではグローバル変数も静的変数も同じ.bss領域に入るので、ほとんど同じですね!

==2599==  Expected: stack array "a" of size 40 in this frame
 ↓
==2708==  Expected: global array "s" of size 40 in object with soname "NONE"
注意点

その関数のスタックフレーム外の領域を不正参照した場合は、
SGCheckでは検出できないようです
…。気を付けないといけないですねぇ(以下例)。

以下は上記サンプルソースのループ条件を「i <= 10」→「i <= 11」としたものです。
a[10] = 42の処理でスタックが破壊され、隣の変数iの値が不正に42になっています。
次のループで、a[42]にアクセスしていますが、関数のスタックフレーム内でないので、
SGCheckはエラーを報告していません。

==2745== Invalid write of size 4
==2745==    at 0x400546: main (sgcheck.c:12)
==2745==  Address 0x1fff000588 expected vs actual:
==2745==  Expected: stack array "a" of size 40 in this frame
==2745==  Actual:   unknown
==2745==  Actual:   is 0 after Expected
==2745==
==2745== Invalid read of size 4
==2745==    at 0x400553: main (sgcheck.c:13)
==2745==  Address 0x1fff000588 expected vs actual:
==2745==  Expected: stack array "a" of size 40 in this frame
==2745==  Actual:   unknown
==2745==  Actual:   is 0 after Expected
==2745==
a[10] = 42
a[42] = 0
==2745==
==2745== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

また、明らかに関数内のスタックフレームを破壊しているのに
エラーが検出されないことがあります…以下例。

サイズ10の配列で、インデックス11に値代入。
a[11] = 42;

まだマニュアルをしっかり読んでいないためかもしれませんが、
原因がわかり次第本エントリーに追記します。

プライバシーポリシー お問い合わせ