Application Verifierを使用したメモリアクセスエラーのデバッグ

Habrauser burdakovdは、C ++、ベクター、および他人の記憶への書き込みに関するQ&Aでタスクを設定しました。 タスクは、とりわけ、 Application Verifierツールの使用方法を簡単にデモンストレーションし、誰がメモリを損なうかを見つけることができるという点で優れています。



Application Verifierは非常に強力なツールであり、ヒープの使用方法の診断に加えて、ハンドルの不適切な処理、マルチスレッドの実装エラーの検出、そのような状況でのプログラムの正しい動作を確認するためのリソース不足のエミュレートなど、他の多くのことを実行できます回。



ツール



Application Verifierに加えて、 Microsoft Debugging Tools for Windowsに含まれている無料のデバッガーであるWinDBGが必要です。 以前は、デバッグツールは個別にダウンロードできましたが、現在では何らかの理由でWindows SDKまたはWindows Driver Kitの一部にすぎません。 ただし、 以前のバージョンは個別にダウンロードできます。これは、タスクに最適です。 さて、またはここに最新バージョン(6.12.2.633)を投稿しました。SDK全体をダウンロードしないようにするためです: dbg_x86.msidbg_amd64.msi



また、Visual C ++(任意のバージョン、新しいバージョン、おそらくVS2003、 Expressが可能)またはWindows SDKのC ++コンパイラも必要です。 必要なのは、WinDBGが理解するPDBデバッグ情報が必要なため、MinGWではなくMicrosoftのコンパイラです。



例を置く



上記の問題ソースを取得しますpastieのコピー )。 デバッグ情報(コンパイラーの場合はキー/ Ziまたは/ ZI、リンカーの場合は/ DEBUG)と最適化を無効にして収集します。 コンソールからビルドするコマンドラインは次のようになります。

cl /D_DEBUG /Zi /Od /EHsc /DEBUG /MDd vector_misuse.cpp







アプリケーション検証ツールを構成する

  1. 管理者権限でAppVerifierを起動します。
  2. [ファイル]-> [アプリケーションの追加](またはCtrl + A)を選択し、misused_vector.exeを見つけて[開く]をクリックします。
  3. 基本ノードからすべてのチェックマークを削除します。
  4. [基本]-> [ヒープ]ノードでチェックマークを設定します。 念のため、このノードのプロパティに移動して(それを右クリック->プロパティ)、Full(一番上)と反対のTraces(ダイアログのほぼ中央)の反対側のチェックボックスがオンになっていることを確認してください。 オンになっていない場合は、オンにして[OK]をクリックします。
  5. [保存]ボタンをクリックします。

デバッガーを構成する

  1. [ファイル]-> [シンボルファイルのパス]に移動し、 srv*c:\mysymbols*http://msdl.microsoft.com/download/symbols



    という行を入力します。 これは、デバッガーが最初にc:\ mysymbolsディレクトリ内の文字を検索し、そうでない場合は、Microsoft Symbol Storeからインターネットからダウンロードすることを意味します。 美しい呼び出しスタックを表示するには、パブリックシンボルが必要です。 .symfix+ c:\mysymbols



    を使用できますが、アプリケーションがデバッガーにロードされた後です。
  2. [ファイル]-> [実行可能ファイルを開く](Ctrl + E)でmisused_vector.exeを選択します。 ワークスペースを維持するという提案に同意します。 デバッガはイメージをメモリにロードしますが、実行を開始しません。
  3. 実行時にサンプルを起動します-Debug-> Go(またはF5、またはデバッガプロンプトでg)。
以前にWinDBGを使用したことがない場合は、[表示]-> [フォント]メニューでフォントを設定することをお勧めします。 デフォルトでインストールされているものは、あなたにとって完全に狂ったように見えるかもしれません(または見えないかもしれません)。



秋の理由を見つける



プログラムを実行した後、Access Violationでクラッシュします。



スタック-呼び出しスタックの表示(または招待でAlt + 6またはkp)を見て、ネストの第2レベルで関数fに含まれるものを確認します。 関数の引数を[呼び出しスタック]ウィンドウに表示するには、[ソース引数]ボタンをクリックします。 コード行へのリンクを表示するには、「ソース」ボタンをクリックします。 kpコマンドは、この情報をデバッガのコマンドウィンドウに表示します。 ソーステキストを含むウィンドウも開き、現在の行が強調表示されます。



OK、問題は次の行にあることがわかります
 v[i] += f(x / 2);
      
      



しかし、彼女の何が間違っているのでしょうか? デバッガーは、正しく尋ねるとこの質問に答えます。 プロンプトで書きます!analyze -v



!analyze -v



Enterを押します。



デバッガーは、次のことに興味があるテキストのシートをダンプします。

DEFAULT_BUCKET_ID:INVALID_POINTER_READ-無効なポインターで読み取ろうとしました

READ_ADDRESS:060a0ff4-読み取ろうとしたアドレス自体。



すでに見た呼び出しスタックと、例外が発生したマークされた行を持つソースの一部も印刷されます。



これはもちろん非常に興味深いですが、なぜこの記憶が読めないのか知りたいですか? AppVerifierで行った設定のおかげで、システムは呼び出しスタックを収集し、各メモリの割り当てと割り当て解除でそれらを慎重に保存したため、要求に応じて親切に提供することができました。



デバッガーの招待状を!heap -p -a 060a0ff4



ます!heap -p -a 060a0ff4



(ここでは、READ_ADDRESSにあるアドレスを置き換える必要がありますが、ほとんどの場合異なります。デバッガーは、このアドレスがそのようなヒープに属することを通知します。呼び出しスタックを使用して、次のように(解放された割り当てで)解放されたサイズの:
     5da190b2 verifier!AVrfDebugPageHeapFree + 0x000000c2
     77cd1464 ntdll!RtlDebugFreeHeap + 0x0000002f
     77c8ab3a ntdll!RtlpFreeHeap + 0x0000005d
     77c33472 ntdll!RtlFreeHeap + 0x00000142
     75cc14dd kernel32!HeapFree + 0x00000014
     5c677f59 MSVCR100D!_Free_base + 0x00000029
     5c687a4e MSVCR100D!_Free_dbg_nolock + 0x000004ae
     5c687560 MSVCR100D!_Free_dbg + 0x00000050
     5c686629 MSVCR100D!演算子削除+ 0x000000b9
     00f71af0 vector_misuse!Std ::アロケーター<int> :: deallocate + 0x00000010
     00f7193b vector_misuse!Std :: vector <int、std :: allocator <int >> :: reserve + 0x0000010b
     00f716db vector_misuse!Std :: vector <int、std :: allocator <int >> :: _ Reserve + 0x0000005b
     00f714c4 vector_misuse!Std :: vector <int、std :: allocator <int >> :: push_back + 0x000000c4
     00f712dc vector_misuse!F + 0x0000002c
     00f7130b vector_misuse!F + 0x0000005b
     00f7130b vector_misuse!F + 0x0000005b
     00f7134b vector_misuse!Main + 0x0000000b
     00f7323f vector_misuse!__ tmainCRTStartup + 0x000001bf
     00f7306f vector_misuse!MainCRTStartup + 0x0000000f
     75cc33ca kernel32!BaseThreadInitThunk + 0x0000000e
     77c39ed2 ntdll!__ RtlUserThreadStart + 0x00000070
     77c39ea5 ntdll!_RtlUserThreadStart + 0x0000001b 


したがって、再帰のネストの第3レベルで、次のベクトル:: push_backで、ベクトルはそのサイズ(ベクトル::予約)を変更することを決定し、このベクトル自体の再割り当て(std ::アロケーター::割り当て解除、さらにスタックの下位)につながることを学びました2番目のレベルに戻るときの解放されたメモリへの後続のアクセス。



合計



私はいつも美しい結論と要約を書くことに問題を抱えていたので、問題はありません。 賢い人々、彼ら自身が必要な結論を引き出します:)



ご清聴ありがとうございました。 :)



All Articles