web-dev-qa-db-ja.com

メモリリークディテクタの動作原理

メモリリーク検出器は実際にどのように機能しますか?一般的に基本的な概念は何ですか?これを説明する言語としてC++を使用できます。

51
amit1990

リークディテクタが機能する方法はいくつかあります。 mallocおよびfreeの実装を、割り当て中により多くの情報を追跡でき、パフォーマンスに関係のない実装に置き換えることができます。これは、 dmalloc の動作と似ています。一般に、mallocされているが、freeされていないアドレスはすべてリークされます。

基本的な実装は実際には非常に簡単です。すべての割り当てとその行番号のルックアップテーブルを維持し、解放されたときにエントリを削除するだけです。次に、プログラムが完了すると、リークされたすべてのメモリを一覧表示できます。難しいのは、割り当てをいつどこで解放すべきかを判断することです。同じアドレスへのポインタが複数ある場合、これはさらに困難です。

実際には、おそらく1行番号だけでなく、失われた割り当てのスタックトレースが必要になります。

もう1つのアプローチは、 valgrind がどのように機能するかです。これは、仮想マシン全体を実装して、アドレスとメモリ参照、および関連する簿記を追跡します。 valgrindアプローチははるかに高価ですが、範囲外の読み取りや書き込みなどの他のタイプのメモリエラーについても通知できるため、はるかに効果的です。

Valgrindは基本的に基礎となる命令を計測し、特定のメモリアドレスに参照がなくなったときに追跡できます。これは、アドレスの割り当てを追跡することで実行できるため、メモリの一部が失われたことだけでなく、いつ失われたかを正確に知ることができます。 。

C++は、new演算子とdelete演算子を追加するため、両方のタイプのリーク検出器の処理を少し難しくします。技術的には、newmallocとは完全に異なるメモリソースになる可能性があります。ただし、実際には、多くの実際のC++実装はmallocを使用してnewを実装するか、代替アプローチの代わりにmallocを使用するオプションがあります。

また、C++のような高級言語には、std::vectorstd::listのようなメモリを割り当てる代替の高級言語がある傾向があります。基本的なリークディテクタは、高レベルモードによって行われた潜在的に多くの割り当てを個別に報告します。これは、コンテナ全体が失われたと言うよりもはるかに役に立ちません。

60
b4hand

これが CheckPointerツールの動作に関する公開されたテクニカルペーパーです。

基本的に、すべての値(ヒープとスタック)の存続期間と、言語で定義されているタイプに応じたサイズを追跡します。これにより、CheckPointerは、リークだけでなく、valgrindが実行しないスタック内の配列であっても、配列外のバインドされたアクセスを検出できます。

特に、ソースコードを分析して、すべてのポインターの使用を見つけます。 (これはそれ自体でかなりの作業です)。

各ポインタのポインタメタデータを追跡します。

  • ヒープに割り当てられたオブジェクト、またはポインターとが指すグローバル変数またはローカル変数または関数のオブジェクトメタデータへの参照
  • ポインタが現在アクセスできるオブジェクトの(サブ)オブジェクトのアドレス範囲。これは、オブジェクト全体のアドレス範囲よりも小さい場合があります。例えば構造体メンバーのアドレスを取得すると、インストルメント化されたソースコードは、結果のポインターを使用する場合にのみ、そのメンバーへのアクセスを許可します。

また、各オブジェクトの種類と場所、つまり、関数、グローバル、スレッドローカルまたはローカル変数、ヒープ割り当てメモリ、または文字列リテラル定数のいずれであるかを追跡します。

  • 安全にアクセスできるオブジェクトのアドレス範囲、および
  • ヒープに割り当てられたオブジェクトまたは変数に格納されているポインターごとに、そのポインターのポインターメタデータへの参照。

この追跡はすべて、元のプログラムソースを、元のプログラムが実行することを実行するプログラムに変換し、さまざまなメタデータのチェックまたは更新ルーチンをインターリーブすることによって実現されます。結果のプログラムはコンパイルされて実行されます。実行時にメタデータチェックが失敗した場合、バックトレースには失敗のタイプのレポートが提供されます(無効なポインター、有効な範囲外のポインターなど)。

19
Ira Baxter

これはCおよびC++のタグが付けられており、オペレーティングシステムについては言及されていません。この答えはWindows用です。

C

Windowsには仮想メモリの概念があります。プロセスが取得できるメモリはすべて仮想メモリです。これは VirtualAlloc()[MSDN] を介して行われます。リークディテクタがその関数にブレークポイントを設定し、呼び出されるたびにコールスタックを取得してどこかに保存することを想像できます。次に、 VirtualFree()[MSDN] についても同様のことができます。

次に、違いを識別して、保存されたコールスタックとともに表示できます。

C++

C++の概念は異なります。VirtualAlloc()から取得した64kbの大きなブロックを取得し、ヒープと呼ばれる小さな部分に分割します。 C++ヒープマネージャーはMicrosoftから提供され、新しいメソッド HeapAlloc()[MSDN] および HeapFree()[MSDN] を提供します。

次に、以前と同じことを行うことができますが、実際には、その機能はすでに組み込まれています。 Microsoftの GFlags [MSDN] ツールは追跡を有効にすることができます:

Screenshot: GFlags enabled for Notepad

この場合、C++ヒープマネージャー呼び出し用に最大50MBのコールスタック情報を保存します。

この設定はWindowsレジストリからも有効にできるため、メモリリーク検出器で簡単に利用できます。

一般的な概念

ご覧のとおり、一般的な概念は、割り当てと割り当て解除を追跡し、それらを比較して、違いのコールスタックを表示することです。

8
Thomas Weller