.NET開発者向けのWinDbg超能力

会議でのレポートに関する一連の出版物を続けて、最高のレポートDotNext 2016 Moscowを決定しました。このレポートでは、Sasha goldshtn Goldsteinが.NETアプリケーションをデバッグするためのWinDbgの機能について語っています。 この非常に強力なツールを使用すると、組み込みのVisual Studioデバッガーでは処理できない問題を解決できます。



この資料は、解読がすばらしい強力なものに翻訳されているため、英語でレポートを見るのが難しいと感じる人に特に役立ちます!







Windbgは、ハードコアのC ++開発者のみが使用できる非常に複雑なツールと見なされます。 ただし、このレポートでは、どのような場合に.NET開発者に役立つか、WinDbgを使用して.NETアプリケーションをデバッグし、本当に複雑な問題を解決できる場合を示します。



既にWinDbgを使用している人もいますが、このレポートから何か新しいことを学べることを願っています。 そして、まだWinDbgに出会っていない人のために、このツールがあなたに何をすることができるのかをお見せしたいと思います。





私たちのほとんどが毎日使用するVisual Studioデバッガーは、これから紹介する資料の約90%に対応しません。 だからこそ、WinDbgを使用してこのすべてを紹介しています(これは非常に楽しいツールなのでまったく表示されません)。 Visual Studioにはないパワーがあります。



Visual Studioでうまくいかないとき



Visual Studioは強力なデバッガーと呼ばれますか?



彼は悪くない。 しかし、これはマウスやホットキーを使用したい人のためのおもちゃです。 IDEA、DDD、またはWinDbgを備えたJDBのような本当に強力なデバッガと比較すると、Visual Studioは一種のおもちゃです。 Visual Studio 2012のようにマクロがなくなったため、独自のスクリプトでデバッガーを拡張することは非常に困難です。 デバッガーメカニズム自体には実質的に拡張機能はありません。 また、ソースコードがあればVisual Studioも優れています。 しかし、特定のもののソースコードがない場合、何かをするのははるかに困難になります。 ほとんど何もできません。



したがって、私はVisual Studioが好きですが、より強力なツールが必要な場合がいくつかあります。



おそらくあなたの何人かは、LinuxディストリビューションのエディションのRedditこの図元を見たことがあります。





ここで、Visual Studioは最も短いひげです。 実際のオタクは、WinDbgのコンソールバージョンであるWinDbg、cdbなどを使用します。 言い換えれば、学ぶべきハードコアをいつでも見つけることができます。



それでは、最初にWinDbgを少し使いやすくするいくつかのことを見てみましょう。



WinDbgの怖さを軽減



インタラクティブメニュー



WinDbgが非常に怖いのは、膨大な数のコマンドを覚えておく必要があるからです。 ここにはいくつかのメニューとショートカットがありますが、多くの場合、多くのテキストコマンドを手動で入力する必要があります。



ただし、WinDbgには、この素敵なメニューを作成する.cmdtree



コマンドがあります。







マウスを使用してメニュー内を移動し、便利なコマンドを起動できます。 私のリポジトリの1つには、カテゴリにグループ化された便利なコマンドのインタラクティブツリーのサンプルファイルがあります。 このメニューにより、初心者にとってWinDbgが少し簡単になります。



出力内のリンク



コマンドの出力がとても怖いので、多くの人がWinDbgを好きではないと思います。 そして、ほとんどの場合、コピーと貼り付けを使用する必要があります。あるコマンドの結果を取得して、別のコマンドに渡します。



気に入らない人のために、WinDbgバージョンではDMLという名前で長い間デフォルトで有効になっているオプションがあります(DMLはデバッガーマークアップ言語です。後で例を示します)。 このオプションのおかげで、デバッガーの最新バージョンでは、コマンドの出力にリンクがあります。 他の何かを取得するには、リンクをクリックするだけです。



次の例では、 !name2ee



というコマンドを実行しました。このコマンドはクラス名(この例ではC#クラス)を取得し、クラスに関する情報(特に、アセンブリの場所)を提供します。







コマンドの出力には、クリックできるリンクがあります。 たとえば、EEClassフィールドの横のリンクをクリックすると、次のようになります。







リンクは、タイプ情報を表示する別のコマンドを起動します。 スクリーンショットの下部にあるように、フィールド、タイプ、およびこのフィールドに関する追加情報があります。 また、リンクがあります。 それらをクリックすると、追加情報などが得られます。 すべてのチームを覚えていない場合、このツールキットがあれば、チームの結果を調査するのがより便利です。



自動化



WinDbgを怖がらせるもう1つの機能は、単純なものの中には大量のテキスト入力が必要なものがあることです。 しかし、多くの人が知らないもう1つの便利な点があります。WinDbgを起動し、複数のコマンドを一度に実行し、最後に終了することができます(後でいくつか例を示します)。 基本的に、WinDbgには-cスイッチがあり、コマンドラインを受け入れます。



失敗したプロセスのダンプファイルの例を使用して、この機能をライブでデモする方が簡単です。



フォルダC:\ tempに、いくつかのダンプファイルを保存しました。 WinDbgとcdbもあります-前述のように、これはWinDbgのコンソールバージョンです。 WinDbg(cdb)を使用してダンプファイルの1つを開きます。



 cdb.exe -z C:\temp\FileExplorer.exe.14804.dmp -c ".logopen C:\temp\crash.log; !analyze -v; .logclose; q"
      
      





-z



ダンプ用に切り替えます。

-c



ダンプを開いた直後にコマンドを実行できます。



主なことは、ファイルを閉じて最後に終了することを忘れないことです。



".logopen C:\temp\crash.log; !analyze -v; .logclose; q"



-このコマンドラインはダンプファイルを分析し、有用な情報を提供します。



.logopen



は、出力が実行されるログファイルを開きます(ファイル内の行を検索して後で分析できるため、ログファイルの方が使いやすいです)。



ここには、デバッガーの基本的な自動化があります。デバッガーを開始し、ダンプファイルを分析し、これらすべてをログファイルに入れて終了できます。 そして、これらのアクションは繰り返し可能です-バッチモードで実行できます。



その結果、ダンプで何が起こったかについての情報を取得します。 出力の最後には、実行に関する情報、呼び出しスタック、その他の有用な詳細が含まれています。



別の例。 ここでは、出力結果でfindstr



を実行して、クラッシュしたプロセスの名前と、例外と呼ばれる関数を見つけました。







また、オペレーティングシステム、CLRバージョンに関する情報もあります-一般に、多くの有用なものです。



繰り返しますが、例は、メモリリークの検索でよく使用するメソッドです。 メモリリークが発生すると、プロセスがどんどん大きくなっていることがわかります。 そして、定期的に接続してから切断する必要があります。 WinDbgを使用して行う方法を次に示します。







メモリリークが発生する可能性のあるプロセス(参加したい)に対して-pn



-pn



してcdbを実行します(この方が好きなので)。 次に、.NETヒープオブジェクトの統計情報を表示する!dumpheap



を指定します。 -min



は、この場合、10,000バイトを超えるオブジェクトのみが必要である-min



意味します。 最後に、 qd



を使用してqd



終了して切断します。



このコマンドの実行後、WinDbgはプロセスに接続し、10,000バイトを超えるヒープ内の上位オブジェクトを表示し、プロセスから切断します。 言い換えれば、これは、メモリに何が起こるかを見つけるためのほぼ瞬時の方法です。 同じことがバッチモードで実行できます。 Visual Studioで似たようなことをしたい人に幸運を。







スクリプト言語WinDbg



上記の自動化の例が、強力なデバッガーがある場合に自動モードで実行し、スクリプトを作成して繰り返し使用できることを証明することを願っています。 しかし、再利用の利点を実際に活用するには、スクリプト言語の仕組みを理解する必要があります。これは、WinDbgの最も複雑で「ひどい」部分です。



WinDbgにはスクリプト言語が組み込まれていますが、この言語は意図的に開発されたことはありません。 C#とは異なり、その設計は別の委員会によって管理されています。 これは徐々に登場する言語であり、誰かが何かを追加し、他の誰かがそれを修正しました。 私たちが持っていることが起こった。



このスクリプトを見てみましょう(さらに多くの例があり、関連するチュートリアルがありますが、この会話をWinDbgのスクリプトチュートリアルに変えません)。







ここで、最初の行は変数$t0



を値0



で初期化します。 簡単です。 2行目は、特定の関数にブレークポイントを配置します。 NtAllocateVirtualMemory



関数は、メモリを割り当てるWindows APIです。 すべてのメモリ割り当ては、何らかの方法でこのAPIを使用する必要があります。



ブレークポイントに到達するたびに(この関数を呼び出すとき)、引用符内でコマンドを実行します:何らかの恐ろしい式で変数$t0



を増やします(ここで、 rdx



変数には割り当てられたメモリの量が含まれています)。



次に、アプリケーションを実行し続けるためにg



を入力しました。 その後、 .printf



コマンドを使用して変数$t0



現在の値を出力します。これにより、割り当てられた仮想メモリの合計量が.printf



ます。



つまり、ブレークポイントを設定します。 各ブレークポイントは、割り当てられたバイト数だけ変数を増やします。 必要になったら、この変数の値を出力して、割り当てられたメモリ量に関する情報を取得します。



ブレークポイント



どこかにブレークポイントを設定し、あなたのために何かをするという一般的なアプローチは非常に効果的です。 Visual Studioでは、多くの場合、ブレークポイントを使用してデバッガーを単純に停止します。 それは素晴らしい、私もやる。 しかし、ブレークポイントの本当の強みは、それがあなたのために働くことができるということです(あなたのためにあなたのためにではない)。



以下に、この例をいくつか示します。



ファイルを作成するアプリケーションがあるが、これらのファイルがどこから来たのかわからないとします。 アプリケーションは引き続きファイルを作成しますが、それらを削除せずにブロックします。 したがって、アプリケーションの実行中は、これらのファイルを削除できません。 確かに多くの人が同様の状況に直面していました。



これらのファイルがどこから来たのかを知りたいとします。 これを行うにはいくつかの方法があります。 最も簡単なのは、ブレークポイントを追加することです。 ファイルを作成する場所にブレークポイントを配置して、ファイルの出所を確認しましょう。







Windows APIには、ファイルを開くための2つの主要な関数CreateFileW



CreateFileA



ます。



これらの関数のいずれかを呼び出すたびに、開くファイルの名前を出力します。

ここで、 @esp+4



は32ビットプロセス(x86)のスタックポインターであり、 @esp+4



は関数の最初のパラメーターです(これは覚えておく必要はありません。これは、デバッガーで確認できます)。 CreateFileW



を呼び出す場合、ファイル名はUnicode文字列であるため、 %mu



形式を使用しますCreateFileA



場合、ファイル名はANSI文字列であり、 %ma



を使用します。



次に、ファイル名と3つのダッシュを印刷します。 k



コマンドは、コールスタックを表示します。 その結果、アプリケーションがファイルを開くたびに、どのファイルを開くかについてのメッセージがデバッガーに表示されます(呼び出しスタックは、そこに到達した方法を示します)。 上記の例はC ++呼び出しスタックですが、.NETアプリケーションでも同じことができます。 さらにいくつかの例を考えてみましょう。



私のアプリケーションがファイルの欠落について不平を言っているとします-いくつかのファイルを開くことができません。 しかし、その理由と場所を正確に教えてくれません。 「ファイルを開けません」などの愚かなエラーメッセージのみが表示されます。 これは頻繁に起こります。



少し複雑ですが、ブレークポイントを設定するだけです。 エラーが発生したことがわかります(ファイルを開くことができませんでした)。 これを行うには、 CreateFileW



(以下の例では、小さなエラー:オフセット0x61



は存在しないはずです。無視してください)。







そこで、ブレークポイントをCreateFileW



に配置します。 gu



コマンドは、この関数から戻る前に実行されます。 停止後、 @eax



レジスタを確認します。 32ビットWindowsでは、 @eax



は関数の結果を保存します。 64ビットWindowsでは、 @rax



は本質的に同じ負荷を運びます。 戻り値がゼロの場合、ファイルを作成できませんでした。 デバッガーが呼び出しが機能しなかったことを確認した場合、開くことができなかったファイルを印刷します(そして、再び呼び出しが発生した場所で呼び出しスタックを印刷できます)。



繰り返しますが、デバッガは私のために機能しますが、その逆は機能しません。 ファイルを開くことができるすべての場所をコードで見つけようとはしていません。 デバッガーに次のように伝えます。「ファイルのオープンが失敗したときに知らせてください。」



StackOverflowの実際の例を次に示します。







問題のなんと長い説明! この男は何かを理解しようとしています。 そして最後に、彼は次の質問をします。「この定義は疑問を提起します。VirtualAllocを呼び出しているのは誰ですか? ヒープマネージャーまたは.NETランタイムですか?」



このタスクに対処する方法を知っていると思います。







VirtualAlloc



ブレークポイントを設定し、必要なものをすべて見つけます。 すべてがかなり明白です-関数を呼び出している人を知りたいですか? そこにブレークポイントを置き、必要な情報を取得します。もっと簡単なことは何ですか? そのため、 VirtualAlloc



にブレークポイントを設定し、割り当てられているメモリ量を出力します。 マネージコードの呼び出しスタック( !clrstack



)も出力します-これは私が話した例です。



したがって、この例では、次のように表示されます。ガベージコレクターから仮想メモリを割り当てます。 gc_heap



スタック、 grow_heap_segment



virtual_alloc_commit_for_heap



で、コンテンツを読み取るためにXMLDictionaryReader



によって呼び出されXMLDictionaryReader



た。



後続の処理でブレークポイントをどこかに配置する手法は、おそらくこのレポートの一部として提供する最も重要なものです。 これは、Visual Studioにはない非常に強力なツールです。 Visual Studioは、ブレークポイントを挿入するソースコードがある場合に適しています。 ただし、Visual Studioでカスタムアクションや条件などを操作する必要がある場合は、基本的なサポートのみがあります。 WinDbgを使用すると、さらに多くのことができます! また、WinDbgは、ブレークポイントを挿入するソースコードがない場合(Windows API呼び出しまたはいくつかの内部CLR関数に配置する予定がある場合など)に、はるかに優れています。 WinDbgはとてもフレンドリーだとは言えませんが、間違いなく非常に強力です。



別の例に切り替えましょう。 不良オブジェクトが1つあるこのオブジェクトの膨大なコレクションがあるとします。 なぜオブジェクトが悪いのですか? たとえば、エンコードを中断する小さなドットを持つ文字aが含まれています。 私が見つけたいこの「壊れた」オブジェクト。







あまり美しくありませんが、機能します。 レポートの終わりに向かって、この問題を解決する別の方法を示します。 そのため、最初にクラスの場所を見つけます!name2ee OrderService!OrderService.Order







これは私が探しているクラスです- Order



。 いいね 次に、クラスダンプを作成して、必要なフィールドがどこにあるかを見つけます(オブジェクトの先頭を基準にして)。



[ Address



フィールドに興味があります。 ご覧のとおり、オブジェクトの先頭からのオフセットは4です。







あとは、ヒープ内のOrder



オブジェクトを見つけて、オブジェクトの先頭からオフセット4の行を見つけるだけです。 その後、線の内側を見て、「上に丸い点がある」かどうかを確認する必要があります。



行を再度書き直すことはありません-これは不要ですが、いくつかの重要な点に言及します。





最後の例(拡張機能に移る前)では、いくつかのクールなコマンドを示します。



不思議なwt



コマンドがありますが、その意味を知っている人はほとんどいません。 コードの実行をトレースします。 関数を取得し、 wt



実行すると、この関数が行うすべての呼び出しが出力されます。 この場合、トレースの深さを制限できます。



たとえば、 mark_phase



間にガベージコレクターが何をするのか興味がありました。 それでは、 mark_phase



というガベージコレクター関数にブレークポイントを設定して、 wt



を実行してみましょう。







その結果、この美しいツリーが得られます(深さを1に制限しました) mark_phase



によってmark_phase



れるすべての関数を取得します。 ガベージコレクターの動作に興味がある場合は、ここに多くの詳細があります( generation_size



GcScanRoots



scan_background_roots



および他の関数のヒープ)。 完全な出力には複数のページが含まれます。 最後に、すべての機能と実行されたコマンドの数を含むレポートが表示されます。







したがって、 mark_through_cards_for_segment



mark_through_cards_for_large_...



などの命令数の観点から最も「高価な」関数を見ることができmark_through_cards_for_large_...







デバッガ内部のプロファイラのように見えます。 プロファイリングは、プログラムの指示に従って段階的に進むことを強調することが重要です。 非常に遅い、つまり プロファイリング自体は適切ではありません。 しかし、特定の方法で何が起こっているかを理解することは非常に良いことです。



拡張機能



会話のこの部分では、いくつかの便利な拡張機能を紹介します。



Pykd



そして、まずまずのものを使用してデバッガー用のスクリプトを作成できる拡張機能から始めます。







前に見たスクリプトほど悪くはありません。 これはPyKDと呼ばれる非常に素晴らしい拡張機能です。 PyKDは何をしますか? Pythonを実行してWinDbgを自動化できます-つまり WinDbgコマンドを実行し、出力を分析し、WinDbgでPythonツールを使用します。



上記の例はあまり興味深いものではありません。 ここでは、スタックが破損している場合にスタックを修正しようとしています。 ただし、その考えは、WinDbgで実行できるほとんどのことに対してPython APIを使用することです。 ひどいWinDbg言語で書かない1つの方法は、Pythonを使用することです。



私には、heap_stat.pyと呼ばれるPyKDを使用して作成された優れたスクリプトがあります。







実際、これは.NET用ではなく、C ++アプリケーション用ですが、C ++開発者に、前に示した機能の一部を提供します(ヒープとそこにあるオブジェクトの表示)。 .NETの場合、これまで見てきたように、これは非常に簡単です。 C ++の場合、これはもう少し複雑です。



この拡張機能は、ヒープC ++で動作するPythonスクリプトであり、オブジェクトを検索して、このタイプのオブジェクトの数を表示します。 場合によっては、合計サイズも出力できます。 WinDbgスクリプト言語のみを使用してこれを行うことは非常に困難であり、Pythonを使用することもできます。 結局のところ、これはPythonです。



拡張モデル





他のいくつかの拡張機能を見てみましょう。 原則として、拡張モデルは非常に単純です。







WinDbgの各拡張機能は単なるdllです。 関数のエクスポートをサポートする任意の言語で、C ++またはC#-で記述できます。 そして、拡張機能をデバッガーに渡し、各関数をコマンドとして実行するだけです。



拡張機能は、デバッガーAPIにアクセスできます。 拡張機能が何かを表示する場合、たとえば、メモリ内のオブジェクトを見る場合、デバッガーインターフェイスにアクセスできるとします。



基本的にどのように見えるかを示すために私が設計した簡単な拡張機能を次に示します。







この拡張機能は、コンテンツをURLで表示します。 デバッガーから、HTTP要求を実行し、受信したHTMLコードを印刷できます。



拡張機能はC#で記述されており、非常に簡単です。結果がどのように見えるかを見てみましょう。 これを行うために、WinDbgと参加可能なプロセス(メモ帳など)を起動します。 その後、拡張機能をダウンロードします。たとえば、google.comにアクセスできます。 インターネット接続が機能する場合、この結果が得られます。







これはおそらくロシア語です(エンコードはありません)。



興味深いことに、デバッガーの出力には直接リンクがあります。 これにより、その使用がユーザーにより開かれます。 たとえば、ここでBloggerをクリックすると、画面の下部に、デバッガーが別のリクエスト(別のページ)を実行していることがわかります。 その結果、非常にシンプルなテキストブラウザーがデバッガーに組み込まれました。



上記のデバッガを拡張する非常に簡単な例を挙げましたが、それがあなたの出発点として役立つことを願っています。 本当に強力なことを行う独自の拡張機能を作成することに興味がある場合は、このアプローチを使用して独自の拡張機能を開発できます。 難しくありません。C#で拡張機能を作成できます。



いくつかの既存の拡張機能を見てみましょう(独自の拡張機能を作成することは必ずしも必要ではありません)。

CMKD



64ビットコードをデバッグするときに直面しなければならない典型的な状況の例を次に示します。 Windows 64ビットコードには呼び出し規約があり、最適化されたコードをデバッグするときに関数の引数を取得することが非常に難しくなります。 , , , , — . Visual Studio, WinDbg.







WaitForMultipleObjects



, . , . : « , , , ». 4 , , , , « » 4 .



cmkd , . , , .







, !stack



. WaitForMultipleObjects



. CMKD , .



, , . , , , rdx



, , r13



. r13



- , . , - .



, . , .NET-, C++ 64- Windows ( , ).



SOSEX



SOSEX . , , WinDbg , SOSEX — , .NET WinDbg.



SOSEX . Microsoft, ( SOSEX ). .



SOSEX — heap. heap .NET, , , heap — , 10 — , .



? heap — — . :







.foreach



— heap , (, ). !bhi



, !mroot



, , , Byte



Schedule



, , , Employee



.



. — SOSEX !gcroot



. 30 ( 30 ) — 5 . , , ( 30 ). !mroot



30 0 ( — , , ). , . heap, WinDbg, — SOSEX. — heap. .



netext



, , netext . Microsoft — . , . ASP.NET. , heap.







, !wruntime



, ASP.NET, . .



!whttp



, HTTP-, heap. HTTP-, ( ). , , , , .







HTTP- : , ( ) .. ASP.NET, .



netext SQL- . heap. SQL, , .







, HttpContext



_request._rawUrl



_response._statusCode



. abc HTTP-.



( )?







netext. . Order



, Address



, $addr



. . — .



netext , .

tracer



, (, , ) — . tracer. WinDbg, .



, , , , , . tracer , ( ) , — . , .









WinDbg, , , , , Visual Studio.



- Visual Studio, , Windows. , , — . WinDbg , , , , TCP, SSL, HTTP — , .



.

.







cdb ( TCP- 5050 ) .







, . x86 :

 cdb -remote tcp:server=localhost,port=5050
      
      





: , , , .



. Microsoft , . . とても助かりました。



WinDbg, — Smart Client.







, WinDbg WinDbg . dbgsrv.exe, , .



, . , , — ASP.NET.



. , , — .



WinDbg. , , «» . - , «» , - . , , . , , . , . , Visual Studio.



. WinDbg:





CLRMD :





:





msos — CLI-, C#:








20- — « The Performance Investigator's Field Guide », DotNext 2017 Piter ( — 19-20 ).



, «Production Performance and Troubleshooting of .NET Applications»



All Articles