Share via


WinDbg - タイムライン

ビットを検査する虫眼鏡を使用した WinDbg ロゴ。

タイム トラベル デバッグ (TTD) を使用すると、ユーザーはプログラムの実行の記録であるトレースを記録できます。 タイムラインは、実行中に発生するイベントを視覚的に表現したものです。 イベントとして表示される場所は、ブレークポイント、メモリの読み取り/書き込み、関数の呼び出しと戻り、例外などがあり得ます。

例外、メモリ アクセス、ブレークポイント、関数呼び出しを表示するデバッガーのタイムライン。

タイムライン ウィンドウを使用すると、重要なイベントをすばやく表示し、相対位置を理解し、TTD トレース ファイル内のその位置に簡単にジャンプできます。 複数のタイムラインを使用して、タイムトラベル トレース内のイベントを視覚的に調査し、イベントの相関関係を発見します。

タイムライン ウィンドウは、TTD トレース ファイルを開くときに表示され、データ モデル クエリを手動で作成しなくても主要なイベントを表示します。 同時に、すべてのタイム トラベル オブジェクトを使用して、より複雑なデータ クエリを実行できるようになります。

タイム トラベル トレース ファイルの作成と操作の詳細については、「タイム トラベル デバッグ - 概要」を参照してください。

タイムラインの種類

タイムライン ウィンドウには次のイベントを表示できます。

  • 例外 (特定の例外コードでさらにフィルタリングできます)
  • ブレークポイント
  • 関数呼び出し (module!function の形式で検索)
  • メモリ アクセス (2 つのメモリ アドレス間の読み取り / 書き込み / 実行)

各イベントの上にマウスを置くと、ツールチップから詳細情報が表示されます。 イベントをクリックすると、そのイベントのクエリが実行され、詳細情報が表示されます。 イベントをダブルクリックすると、TTD トレース ファイル内のその場所にジャンプします。

例外

トレース ファイルをロードし、タイムラインがアクティブになると、記録内の例外が自動的に表示されます。

ブレークポイントの上にマウスを置くと、例外の種類や例外コードなどの情報が表示されます。

特定の例外コードに関する情報と例外を表示するデバッガーのタイムライン。

オプションの例外コード フィールドを使用して、特定の例外コードをさらにフィルタリングできます。

タイムラインの種類が例外に設定され、例外コードが 0xC0000004 に設定されているタイムライン デバッガーの例外のダイアログ ボックス。

特定の例外タイプに新しいタイムラインを追加することもできます。

ブレークポイント

ブレークポイントを追加した後、そのブレークポイントがヒットしたときの位置をタイムライン上に表示できます。 これは、たとえば bp Set Breakpoint コマンドを使用して実行できます。 ブレークポイントの上にマウスを移動すると、ブレークポイントに関連付けられたアドレスと命令ポインタが表示されます。

約 30 個のブレークポイント インジケーターを表示するデバッガーのタイムライン。

ブレークポイントがクリアされると、関連付けられたブレークポイント タイムラインが自動的に削除されます。

関数呼び出し

関数呼び出しの位置をタイムライン上に表示できます。 これを行うには、 たとえば module!functionの形式 TimelineTestCode!multiplyTwoで検索を提供します。 ワイルドカード (例: TimelineTestCode!m*) を指定することもできます。

関数呼び出し名が入力されたタイムラインのデバッガーへの追加。

関数呼び出しの上にカーソルを置くと、関数名、入力パラメータ、その値、および戻り値が表示されます。 この例では、 バッファサイズ が DisplayGreeting!GetCppConGreeting のパラメータであるため、示しています。

関数呼び出しとレジスタ ウィンドウを表示するデバッガーのタイムライン。

メモリ アクセス

メモリ アクセス タイムラインを使用して、メモリの特定の範囲がいつ読み書きされたか、またはどこでコードが実行されたかを表示します。 開始アドレスと停止アドレスは、2 つのメモリ アドレス間の範囲を定義するために使用されます。

書き込みボタンが選択されたタイムラインメモリ アクセス ダイアログの追加。

メモリ アクセス項目の上にマウスを置くと、値と命令ポインタが表示されます。

メモリ アクセス イベントを表示するデバッガーのタイムライン。

タイムラインを操作する

タイムライン上にカーソルを置くと、灰色の垂直線がカーソルの後に表示されます。 青い垂直線は、トレース内の現在位置を示します。

虫眼鏡アイコンをクリックして、タイムラインを拡大または縮小します。

上部のタイムライン コントロール領域で、長方形を使用してタイムラインのビューをパンします。 長方形の外側の区切り文字をドラッグして、現在のタイムライン ビューのサイズを変更します。

アクティブなビューポートを選択するために使用される上部領域を示すデバッガーのタイムライン。

マウスの動き

Ctrl + スクロールホイールを使用してズームインおよびズームアウトします。

Shift + スクロール ホイールを使用して左右にパンします。

タイムラインのデバッグ手法

デバッグ タイムライン手法をデモンストレーションするために、ここでは タイム トラベル デバッグ ウォークスルー を再利用します。 このデモは、サンプル コードを構築するための最初の 2 つの手順が完了し、そこに記載されている最初の 2 つの手順を使用して TTD 記録を作成していることを前提としています。

セクション 1: サンプルコードをビルドする

セクション 2: 「DisplayGreeting」サンプルのトレースを記録する

このシナリオでは、最初のステップはタイム トラベル トレース内の例外を見つけることです。 これは、タイムライン上にある唯一の例外をダブルクリックすることで実行できます。

コマンド ウィンドウを見ると、例外をクリックしたときに次のコマンドが発行されたことがわかります。

(2dcc.6600): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: CC:0
@$curprocess.TTD.Events.Where(t => t.Type == "Exception")[0x0].Position.SeekTo()

View>>Registers を選択してタイムラインのこの時点のレジスタを表示し、調査を開始します。

demolab 例外とレジスタ ウィンドウを表示するデバッガーのタイムライン。

コマンド出力では、スタック (esp) とベース ポインター (ebp) が 2 つのまったく異なるアドレスを指していることに注意してください。 これはスタックの破損を示している可能性があります。関数が返されてスタックが破損した可能性があります。 これを検証するには、CPU 状態が破損する前に戻り、いつスタック破損が発生したかを判断できるかどうかを確認する必要があります。

これを行う際に、ローカル変数とスタックの値を調べます。

View>>Locals を選択してローカル値を表示します。

View>>Stack を選択して、コード実行スタックを表示します。

トレースの失敗時点では、エラー処理コードの真の原因の数ステップ後に終了するのが一般的です。 タイム トラベルを使用すると、一度に指示を遡って真の根本原因を特定できます。

[ホーム] リボンから [ステップイン] コマンドを使用して、3 つの命令に戻ります。 これを実行しながら、スタック、ローカル、およびレジスタ ウィンドウを調べ続けます。

コマンド ウィンドウには、3 命令前に戻ると、タイムトラベル位置とレジスタが表示されます。

0:000> t-
Time Travel Position: CB:41
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00540020 esp=003cf7d0 ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00540020 ??              ???
0:000> t-
Time Travel Position: CB:40
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00061767 esp=003cf7cc ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
DisplayGreeting!main+0x57:
00061767 c3              ret
0:000> t-
Time Travel Position: CB:3A
eax=0000004c ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=0006175f esp=003cf718 ebp=003cf7c8 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
DisplayGreeting!main+0x4f:
0006175f 33c0            xor     eax,eax

トレースのこの時点では、スタックとベース ポインターにはより意味のある値が含まれているため、コード内の破損が発生したポイントに近づいているように見えます。

esp=003cf718 ebp=003cf7c8

また、興味深いのは、ローカル ウィンドウにはターゲット アプリの値が含まれており、ソース コード ウィンドウには、トレースのこの時点でソース コード内で実行する準備ができているコード行が強調表示されていることです。

さらに調査するには、メモリ ウィンドウを開いて、スタック ポインタ (esp) メモリ アドレス付近の内容を表示します。 この例では、値は 003cf7c8 です。 [Memory>>Text>>ASCII] を選択すると、そのアドレスに保存されている ASCII テキストが表示されます。

レジスタ、スタック、メモリのウィンドウを表示するデバッガー。

メモリ アクセスのタイムライン

対象のメモリ位置が特定されたら、その値を使用してメモリ アクセス タイムラインを追加します。 [+ タイムラインを追加] をクリックし、開始アドレスを入力します。 4 バイトを見ていきますので、それを開始アドレス 003cf7c8 に追加すると、003cf7cb になります。 デフォルトではすべてのメモリ書き込みを確認しますが、そのアドレスでの書き込みまたはコード実行のみを確認することもできます。

書き込みボタンが選択され、003cf7c8 の開始値が表示されているタイムライン メモリ アクセス ダイアログの追加。

ここで、タイムラインを逆にたどって、このタイムトラベルトレースのどの時点でこの記憶の場所が書き込まれたのかを調べ、何が見つかるかを確認できます。 タイムラインのこの位置をクリックすると、コピーされる文字列に対して地元の人が異なる値を評価していることがわかります。 文字列の長さが正しくないかのように、宛先の値が不完全であるように見えます。

ソースと宛先の値が異なるローカル値が表示されているメモリ アクセス タイムラインとローカル ウィンドウ。

ブレークポイントのタイムライン

ブレークポイントの使用は、関心のあるイベントでコードの実行を一時停止する一般的な方法です。 TTD を使用すると、ブレークポイントを設定し、トレースが記録された後にそのブレークポイントに到達するまで時間を遡ることができます。 問題の発生後にプロセスの状態を調査し、ブレークポイントの最適な場所を決定する機能により、TTD に固有の追加のデバッグ ワークフローが可能になります。

別のタイムライン デバッグ手法を検討するには、タイムラインで例外をクリックし、 [ホーム] リボンの [ステップイン] コマンドを使用して、もう一度 3 ステップ前に戻ります。

この非常に小さなサンプルでは、コードを調べるだけで非常に簡単ですが、数百行のコードと数十のサブルーチンがある場合は、ここで説明する手法を使用して、問題を特定するのに必要な時間を短縮できます。

前述したように、ベース ポインター (esp) は命令を指すのではなく、メッセージ テキストを指します。

ba コマンドを使用して、メモリ アクセスにブレークポイントを設定します。 aw - write ブレークポイントを設定して、メモリのこの領域にいつ書き込まれるかを確認します。

0:000> ba w4 003cf7c8

ここでは単純なメモリ アクセス ブレークポイントを使用しますが、ブレークポイントはより複雑な条件ステートメントとして構築できます。 詳細については、「bp、bu、bm (ブレークポイントの設定)」を参照してください。

[ホーム] メニューから [戻る] を選択すると、ブレークポイントに到達するまで過去に戻ります。

この時点で、プログラム スタックを調べて、どのコードがアクティブであるかを確認できます。

メモリ アクセス タイムラインとスタック ウィンドウを表示するデバッガーのタイムライン。

Microsoft が提供する wscpy_s() 関数にこのようなコード バグがある可能性は非常に低いため、スタックをさらに調べます。 スタックは、Greeting!main が Greeting!GetCppConGreeting を呼び出していることを示しています。 私たちの非常に小さなコード サンプルでは、この時点でコードを開くだけで、おそらくエラーを簡単に見つけることができます。 ただし、より大規模で複雑なプログラムで使用できるテクニックを説明するために、関数呼び出しタイムラインの追加を設定します。

関数呼び出しのタイムライン

[+ タイムラインの追加] をクリックし、 関数の検索文字列の DisplayGreeting!GetCppConGreeting に入力します。

[開始位置] チェック ボックスと [終了位置] チェック ボックスは、トレース内の関数呼び出しの開始位置と終了位置を示します。

dx コマンドを使用して関数呼び出しオブジェクトを表示し、関数呼び出しの開始位置と終了位置に対応する関連する TimeStart フィールドと TimeEnd フィールドを確認できます。

dx @$cursession.TTD.Calls("DisplayGreeting!GetCppConGreeting")[0x0]
    EventType        : 0x0
    ThreadId         : 0x6600
    UniqueThreadId   : 0x2
    TimeStart        : 6D:BD [Time Travel]
    SystemTimeStart  : Thursday, October 31, 2019 23:36:05
    TimeEnd          : 6D:742 [Time Travel]
    SystemTimeEnd    : Thursday, October 31, 2019 23:36:05
    Function         : DisplayGreeting!GetCppConGreeting
    FunctionAddress  : 0x615a0
    ReturnAddress    : 0x61746
    Parameters  

[開始] または [終了] のいずれか、または [開始] と [終了] の両方の場所ボックスをオンにする必要があります。

DisplayGreeting!GetCppConGreeting の関数検索文字列と関数呼び出しタイムラインの追加を表示する新しい [タイムライン] ダイアログを追加します。

私たちのコードは再帰的でもリエントラントでもないので、GetCppConGreeting メソッドが呼び出されたときにタイムライン上で見つけるのは非常に簡単です。 GetCppConGreeting への呼び出しも、ブレークポイントと定義したメモリ アクセス イベントと同時に発生します。 したがって、アプリケーションクラッシュの根本原因を注意深く調査するコードの領域を絞り込んだようです。

異なる文字列値を含むメッセージとバッファーを持つ、メモリ アクセス タイムラインとローカル ウィンドウを表示するデバッガーのタイムライン。

複数のタイムラインを表示してコードの実行を調査する

コード サンプルは小さいですが、複数のタイムラインを使用する手法により、タイム トラベル トレースを視覚的に探索できます。 トレース ファイル全体を調べて、「ブレークポイントに到達する前にメモリ領域にアクセスされたのはいつですか?」などの質問をすることができます。

メモリ アクセス タイムラインとローカル ウィンドウを表示するデバッガーのタイムライン。

追加の相関関係を確認したり、予期していなかったものを見つけたりできる機能により、タイムライン ツールは、コマンド ライン コマンドを使用してタイム トラベル トレースを操作する場合と異なります。

タイムラインのブックマーク

重要なタイム トラベル位置をメモ帳に手動でコピーして貼り付けるのではなく、WinDbg でブックマークします。 ブックマークを使用すると、他のイベントに対するトレース内のさまざまな位置を一目で確認したり、注釈を付けたりすることが容易になります。

ブックマークにわかりやすい名前を付けることができます。

Display Greeting アプリの最初の API 呼び出しの名前の例を示す新しいブックマーク ダイアログ。

[タイムライン > の表示] で使用できるタイムライン ウィンドウからブックマークにアクセスします。 ブックマークの上にマウスを置くと、ブックマーク名が表示されます。

3 つのブックマークを表示するタイムライン。そのうちの 1 つにカーソルが置かれ、ブックマーク名が表示されています。

ブックマークを右クリックしてその位置に移動したり、ブックマークの名前を変更したり削除したりできます。

位置への移動、編集、および削除のオプションを表示する、ブックマークの右クリック ポップアップ メニュー。

Note

バージョン 1.2402.24001.0 では、ブックマーク機能は使用できません。

参照

WinDbg の機能

タイムトラベルのデバッグのチュートリアル