12

私はC++で書かれたレガシーアプリケーションを維持しています。それは時々クラッシュし、Valgrindはいくつかのオブジェクトの二重削除を教えてくれます。

あなたが完全に理解していないアプリケーションで二重削除を引き起こしているバグを見つけるための最良の方法は何ですか?そしてそれは書き直すには大きすぎますか?

あなたの最高のヒントとコツを共有してください!

4

6 に答える 6

4

そのような状況で私を助けてくれたいくつかの一般的な提案は次のとおりです。

  1. ロガーを使用している場合は、ログレベルを完全なデバッグに上げます。出力で疑わしいものを探します。アプリがポインターの割り当てをログに記録せず、疑わしいオブジェクト/クラスの削除を行わない場合はcout << "class Foo constructed, ptr= " << this << endl;、コードにいくつかのステートメントを挿入します(および対応するdelete/ destructorの出力)。
  2. --db-attach=yesを指定してvalgrindを実行します。少し退屈な場合でも、これは非常に便利です。Valgrindは、重大なメモリエラーまたはイベントを検出するたびにスタックトレースを表示し、デバッグするかどうかを尋ねます。アプリが大きい場合は、何度も「n」を繰り返し押すことに気付くかもしれませんが、問題のオブジェクトが最初に(そして次に)削除されるコード行を探し続けてください。
  3. コードを精査するだけです。問題のオブジェクトの構築/削除を探します。悲しいことに、サードパーティのライブラリに含まれることがあります:-(。
  4. 更新:最近これを発見しました:どうやらgcc 4.8以降(システムでGCCを使用できる場合)には、メモリエラーを検出するためのいくつかの新しい組み込み機能「アドレスサニタイザー」があります。LLVMコンパイラシステムでも利用できます。
于 2012-04-09T18:00:34.360 に答える
2

これはあなたのために働くかもしれないし、そうでないかもしれません。

昔、私は当時15歳だった100万以上の回線プログラムに取り組んでいました。まったく同じ問題に直面しました-巨大なデータセットで二重削除します。このようなデータがあれば、すぐに使用できる「メモリプロファイラー」は使用できません。

私の側にあったもの:

  1. それは非常に再現性がありました-私たちはマクロ言語を持っていて、毎回まったく同じ方法で同じスクリプトを実行しました
  2. プロジェクトの歴史の中で、誰かが「#definemallocmy_malloc」と「#definefreemy_free」が何らかの用途を持っていると判断したことがあります。これらは、組み込みのmalloc()とfree()を呼び出す以上のことはしませんでしたが、プロジェクトはすでにコンパイルされ、このように機能しました。

今トリック/アイデア:

my_malloc(int size)
{
   static int allocation_num = 0;  // it was single threaded

   void* p = builtin_malloc(size+16);

   *(int*)p = ++allocation_num;
   *((char*)p+sizeof(int)) = 0; // not freed

   return (char*)p+16;  // check for NULL in order here
}

my_free(void* p)
{
    if (*((char*)p+sizeof(int)))
    {
        // this is double free, check allocation_number
        // then rerun app with this in my_alloc
        //    if (alloc_num == XXX) debug_break();
    }

    *((char*)p+sizeof(int)) = 1; // freed

    //built_in_free((char*)p-16);  // do not do this until problem is figured out
}

new / deleteを使用すると難しい場合がありますが、LD_PRELOADを使用すると、アプリを再コンパイルしなくてもmalloc/freeを置き換えることができる場合があります。

于 2012-04-09T18:54:05.137 に答える
2

うん。@OliCharlesworthが言ったこと。ポインタが割り当てられたメモリを指しているかどうかをテストする確実な方法はありません。これは実際にはメモリの場所そのものであるためです。

あなたの質問が意味する最大の問題は、再現性の欠如です。それを念頭に置いて続けると、単純な「削除」構造を に変更することに行き詰まっていますdelete foo;foo = NULL;

それでも、最善のシナリオは、実際にそれを突き止めるまで、「発生が少ないように見える」ことです。

また、Valgrind が二重削除の問題であると示唆している証拠を尋ねたいと思います。そこに残っているより良い手がかりかもしれません。

これは、より単純で本当に厄介な問題の 1 つです。

于 2012-04-09T17:53:17.643 に答える
0

Windows では、アプリが MSVC++ でビルドされていると仮定すると、標準ライブラリのデバッグ バージョンに組み込まれている広範なヒープ デバッグツールを利用できます。

Windows でもApplication Verifierを使用できます。私の記憶が正しければ、保護されたガードページを間に挟んで、各割り当てを個別のページに強制するモードがあります。これはバッファ オーバーランを見つけるのに非常に効果的ですが、ダブルフリーの状況にも役立つと思います。

(任意のプラットフォームで) できるもう 1 つのことは、(おそらくマクロを使用して) 変換されたソースのコピーを作成し、次のすべてのインスタンスが次のようになるようにすることです。

delete foo;

は次のように置き換えられます。

{ delete foo; foo = nullptr; }

(中括弧は多くの場合に役立ちますが、完全ではありません。) これにより、double-free の多くのインスタンスが null ポインター参照に変わり、検出がはるかに簡単になります。すべてをキャッチするわけではありません。古いポインターのコピーを持っているかもしれませんが、それは多くの一般的な削除後の使用シナリオを押しつぶすのに役立ちます。

于 2012-04-10T16:47:29.777 に答える
0

これは便利だと思いました: backtrace() on linux。(-rdynamic を指定してコンパイルする必要があります。) これにより、すべてのメモリ操作 (新規/削除) の周りに try/catch ブロックを配置し、catch ブロックでスタック トレースを出力することにより、その二重の解放がどこから来ているかを知ることができます。

このようにして、valgrind を実行するよりもずっと速く容疑者を絞り込むことができます。

バックトレースを便利な小さなクラスにラップして、次のように言えます。

try {
  ...
} catch (...) {
  StackTrace trace;
  std::cerr << "Double free!!!\n" << trace << std::endl;
  throw;
} 
于 2012-04-09T20:31:45.983 に答える
0

おそらく、削除を新しいバージョンとは異なる方法で処理したバージョンからアップグレードしています。

おそらく、以前のバージョンdeleteでは、呼び出されたときに静的チェックをif (X != NULL){ delete X; X = NULL;}行い、新しいバージョンではdeleteアクションを実行するだけでした。

ポインターの割り当てを調べて確認し、構築から削除までオブジェクト名の参照を追跡する必要がある場合があります。

于 2012-04-09T18:30:38.363 に答える