パスワードを忘れた? アカウント作成
537417 journal

okuの日記: 戯れに翻訳した Valgrind FAQ (無保証)

日記 by oku
Valgrind FAQ
リリース 3.1.0 2005年11月25日
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

目次
1. 背景
2. コンパイル、インストール、設定
3. Valgrind が意図せず異常終了する
4. Valgrind が意図した通り動かない
5. Memcheck がバグを見つけない
6. その他
7. もっと助けを得るにはどうすればよいか

------------------------------------------------------------------------
1. 背景
------------------------------------------------------------------------

1.1. あなたは「Valgrind」をどう発音しているのか?

「Val」は「value (ヴァリュー)」のように、「grind」は短い「i」で -- つまり「grinned (グリンド)」(「tinned」のリズムで) の方が「grined (グラインド)」(「find」のリズム) と発音するよりも好きだ。

気を悪くしないで欲しい: 最初は皆ほとんど間違うのだ。

------------------------------------------------------------------------

1.2. 「Valgrind」の名はどこから来たのか?

北欧神話から。 元々 (リリース前)、このプロジェクトは、北欧神話の神の見張り役にちなんで Heimdall という名前だった。 彼は「昼夜を問わず何百マイルを見渡し、草が成長する音を聞き分け、羊の背で毛が延びるのを見る」事 (等々) ができた。 これは素晴らしい名前だったのだが、既にセキュリティパッケージの「Heimdal」に先を越されていた。

北欧のテーマはそのままに「Valgrind」が選ばれた。 「Valgrind」は、アスガルドにある選ばれた死者の殿堂、ヴァルハラの門である。 この門の上には狼がいて、更にその上の猪の頭に巨大な鷲がとまっており、その目は遠く九つの世界を見ている (訳注: この訳は自信なし)。

「value grinder」の短縮ではない (悪くない推測ではあるが)。


------------------------------------------------------------------------
2. コンパイル、インストール、設定
------------------------------------------------------------------------

2.1. Valgrind をビルドしようとしたが、「make」が以下のような assertion failure で失敗する:

    % make: expand.c:489: allocated_variable_append:
            Assertion 'current_variable_set_list->next != 0' failed.


多分「make」のバグである。 バージョン 3.79.1 の全てというわけではないが、いくつかにこのバグがある。 www.mail-archive.com/bug-make@gnu.org/msg01658.html を見よ。 より最近のバージョンの「make」にアップグレードすること。 あるいは、CFLAGS 環境変数を設定しないことでこの問題を回避できるという報告もある。


------------------------------------------------------------------------
3. Valgrind が意図せず異常終了する
------------------------------------------------------------------------

3.1. Valgrind 上でプログラムが正常に動作したが、exit の際に __libc_freeres() を含むエラーの塊を吐いて、segmentation fault で死んでしまう。

プログラムが exit する時に、Valgrind は glibc の __libc_freeres() 関数を呼び出す。これはメモリデバッガへのフックであり、これによって使用している全てのメモリを free し切るよう glibc に要求することができる。Valgrind が glibc のスペースリークを間違って報告しないようにするためには、このようにすることが必要なのだ。

問題は、古いバージョンの glibc で __libc_freeres() がこのようなクラッシュを引き起こすということである。

Valgrind 1.1.X 以降での回避方法: --run-libc-freeres=no フラグを指定する。glibc アロケーションのスペースリークが報告されるかも知れない (これは本当のリークではないので、glibc の人達に報告し『ない』ようにしてください) が、少なくともプログラムは動作する。

------------------------------------------------------------------------

3.2. 私の (バグい) プログラムが以下のように死ぬ:

    % valgrind: vg_malloc2.c:442 (bszW_to_pszW): Assertion 'pszW >= 0' failed.

もし Memcheck (メモリチェッカ) が、何であろうとあなたのプログラムの不正な読み込み、書き込み、free を報告したら、それが起きるかも知れないということだ。理由は、あなたのプログラムが Valgrind の低水準メモリマネージャを壊し、その結果、上記の assertion で死んだか、その類であろう。治療法はあなたのプログラムが不正なメモリアクセスをしないように修正することだ。うまくいけば上記の異常が消えるだろう。

------------------------------------------------------------------------

3.3. 私のプログラムが、以下のようなメッセージを表示して死ぬ:

    % disInstr: unhandled instruction bytes: 0x66 0xF 0x2E 0x5

古いバージョンではいくつかの x86 命令、主に SSE/SSE2 をサポートしていない。新しいバージョンの Valgrind を試して欲しい; 現在ではほとんど全ての命令をサポートしている。新しいバージョンでも発生して、それが SSE/SSE2 命令であれば、gcc に -march フラグを指定せずにあなたのプログラムをコンパイルすることで取り除けるかも知れない。いずれにせよ、我々に報告してもらえれば修正する。

もう一つの可能性は、コードアドレスの外に間違ってジャンプするバグがあなたのプログラムにあるということで、この場合は SIGILL signal を受けとることもある。Memcheck/Addrcheck はこれが発生する前に警告を発するが、アドレス可能なメモリ空間に着地した場合は警告しない。

------------------------------------------------------------------------

3.4. Java のプログラム (あるいは他の JIT コンパイラ) を Valgrind で試したみたが、何かがおかしいようだ。Valgrind はこのようなプログラムを扱えないのか?

Valgrind は動的に生成されるコードを扱うことができるが、それは生成されたコードが別に生成されたコードで後から上書きされない場合に限る。もしこれが起きると、Valgrind は古いコードのつもりで解釈し続けてしまう (これは x86 と AMD64 で真である。PPC32 では Valgrind が検知可能できる明示的なキャッシュのフラッシュ命令がある)。この場合は --smc-check=all を試してみることができる; Valgrind の実行は非常に遅くなるが、古いコードの使用を検出できるだろう。

別解として、もし JIT コンパイラのソースコードを持っているなら、古いコードをマークするのに VALGRIND_DISCARD_TRANSLATIONS クライアント要求の呼び出しを挿入して、--smc-check=all の使用を避けることもできる。

これを別にすれば、理論上 Valgrind はどんな Java のプログラムでも、たとえ JNI を使って部分的に C や C++ で書かれているようなプログラムであっても、きちんと動かすことができる。実際には、Java はほとんどのプログラムがしないような嫌らしい実装をされる傾向があるので、Valgrind は時々これらの重箱の隅に陥ってしまう。

もしあなたの Java プログラムが --smc-check=all をつけても Valgrind で動かないのであれば、バグレポートして欲しい。問題を修正できるかも知れない。


------------------------------------------------------------------------
4. Valgrind が意図した通り動かない
------------------------------------------------------------------------

4.1. 私のプログラムは C++ STL と string クラスを使っている。Valgrind はプログラムが exit した時に「still reachable」なメモリリークがこれらのクラスに含まれていると報告したが、何もなさそうだ。

まず始めに: 気を楽に、これは多分バグではなく仕様だ。C++ 標準ライブラリの多くの実装では自分向けのメモリプールアロケータを用いている。大量のデストラクトされたオブジェクトのメモリは、直ちに free されて OS に戻るわけではなく、後で再利用するためにプールの中に止めおかれる。このプールはプログラムの exit() で free されないという事実が、Valgrind をしてこのメモリが未だ到達可能であると報告させている。exit() でプールを free しないという振舞はライブラリのバグだと言えるかも知れないが。

gcc を使う場合、グローバルにメモリキャッシュを抑止することで、STL に直ちに malloc と free を使うように強制することができる。気をつけること! このようにするとあなたのプログラムは多分、場合によっては劇的に、遅くなる。

* gcc 2.91、2.95、3.0、3.1 では、STL を使う全てのソースを -D__USE_MALLOC でコンパイルする。気をつけること! これは gcc 3.3 から削除された。

* gcc 3.2.2 以降では、プログラムを実行する前に、環境変数 GLIBCPP_FORCE_NEW を export する。

* gcc 3.4 以降では、環境変数の名前が GLIBCXX_FORCE_NEW に変わった。

メモリプールを禁止する方法は他にもある: malloc_alloc テンプレートをあなたのオブジェクトで使う (移植性はないが、gcc では動くだろう) か、あるいは自分自身でメモリアロケータを書いてしまうこともできる。しかし、それはこの FAQ の対象外だ。もし本当にそれを望むのであれば、
<http://gcc.gnu.org/onlinedocs/libstdc++/ext/howto.html#3>
を読むことから始めよ。但し、以下の点に注意すること:

1. gcc は現在も変更中であり、このドキュメントには今のところ完全には反映されていない (「今」== 2003年4月26日)。

2. アロケータは STL のより厄介な部分に属し、プラットフォーム間で移植性を保つのに多大な労力がはらわれて来た。あなたのプラットフォームでその方法が偶然うまくいったとしても他の環境では動かないかも知れない。

------------------------------------------------------------------------

4.2. Memcheck (や他のツール) のスタックトレースは不親切だ。どうすれば読みやすくできるのか?

もし十分長くないというのであれば、--num-callers を使って長くする。

もし十分詳しくないというのであれば、デバッグ情報を付加するために -g でコンパイルする。かつ、シンボルテーブルを strip しない (プログラムは「strip」を動かさない限りは strip されないだろうが、いくつかのライブラリは strip して出荷されている)。

あるいは、もし共有オブジェクトがプログラムの終了前にアンロードされてしまうのであれば、リークレポートが共有オブジェクトを含むようにする。Valgrind はデバッグ情報を捨てているだろうし、エラーメッセージは全て ??? エントリに変わっているのだろう。この回避方法はこれらの共有オブジェクトの dlclose() 呼び出しを避けることである。

あるいは、-fomit-frame-pointer と -fstack-check でスタックトレースが悪くなることもある。

サブトレースの例:

* デバッグ情報あり、strip されていない場合 (最善):

    Invalid write of size 1
       at 0x80483BF: really (malloc1.c:20)
       by 0x8048370: main (malloc1.c:9)

* デバッグ情報なし、strip されていない場合:

    Invalid write of size 1
       at 0x80483BF: really (in /auto/homes/njn25/grind/head5/a.out)
       by 0x8048370: main (in /auto/homes/njn25/grind/head5/a.out)

* デバッグ情報なし、strip されている場合:

    Invalid write of size 1
       at 0x80483BF: (within /auto/homes/njn25/grind/head5/a.out)
       by 0x8048370: (within /auto/homes/njn25/grind/head5/a.out)
       by 0x42015703: __libc_start_main (in /lib/tls/libc-2.3.2.so)
       by 0x80482CC: (within /auto/homes/njn25/grind/head5/a.out)

* デバッグ情報あり、-fomit-frame-pointer:

    Invalid write of size 1
       at 0x80483C4: really (malloc1.c:20)
       by 0x42015703: __libc_start_main (in /lib/tls/libc-2.3.2.so)
       by 0x80482CC: ??? (start.S:81)

* 共有オブジェクトがアンロードされてリークエラーメッセージあり:

    84 bytes in 1 blocks are possibly lost in loss record 488 of 713
       at 0x1B9036DA: operator new(unsigned) (vg_replace_malloc.c:132)
       by 0x1DB63EEB: ???
       by 0x1DB4B800: ???
       by 0x1D65E007: ???
       by 0x8049EE6: main (main.cpp:24)

------------------------------------------------------------------------

4.3. Memcheck (や他のツール) によるスタックトレースに間違った関数名がある。何が起きたのか?

場合によっては Valgrind のスタックトレースに間違った関数名が表示されてしまう。これは glibc が一つの関数に二つの名前をつけていることによるものだ。ほとんどの場合、Valgrind は適切な方の名前を選び出すが、非常に稀に間違った方を使ってしまう。我々が知っている例としては、「memcmp」でなく「bcmp」、「strchr」でなく「index」、「strrchr」でなく「rindex」を表示することがある。


------------------------------------------------------------------------
5. Memcheck がバグを見つけない
------------------------------------------------------------------------

5.1 「valgrind --tool=memcheck my_program」を試したところ、Valgrind のスタートアップメッセージは表示されたが、何のエラーも表示されなかった。このプログラムにはエラーがあることが分かっているのに。

これには二つの可能性がある。

第一に、デフォルトでは、Valgrind が追うのは最上位プロセスだけだ。つまり、あなたのプログラムが子プロセスを作ったとしても、それらはデフォルトでは Valgrind にトレースされることはない。同様に、あなたのプログラムがシェルスクリプトや Perl スクリプト等から起動されるのであれば、Valgrind はシェルなり、Perl インタプリタなり、その類をトレースすることになる。

子プロセスをトレースするには、--trace-children=yes オプションを使うこと。

もしプロセスの巨大ツリーをトレースしているのであれば、出力をネットワーク越しにすることでぐちゃぐちゃなのをマシにすることができる。Valgrind に --log-socket=127.0.0.1:12345 (ローカルホストの 12345 ポートにログを出力したい場合) フラグを与えること。ポートを読むのに valgrind-listener プログラムを使用することができる:

    valgrind-listener 12345

もちろん、最初にリスナープロセスを起動しておく必要がある。詳細はマニュアルを参照すること。

第二に、あなたのプログラムがスタティックリンクされているなら、malloc() のような関数を自前のバージョンに置き換えることができないので、ほとんどの Valgrind ツールはうまく動作しないだろう。鍵となる見分け方は Memcheck が No malloc'd blocks -- あなたのプログラムが malloc() を呼んでいるのにリークの可能性があり得ない -- と言うかどうかだ。回避方法はスタティックリンクを止めることだ。

------------------------------------------------------------------------

5.2. なぜ Memcheck はこのプログラム配列溢れを検知しないのか?

    int static[5];
    
    int main(void)
    {
      int stack[5];
    
      static[5] = 0;
      stack [5] = 0;
              
      return 0;
    }

残念ながら、Memcheck は static や stack の配列境界をチェックしない。我々としてもチェックさせたいのは山々なのだが、Memcheck の動作にうまくフィットするよい方法がないだけなのだ。申し訳ない。


------------------------------------------------------------------------
6. その他
------------------------------------------------------------------------

6.1. suppression を書こうとしているのだがうまく動かない。suppression を書いてくれないか?

いいとも! 自動的に suppression を吐き出すには --gen-suppressions=yes 機能を使うこと。望むなら修正もできる。例えば、ワイルドカード文字「*」を使って同様に自動的に生成された suppression と組み合わせる等だ。

もし本当に suppression を手で書きたいのであれば、注意深くマニュアルを読むこと。特に C++ の関数名を「mangle」しないといけない点に注意。

------------------------------------------------------------------------

6.2. Memcheck/Addrcheck のメモリリークディテクターで、「definitely lost」「possibly lost」「still reachable」「suppressed」はどう違うのか?

詳細はユーザマニュアルの Memcheck セクションにある。

簡単に書くと:

* 「definitely lost」はプログラムがメモリリークしていることを意味する -- 修正せよ!

* 「possibly lost」は、ポインタを妙に使っているのでなければ、プログラムがメモリリークしているかも知れないことを意味する。

* 「still reachable」はプログラムがおそらく問題ない -- free できるメモリを free していないことを意味する。これはとても一般的だし多くの場合妥当である。これらの報告を見たくないのであれば --show-reachable=yes を使わないこと。

* 「suppressed」はリークエラーが抑止されていることを意味する。デフォルトの suppression ファイルにはいくつかの suppression がある。suppressed エラーは無視しても構わない。


------------------------------------------------------------------------
7. もっと助けを得るにはどうすればよいか
------------------------------------------------------------------------

投稿する前にこのセクションを全て読んで欲しい。

もし回答が不完全あるいは不適切だと考えるのであれば、valgrind@valgrind.org へメールして欲しい: <valgrind@valgrind.org>

Valgrind ドキュメントの適切なセクションを読むこと:
<http://www.valgrind.org/docs/manual.html>

配布ドキュメントを読むこと:
<http://www.valgrind.org/docs/dist.html>

<http://search.gmane.org> valgrind-users: <http://news.gmane.org/gmane.comp.debugging.valgrind> グループ名 gmane.comp.debugging.valgrind を使っているメーリングリストアーカイブを検索すること。

これら全てに当たっても解決しないのであれば、valgrind-users メーリングリスト に投稿すべきだ:
<http://lists.sourceforge.net/lists/listinfo/valgrind-users>
この場合は以下を注意深く読んで欲しい。完璧な投稿は、それを読むエキスパートや熟練ユーザに十分な情報と回答の動機を与える可能性を増やすだろう。

適切な場合は valgrind -v の完全な出力を含めた、問題の完全な詳細情報を提示すること。また、使用している Linux ディストリビューション (Red Hat、Debian 等) とバージョン番号も、だ。

valgrind の (suppress していない) 出力の大きな塊を含めない限り、滅多にあなたの投稿が長すぎるということはないので、どうせ間違うなら情報を出し過ぎる方に倒すこと。

件名は明確に、メッセージ本文も推敲すること。

最後に、コミュニティのほとんどは非常に親切でメールされた質問によく答えてくれるという事実にも拘わらず、おそらくは無給のボランティアに助けを求めているのだと言うことを忘れないように。
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

計算機科学者とは、壊れていないものを修理する人々のことである

読み込み中...