Time Travel Debugging - サンプル アプリのチュートリアル

Small time travel logo showing clock.

このラボでは、コードの欠陥がある小さなサンプル プログラムを使用して、Time Travel Debugging (TTD) を紹介します。 TTD を使って問題をデバッグし、その根本原因を特定します。 この小さなプログラムの問題は簡単に見つけることができますが、一般的な手順は、より複雑なコードで使用できます。 この一般的な手順は、次のように要約できます。

  1. 失敗したプログラムのタイム トラベル トレースをキャプチャします。
  2. dx (Display Debugger Object Model Expression) コマンドを使用して、記録に格納されている例外イベントを見つけます。
  3. !tt (time travel) コマンドを使用して、トレース内の例外イベントの位置に移動します。
  4. トレースのその時点から、問題のコードがスコープに入るまで、1 ステップ前に戻ります。
  5. エラーコードをスコープに含め、ローカル値を調び、正しくない値を含む可能性のある変数の仮説を作成します。
  6. 正しくない値を持つ変数のメモリ アドレスを確認します。
  7. ba ( Break on Access ) コマンドを使用して、suspect 変数のアドレスにメモリ アクセス (ba) ブレークポイントを設定します。
  8. g- を使用して、疑わしい変数の最後のメモリ アクセスポイントに戻ります。
  9. その場所、または前のいくつかの手順がコードの欠陥のポイントであるかどうかを確認します。 その場合は、完了です。 正しくない値が他の変数から取得された場合は、2 番目の変数にアクセス ブレークポイントで別の中断を設定します。
  10. g- を使用して、2 番目の疑わしい変数の最後のメモリ アクセスポイントに戻ります。 その場所か、前のいくつかの手順にコードの欠陥が含まれているかどうかを確認します。 その場合は、完了です。
  11. エラーの原因となった正しくない値を設定したコードが見つかるまで、このプロセスを繰り返します。

この手順で説明する一般的な手法は、さまざまなコードの問題に適用されますが、一意のアプローチを必要とする固有のコードの問題があります。 このチュートリアルで示す手法は、デバッグ ツール セットを拡張するのに役立つ必要があり、TTD トレースで可能な機能の一部を示します。

ラボの目標

このラボを完了すると、タイム トラベル トレースで一般的な手順を使用して、コード内の問題を見つけることができます。

ラボのセットアップ

ラボを完了するには、次のハードウェアが必要です。

  • Windows 10を実行しているノート PC またはデスクトップ コンピューター (ホスト)

ラボを完了するには、次のソフトウェアが必要です。

  • WinDbg プレビュー。 WinDbg プレビューのインストールの詳細については、「WinDbg プレビュー - インストール」を参照してください。
  • サンプル C++ コードをビルドするVisual Studioします。

ラボには、次の 3 つのセクションがあります。

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

セクション 1 では、Visual Studioを使用してサンプル コードをビルドします。

Visual Studioでサンプル アプリを作成する

  1. Microsoft Visual Studioで、[FileNew>>Project/Solution... をクリックし、Visual C++ テンプレートをクリックします。

    Win32 コンソール アプリケーションを選択します。

    DisplayGreeting のプロジェクト名を指定し、[OK] をクリックします

  2. セキュリティ開発ライフサイクル (SDL) チェックをオフにします。

    win32 application wizard application settings.

  3. [完了] をクリック します

  4. 次のテキストを、Visual Studioの DisplayGreeting.cpp ペインに貼り付けます。

    // DisplayGreeting.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <array>
    #include <stdio.h>
    #include <string.h>
    
    void GetCppConGreeting(wchar_t* buffer, size_t size)
    {
        wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!";
    
        wcscpy_s(buffer, size, message);
    }
    
    int main()
    {
         std::array <wchar_t, 50> greeting{};
         GetCppConGreeting(greeting.data(), sizeof(greeting));
    
         wprintf(L"%ls\n", greeting.data());
    
         return 0;
    }
    
  5. Visual Studioで、Project>DisplayGreeting プロパティをクリックします。 次に、 C/C++コード生成をクリックします。

    次のプロパティを設定します。

    設定
    セキュリティ チェック セキュリティ チェックを無効にする (/GS-)
    基本ランタイムのチェック 既定

    Note

    これらの設定は推奨されませんが、コーディングを迅速化したり、特定のテスト環境を容易にしたりするために、これらの設定を使用するように誰かがアドバイスするシナリオを想像できます。

  6. Visual Studioで、[BuildBuild>ソリューション] をクリックします。

    すべて問題がなければ、ビルド ウィンドウにビルドが成功したことを示すメッセージが表示されます。

  7. ビルドされたサンプル アプリ ファイルを見つける

    ソリューション エクスプローラーで DisplayGreeting プロジェクトを右クリックし、エクスプローラーで [フォルダーを開く] を選択します。

    サンプルのコンパイル済み exe ファイルとシンボル pdb ファイルを含む Debug フォルダーに移動します。 たとえば、プロジェクトが格納されているフォルダーである場合は、 C:\Projects\DisplayGreeting\Debug に移動します。

  8. コードの欠陥を含むサンプル アプリを実行する

    exe ファイルをダブルクリックして、サンプル アプリを実行します。

    Screenshot that shows the .exe file running in the console.

    このダイアログ ボックスが表示されたら、[プログラムを閉じる] を選択します。

    Screenshot that shows the

    チュートリアルの次のセクションでは、サンプル アプリの実行を記録して、この例外が発生している理由を特定できるかどうかを確認します。

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

セクション 2 では、不適切な動作のサンプル "DisplayGreeting" アプリのトレースを記録します

サンプル アプリを起動し、TTD トレースを記録するには、次の手順に従います。 TTD トレースの記録に関する一般的な情報については、「タイム トラベル デバッグ - トレースの記録」を参照してください。

  1. WinDbg プレビューを管理者として実行して、タイム トラベル トレースを記録できるようにします。

  2. WinDbg プレビューで、FileStart>debuggingLaunch>実行可能ファイル (詳細) を選択します。

  3. 記録するユーザー モードの実行可能ファイルへのパスを入力するか、[ 参照 ] を選択して実行可能ファイルに移動します。 WinDbg プレビューで実行可能ファイルの起動メニューを操作する方法については、「 WinDbg プレビュー - ユーザー モード セッションを開始する」を参照してください。

    Screen shot of WinDbg Preview showing start recording checkbox in launch executable (advanced) screen.

  4. [ Time Travel Debugging を使用して記録 する] ボックスをオンにして、実行可能ファイルの起動時にトレースを記録します。

  5. [ 構成] と [記録 ] をクリックして記録を開始します。

  6. [記録の構成] ダイアログ ボックスが表示されたら、[ レコード ] をクリックして実行可能ファイルを起動し、記録を開始します。

    Screen shot of WinDbg Preview showing configure recording dialog with apath set to temp.

  7. トレースが記録されていることを示す記録ダイアログが表示されます。 その直後に、アプリケーションがクラッシュします。

  8. [ プログラムを閉じる] をクリックして、[DisplayGreeting has stopped working]\(DisplayGreeting has stopped working\)\(DisplayGre

    Faulting app dialog box.

  9. プログラムがクラッシュすると、トレース ファイルが閉じられ、ディスクに書き込まれます。

    Screen shot of WinDbg Preview showing output with 1/1 keyframes indexed.

  10. デバッガーによってトレース ファイルが自動的に開き、インデックスが作成されます。 インデックス作成は、トレース ファイルの効率的なデバッグを可能にするプロセスです。 このインデックス作成プロセスは、より大きなトレース ファイルに時間がかかります。

    (5120.2540): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: D:0 [Unindexed] Index
    !index
    Indexed 10/22 keyframes
    Indexed 20/22 keyframes
    Indexed 22/22 keyframes
    Successfully created the index in 755ms.
    

注意

キーフレームは、インデックス作成に使用されるトレース内の場所です。 キーフレームは自動的に生成されます。 トレースが大きいほど、より多くのキーフレームが含まれます。

  1. この時点で、トレース ファイルの先頭に移動し、時間内に前後に移動する準備が整います。

    これで TTD トレースが記録されたので、トレースを再生したり、トレース ファイルを操作したりできます 。たとえば、同僚と共有できます。 トレース ファイルの操作の詳細については、「タイム トラベル デバッグ - トレース ファイルの操作」を参照してください。

このラボの次のセクションでは、トレース ファイルを分析して、コードに関する問題を特定します。

セクション 3: トレース ファイルの記録を分析してコードの問題を特定する

セクション 3 では、トレース ファイルの記録を分析してコードの問題を特定します。

WinDbg 環境の構成

  1. 次のコマンドを入力して、シンボル パスにローカル シンボルの場所を追加し、シンボルを再読み込みします。

    .sympath+ C:\MyProjects\DisplayGreeting\Debug
    .reload
    
  2. 次のコマンドを入力して、ソース パスにローカル コードの場所を追加します。

    .srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
    
  3. スタックとローカル変数の状態を表示できるようにするには、[WinDbg プレビュー] リボンの [ 表示 ] と [ ローカル] と [ ビュースタック] を選択します。 ウィンドウを整理して、ウィンドウ、ソース コード、コマンド ウィンドウを同時に表示できるようにします。

  4. WinDbg プレビュー リボンで、 ソース ファイルと オープン ソース ファイルを選択します。 DisplayGreeting.cpp ファイルを見つけて開きます。

例外を調べる

  1. トレース ファイルが読み込まれると、例外が発生したという情報が表示されます。

    2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000
    eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000
    
  2. dx コマンドを使用して、記録内のすべてのイベントを一覧表示します。 例外イベントがイベントに一覧表示されます。

    0:000> dx -r1 @$curprocess.TTD.Events
    ...
    [0x2c]           : Module Loaded at position: 9967:0
    [0x2d]           : Exception at 9BDC:0
    [0x2e]           : Thread terminated at 9C43:0
    ...
    
    

    注意

    このチュートリアルでは、3 つの期間を使用して、余分な出力が削除されたことを示します。

  3. 例外イベントをクリックすると、その TTD イベントに関する情報が表示されます。

    0:000> dx -r1 @$curprocess.TTD.Events[17]
    @$curprocess.TTD.Events[17]                 : Exception at 68:0
        Type             : Exception
        Position         : 68:0 [Time Travel]
        Exception        : Exception of type Hardware at PC: 0X540020
    
  4. 例外データをさらにドリルダウンするには、[例外] フィールドをクリックします。

    0:000> dx -r1 @$curprocess.TTD.Events[17].Exception
    @$curprocess.TTD.Events[17].Exception                 : Exception of type Hardware at PC: 0X540020
        Position         : 68:0 [Time Travel]
        Type             : Hardware
        ProgramCounter   : 0x540020
        Code             : 0xc0000005
        Flags            : 0x0
        RecordAddress    : 0x0
    

    例外データは、これが CPU によってスローされたハードウェア 障害であることを示します。 また、これがアクセス違反であることを示す0xc0000005の例外コードも提供します。 これは通常、アクセスできないメモリに書き込もうとしていたことを示します。

  5. 例外イベントの [Time Travel] リンクをクリックして、トレース内のその位置に移動します。

    0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    Setting position: 68:0
    
    @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 68:0
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c 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 ??
    

    この出力では、スタックとベース ポインターが 2 つの非常に異なるアドレスを指していることに注意してください。

    esp=00effe4c ebp=00520055
    

    これは、スタックの破損 (関数が返され、スタックが破損した可能性があります) を示している可能性があります。 これを検証するには、CPU 状態が破損する前に戻り、スタックの破損が発生したタイミングを判断できるかどうかを確認する必要があります。

ローカル変数を調べてコード ブレークポイントを設定する

トレースの障害が発生した時点では、エラー処理コードの真の原因の後に、いくつかの手順が終わるのが一般的です。 タイムトラベルでは、一度に命令に戻って、真の根本原因を見つけることができます。

  1. [ホーム] リボンから、[ステップ イン バック] コマンドを使用して、3 つの手順をステップ バックします。 これを行う場合は、引き続きスタックウィンドウとメモリウィンドウを調べます。

    コマンド ウィンドウには、3 つの命令をステップ バックすると、タイム トラベルの位置とレジスタが表示されます。

    0:000> t-
    Time Travel Position: 67:40
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c 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: 67:3F
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=0019193d esp=00effe48 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+0x4d:
    0019193d c3
    
    0:000> t-
    Time Travel Position: 67:39
    eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00191935 esp=00effd94 ebp=00effe44 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    DisplayGreeting!main+0x45:
    

    注意

    このチュートリアルでは、コマンド出力に UI メニュー オプションの代わりに使用できるコマンドが表示され、コマンド ラインの使用設定を持つユーザーがコマンド ライン コマンドを使用できるようになります。

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

    esp=00effd94 ebp=00effe44
    

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

    Screenshot of WinDbg Preview showing locals windows with memory ASCII output and source code window.

  3. さらに調査するために、メモリ ウィンドウを開いて、 0x00effe44のベース ポインター メモリ アドレスの近くの内容を表示できます。

  4. 関連付けられている ASCII 文字を表示するには、[メモリ] リボンから [ テキスト] を選択し、[ASCII] を選択 します

    screenshot of winbbg preview showing memory ascii output and source code window.

  5. 命令を指すベース ポインターの代わりに、メッセージ テキストを指しています。 だから、何かがここには正しくありません、これは私たちがスタックを破損した時点に近いかもしれません。 さらに調査するために、ブレークポイントを設定します。

注意

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

TTD とブレークポイント

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

メモリ アクセス ブレークポイント

メモリの場所にアクセスしたときに発生するブレークポイントを設定できます。 次の構文を使用して、 ba (アクセス時に中断) コマンドを使用します。

ba <access> <size> <address> {options}
オプション 説明

e

execute (CPU がアドレスから命令をフェッチする場合)

r

読み取り/書き込み (CPU がアドレスに対して読み取りまたは書き込みを行う場合)

書き込み (CPU がアドレスに書き込む場合)

特定の時点で設定できるデータ ブレークポイントは 4 つだけであり、データを正しく揃えるか、ブレークポイントをトリガーしないようにする必要があります (単語は 2 で割り切れるアドレスで終わり、dwords は 4 で割り切り可能で、クワッドワードは 0 または 8 で区切る必要があります)。

ベース ポインターのメモリ アクセス ブレークポイントで中断を設定する

  1. トレースのこの時点で、ベース ポインターへの書き込みメモリ アクセスにブレークポイントを設定します。この例では 00effe44 です。 これを行うには、監視するアドレスを使用して ba コマンドを使用します。 4 バイトの書き込みを監視するため、w4 を指定します。

    0:000> ba w4 00effe44
    
  2. [ 表示] を選択し、[ ブレークポイント] を選択して、意図したとおりに設定されていることを確認します。

    WinDbg Preview showing breakpoints window with one breakpoint.

  3. [ホーム] メニューから [ 戻る ] を選択して、ブレークポイントがヒットするまで時間をさかのぼって移動します。

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:92
    eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8
    eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0         nv up ei pl nz ac pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
    DisplayGreeting!DisplayGreeting+0x3a:
    00d4174a c745e000000000  mov     dword ptr [ebp-20h],0 ss:002b:0053fdc8=cccccccc
    
  4. [ 表示] を選択し、[ ローカル] を選択します。 ローカル ウィンドウでは、 変換先 変数にメッセージの一部のみが含まれているのに対し、 ソース にはすべてのテキストが含まれていることがわかります。 この情報は、スタックが破損したという考えをサポートします。

    Screenshot of WinDbg Preview locals window.

  5. この時点で、プログラム スタックを調べて、アクティブなコードを確認できます。 [表示] リボンから [スタック] を選択します

    Screenshot of WinDbg Preview stack window.

Microsoft が提供する wscpy_s() 関数にこのようなコード バグがある可能性は非常に低いので、スタックをさらに詳しく見てみましょう。 スタックには、Greeting!main が Greeting を呼び出すというメッセージが表示されます。GetCppConGreeting。 非常に小さなコード サンプルでは、この時点でコードを開くだけで、エラーが非常に簡単に見つかる可能性があります。 ただし、より大規模で複雑なプログラムで使用できる手法を説明するために、さらに調査する新しいブレークポイントを設定します。

GetCppConGreeting 関数のアクセス ブレークポイントで中断を設定する

  1. [ブレークポイント] ウィンドウを使用して、既存のブレークポイントを右クリックし、[削除] を選択して既存のブレークポイントをクリア します

  2. DisplayGreeting のアドレスを決定します。 dx コマンドを使用した GetCppConGreeting 関数。

    0:000> dx &DisplayGreeting!GetCppConGreeting
    &DisplayGreeting!GetCppConGreeting                 : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)]
        [Type: void __cdecl(wchar_t *,unsigned int)]
    
  3. ba コマンドを使用して、メモリ アクセスにブレークポイントを設定します。 関数は実行のためにメモリから読み取られるだけなので、r - 読み取りブレークポイントを設定する必要があります。

    0:000> ba r4 b61720
    
  4. [ブレークポイント] ウィンドウでハードウェア読み取りブレークポイントがアクティブであることを確認します。

    WinDbg Preview showing breakpoints window with one hardware read breakpoint.

  5. あいさつ文字列のサイズについて疑問に思っている場合は、sizeof(greeting) の値を表示するウォッチ ウィンドウを設定します。 [表示] リボンから [ ウォッチ ] を選択し、 sizeof(greeting) を指定します。

    WinDbg Preview showing a watch locals window.

  6. [タイム トラベル] メニューで、 タイム トラベルを使用してトレース!tt 0開始するか、コマンドを使用してトレースの先頭に移動します。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  7. [ホーム] メニューの [ 移動 ] を選択するか、コマンドを g 使用して、ブレークポイントにヒットするまでコード内を進めます。

    0:000> g
    Breakpoint 2 hit
    Time Travel Position: 4B:1AD
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x1:
    00b61721 8bec            mov     ebp,esp
    
  8. [ホーム] メニューの [ ステップ アウト ] を選択するか、コマンドを g-u 使用して 1 つのステップを戻します。

    0:000> g-u
    Time Travel Position: 4B:1AA
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!main+0x27:
    00b61917 e8def7ffff      call    DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)
    
  9. 根本原因が見つかったようです。 宣言した 応答応答 配列の長さは 50 文字ですが、GetCppConGreeting に渡す sizeof(greeting) は 0x64、100 です。

    WinDbg Preview showing the Display greeting code with a watch locals window showing X64.

    さらにサイズの問題を見ると、メッセージの長さが 75 文字で、文字列文字の末尾を含めて 76 であることがわかります。

    HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
    
  10. コードを修正する方法の 1 つは、文字配列のサイズを 100 に拡張することです。

    std::array <wchar_t, 100> greeting{};
    

    また、このコード行で sizeof(greeting) を size(greeting) に変更する必要があります。

     GetCppConGreeting(greeting.data(), size(greeting));
    
  11. これらの修正プログラムを検証するために、コードを再コンパイルし、エラーなしで実行されていることを確認できます。

ソース ウィンドウを使用してブレークポイントを設定する

  1. この調査を実行する別の方法は、任意のコード行をクリックしてブレークポイントを設定することです。 たとえば、ソース ウィンドウで std:array 定義行の右側をクリックすると、そこにブレークポイントが設定されます。

    Screenshot of source window showing breakpoint set on std:array.

  2. [タイム トラベル] メニューの [ Time travel]\(タイム トラベル \) コマンドを使用して、トレースの先頭に移動します。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  3. [ホーム] リボンの [ 移動 ] をクリックして、ブレークポイントがヒットするまで戻ります。

    Breakpoint 0 hit
    Time Travel Position: 5B:AF
    eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60
    eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!DisplayGreeting+0x41:
    013a17c1 8bf4            mov     esi,esp
    

あいさつ文変数のアクセス ブレークポイントの中断を設定する

この調査を実行する別の方法として、疑わしい変数にブレークポイントを設定し、変更しているコードを調べる方法もあります。 たとえば、GetCppConGreeting メソッドで greeting 変数にブレークポイントを設定するには、次の手順を使用します。

このチュートリアルのこの部分では、前のセクションのブレークポイントにまだ配置されていることを前提としています。

  1. [表示] と [ローカル] から。 ローカル ウィンドウでは、現在のコンテキストで あいさつ文 を使用できるため、メモリの場所を決定できます。

  2. dx コマンドを使用して、あいさつ文の配列を調べます。

    0:000> dx &greeting
    &greeting                 : 0xddf800 [Type: std::array<wchar_t,50> *]
       [+0x000] _Elems           : "꽘棶檙瞝???" [Type: wchar_t [50]]
    

    このトレースでは、 あいさつ は ddf800 のメモリ内にあります。

  3. ブレークポイント ウィンドウを使用して、既存のブレークポイントを右クリックし、[削除] を選択して、既存のブレークポイントをクリア します

  4. 書き込みアクセスを監視するメモリ アドレスを使用して 、ba コマンドでブレークポイントを設定します。

    ba w4 ddf800
    
  5. [タイム トラベル] メニューの [ Time travel]\(タイム トラベル \) コマンドを使用して、トレースの先頭に移動します。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  6. [ホーム] メニューで、[ 移動 ] を選択して、あいさつ配列の最初のポイントへのメモリ アクセスに進みます。

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:9C
    eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8
    eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x25:
    013a1735 c745ec04000000  mov     dword ptr [ebp-14h],4 ss:002b:001bf7c4=cccccccc
    

    または、トレースの最後まで移動し、コードを逆に実行して、配列メモリの場所が書き込まれたトレース内の最後のポイントを見つけることもできました。

TTD を使用します。メモリ アクセスを表示するメモリ オブジェクト

トレース メモリ内のどのポイントにアクセスしたかを判断するもう 1 つの方法は、TTD を使用することです。メモリ オブジェクトと dx コマンド。

  1. dx コマンドを使用して、あいさつ文の配列を調べます。

    0:000> dx &greeting
    &greeting                 : 0xddf800 [Type: std::array<wchar_t,50> *]
       [+0x000] _Elems           : "꽘棶檙瞝???" [Type: wchar_t [50]]
    

    このトレースでは、 あいさつ は ddf800 のメモリ内にあります。

  2. dx コマンドを使用して、読み取り書き込みアクセス権を使用して、そのアドレスから始まるメモリ内の 4 バイトを確認します。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")
    @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw")                
        [0x0]           
        [0x1]           
        [0x2]           
        [0x3]           
        [0x4]           
        [0x5]           
        [0x6]           
        [0x7]           
        [0x8]           
        [0x9]           
        [0xa]           
        ...         
    
  3. いずれかの出現箇所をクリックすると、そのメモリ アクセスの発生に関する詳細情報が表示されます。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 27:3C1 [Time Travel]
        TimeEnd          : 27:3C1 [Time Travel]
        AccessType       : Write
        IP               : 0x6900432f
        Address          : 0xddf800
        Size             : 0x4
        Value            : 0xddf818
    
  4. [タイムトラベル]をクリックして、トレースをポイントインタイムに配置します。

    0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 27:3C1
    eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046
    eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    ucrtbased!_register_onexit_function+0xf:
    6900432f 51              push    ecx
    
  5. トレースでの読み取り/書き込みメモリ アクセスの最後の出現に関心がある場合は、一覧の最後の項目をクリックするか、追加することができます。dx コマンドの最後まで Last() 関数。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 53:100E [Time Travel]
        TimeEnd          : 53:100E [Time Travel]
        AccessType       : Read
        IP               : 0x690338e4
        Address          : 0xddf802
        Size             : 0x2
        Value            : 0x45
    
  6. [Time Travel] をクリックすると、トレース内のその位置に移動し、このラボで前述した手法を使用して、その時点でのコード実行をさらに確認できます。

TTD の詳細については、以下を参照してください。メモリ オブジェクトについては、TTD を参照してください 。Memory オブジェクト

まとめ

この非常に小さなサンプルでは、数行のコードを見て問題を特定できましたが、大規模なプログラムでは、ここで示す手法を使用して、問題を見つけるのに必要な時間を短縮できます。

トレースが記録されると、トレースと再現手順を共有でき、問題は任意の PC でオンデマンドで再現可能になります。

参照

Time Travel Debugging - 概要

Time Travel Debugging - 記録

Time Travel Debugging - トレースの再生

Time Travel Debugging - トレース ファイルの使用