Time Travel Debugging - サンプル アプリのチュートリアル
このラボでは、コードの欠陥がある小さなサンプル プログラムを使用して、Time Travel Debugging (TTD) を紹介します。 TTD を使って問題をデバッグし、その根本原因を特定します。 この小さなプログラムの問題は簡単に見つかりますが、一般的な手順は、より複雑なコードで使用できます。 この一般的な手順は、次のように要約できます。
- 失敗したプログラムのタイム トラベル トレースをキャプチャします。
- dx (Display Debugger Object Model Expression) コマンドを使用して、記録に格納されている例外イベントを見つけます。
- !tt (タイム トラベル) コマンドを使用して、トレース内の例外イベントの位置に移動します。
- トレースのその時点から、問題のエラーコードがスコープに入るまで、1 ステップ後方に移動します。
- エラーコードをスコープに入れ、ローカル値を調び、正しくない値を含む可能性のある変数の仮説を作成します。
- 正しくない値を持つ変数のメモリ アドレスを決定します。
- ba ( Break on Access ) コマンドを使用して、疑わしい変数のアドレスにメモリ アクセス (ba) ブレークポイントを設定します。
- g- を使用して、疑わしい変数の最後のメモリ アクセスポイントに戻ります。
- その場所、または前のいくつかの手順がコードの欠陥のポイントであるかどうかを確認します。 その場合は、完了です。 正しくない値が他の変数から取得された場合は、2 番目の変数にアクセス ブレークポイントで別の中断を設定します。
- g- を使用して、2 番目の疑わしい変数に対するメモリ アクセスの最後のポイントに戻ります。 その場所か、前のいくつかの手順にコードの欠陥が含まれているかどうかを確認します。 その場合は、完了です。
- エラーの原因となった正しくない値を設定したコードが見つかるまで、このプロセスを繰り返します。
この手順で説明する一般的な手法は、さまざまなコードの問題に適用されますが、一意のアプローチを必要とする固有のコードの問題があります。 このチュートリアルで示す手法は、デバッグ ツール セットを拡張するのに役立つ必要があり、TTD トレースで可能なものの一部を示します。
ラボの目的
このラボを完了すると、タイム トラベル トレースで一般的な手順を使用して、コード内の問題を見つけることができます。
ラボのセットアップ
ラボを完了するには、次のハードウェアが必要です。
- Windows 10またはWindows 11を実行しているノート PC またはデスクトップ コンピューター (ホスト)
ラボを完了するには、次のソフトウェアが必要です。
- WinDbg。 WinDbg のインストールの詳細については、「WinDbg - インストール」を参照してください。
- サンプル C++ コードをビルドするための Visual Studio。
ラボには、次の 3 つのセクションがあります。
- セクション 1: サンプル コードをビルドする
- セクション 2: "DisplayGreeting" サンプルのトレースを記録する
- セクション 3: トレース ファイルの記録を分析してコードの問題を特定する
セクション 1: サンプル コードをビルドする
セクション 1 では、Visual Studio を使用してサンプル コードをビルドします。
Visual Studio でサンプル アプリを作成する
Microsoft Visual Studio で、[ ファイル>] [新しい>プロジェクト/ソリューション] の順にクリックし、Visual C++ テンプレートをクリックします。
Win32 コンソール アプリケーションを選択します。
プロジェクト名として DisplayGreeting を 指定し、[OK] をクリック します。
[セキュリティ開発ライフサイクル (SDL)] チェック ボックスをオフにします。
[完了] をクリック します。
次のテキストを 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; }
Visual Studio で、[ プロジェクト>の表示][詳細プロパティ] をクリックします。 次に、[ C/C++ ] と [ コード生成] をクリックします。
次のプロパティを設定します。
設定 値 セキュリティ チェック セキュリティ チェックを無効にする (/GS-) 基本ランタイムのチェック 既定 Note
これらの設定は推奨されませんが、コーディングを迅速化したり、特定のテスト環境を容易にしたりするために、これらの設定を使用してアドバイスするシナリオを想像できます。
Visual Studio で、[ビルド ソリューションのビルド>] をクリックします。
すべてが正常に終了すると、ビルド ウィンドウに、ビルドが成功したことを示すメッセージが表示されます。
ビルドされたサンプル アプリ ファイルを見つける
ソリューション エクスプローラーで DisplayGreeting プロジェクトを右クリックし、エクスプローラーで [フォルダーを開く] を選択します。
サンプルの準拠した exe ファイルとシンボル pdb ファイルを含む Debug フォルダーに移動します。 たとえば、プロジェクトが格納されているフォルダーである場合は、 C:\Projects\DisplayGreeting\Debug に移動します。
コードの欠陥を含むサンプル アプリを実行する
exe ファイルをダブルクリックして、サンプル アプリを実行します。
このダイアログ ボックスが表示されたら、[プログラムを閉じる] を選択します
チュートリアルの次のセクションでは、サンプル アプリの実行を記録して、この例外が発生している理由を判断できるかどうかを確認します。
セクション 2: "DisplayGreeting" サンプルのトレースを記録する
セクション 2 では、不適切な動作のサンプル "DisplayGreeting" アプリのトレースを記録します
サンプル アプリを起動し、TTD トレースを記録するには、次の手順に従います。 TTD トレースの記録に関する一般的な情報については、「タイム トラベル デバッグ - トレースを記録する」を参照してください。
WinDbg を管理者として実行して、タイム トラベル トレースを記録できるようにします。
WinDbg で、[ファイル>] [デバッグの開始][実行可能ファイルの>起動 (詳細設定)] の順に選択します。
記録するユーザー モードの実行可能ファイルへのパスを入力するか、[ 参照 ] を選択して実行可能ファイルに移動します。 WinDbg の起動実行可能メニューの操作については、「 WinDbg - ユーザー モード セッションを開始する」を参照してください。
[ Time Travel Debugging を使用して記録する ] ボックスをオンにして、実行可能ファイルの起動時にトレースを記録します。
[ 構成と記録] をクリックして記録を開始します。
[記録の構成] ダイアログ ボックスが表示されたら、[ レコード ] をクリックして実行可能ファイルを起動し、記録を開始します。
トレースが記録されていることを示す記録ダイアログが表示されます。 その直後に、アプリケーションがクラッシュします。
[ プログラムを閉じる] をクリックして、[DisplayGreeting has stopped working]\(プログラムの終了\) ダイアログ ボックスを閉じます。
プログラムがクラッシュすると、トレース ファイルが閉じられ、ディスクに書き込まれます。
デバッガーによってトレース ファイルが自動的に開き、インデックスが作成されます。 インデックス作成は、トレース ファイルの効率的なデバッグを可能にするプロセスです。 このインデックス作成プロセスは、トレース ファイルのサイズが大きくなると時間がかかります。
(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.
注意
キーフレームは、インデックス作成に使用されるトレース内の場所です。 キーフレームは自動的に生成されます。 トレースが大きいほど、より多くのキーフレームが含まれます。
この時点で、トレース ファイルの先頭に移動し、前後に移動する準備が整います。
これで TTD トレースが記録されたので、トレースを再生したり、トレース ファイルを操作したりできます (たとえば、同僚と共有する)。 トレース ファイルの操作の詳細については、「タイム トラベル デバッグ - トレース ファイルの操作」を参照してください。
このラボの次のセクションでは、トレース ファイルを分析して、コードに関する問題を特定します。
セクション 3: トレース ファイルの記録を分析してコードの問題を特定する
セクション 3 では、トレース ファイルの記録を分析して、コードの問題を特定します。
WinDbg 環境を構成する
次のコマンドを入力して、シンボル パスにローカル シンボルの場所を追加し、シンボルを再読み込みします。
.sympath+ C:\MyProjects\DisplayGreeting\Debug .reload
次のコマンドを入力して、ソース パスにローカル コードの場所を追加します。
.srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
スタックとローカル変数の状態を表示できるようにするには、WinDbg リボンの [ 表示 ] と [ ローカル ] と [ ビュー とスタック] を選択 します。 ウィンドウを整理して、それらを表示できるように、ソース コードとコマンド ウィンドウを同時に表示できます。
WinDbg リボンで、[ ソース ファイル] と [ オープン ソース ファイル] を選択します。 DisplayGreeting.cppファイルを見つけて開きます。
例外を調べる
トレース ファイルが読み込まれると、例外が発生したという情報が表示されます。
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
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 つの期間を使用します。
Exception イベントをクリックすると、その 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
[例外] フィールドをクリックして、例外データをさらにドリルダウンします。
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の例外コードも提供します。 これは通常、アクセスできないメモリへの書き込みを試みていたことを示します。
例外イベントの [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 状態が破損する前に に戻り、スタックの破損が発生したタイミングを判断できるかどうかを確認する必要があります。
ローカル変数を調べてコード ブレークポイントを設定する
トレースの障害が発生した時点では、エラー処理コードの真の原因の後にいくつかのステップが行われるのが一般的です。 時間旅行では、一度に命令に戻って、真の根本原因を見つけることができます。
[ホーム] リボンから[戻る] コマンドを使用して、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 メニュー オプションの代わりに使用できるコマンドを示します。
トレースのこの時点で、スタックとベース ポインターにはより意味のある値があるため、破損が発生したコード内のポイントに近づいているように見えます。
esp=00effd94 ebp=00effe44
また、ローカル ウィンドウにはターゲット アプリの値が含まれており、ソース コード ウィンドウでは、トレースのこの時点で実行する準備ができているコード行が強調表示されていることも重要です。
さらに調査するために、メモリ ウィンドウを開いて、0x00effe44のベース ポインター メモリ アドレスの近くの内容 を表示できます。
関連付けられている ASCII 文字を表示するには、[メモリ] リボンから [ テキスト ] を選択し、[ ASCII] を選択します。
命令を指すベース ポインターの代わりに、メッセージ テキストを指しています。 したがって、ここには何かはありません。これはスタックを破損した時点に近い可能性があります。 さらに調査するために、ブレークポイントを設定します。
注意
この非常に小さなサンプルでは、コードを簡単に見ることができますが、数百行のコードと多数のサブルーチンがある場合は、ここで説明する手法を使用して、問題を見つけるのに必要な時間を短縮できます。
TTD とブレークポイント
ブレークポイントの使用は、関心のあるイベントでコードの実行を一時停止する一般的な方法です。 TTD を使用すると、ブレークポイントを設定し、トレースが記録された後にブレークポイントにヒットするまで時間を戻すことができます。 問題が発生した後にプロセスの状態を調べ、ブレークポイントに最適な場所を決定する機能により、TTD に固有の追加のデバッグ ワークフローが可能になります。
メモリ アクセス ブレークポイント
メモリの場所にアクセスしたときに発生するブレークポイントを設定できます。 ba (アクセス 時に中断) コマンドを使用し、次の構文を使用します。
ba <access> <size> <address> {options}
オプション | 説明 |
---|---|
e |
execute (CPU がアドレスから命令をフェッチする場合) |
r |
読み取り/書き込み (CPU がアドレスに対して読み取りまたは書き込みを行う場合) |
。 |
write (CPU がアドレスに書き込む場合) |
特定の時点で設定できるデータ ブレークポイントは 4 つだけであり、データを正しく揃えるか、ブレークポイントをトリガーしないようにしてください (単語は 2 で割り切れるアドレスで終わる必要があり、dwords は 4 で割り切り、quadwords は 0 または 8 で割り切れる必要があります)。
ベース ポインターのメモリ アクセス ブレークポイントで中断を設定する
トレースのこの時点で、ベース ポインター (この例では 00effe44) への書き込みメモリ アクセスにブレークポイントを設定します。 これを行うには、監視するアドレスを使用して ba コマンドを使用します。 4 バイトの書き込みを監視するため、w4 を指定します。
0:000> ba w4 00effe44
[ 表示] を選択し、[ ブレークポイント] を選択して、意図したとおりに設定されていることを確認します。
[ホーム] メニューから [ 戻る ] を選択して、ブレークポイントにヒットするまで時間を戻します。
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
[ 表示] 、[ ローカル] の順に選択します。 ローカル ウィンドウでは、 変換先 変数にメッセージの一部のみが含まれているのに対し、 ソース にはすべてのテキストが含まれていることがわかります。 この情報は、スタックが破損したという考えをサポートします。
この時点で、プログラム スタックを調べて、アクティブなコードを確認できます。 [表示] リボンの [スタック] を選択します。
Microsoft が提供する wscpy_s() 関数にこのようなコード バグがある可能性は非常に低いので、スタックをさらに詳しく見てみましょう。 スタックには、そのあいさつ文が表示されます。メインはあいさつを呼び出します!GetCppConGreeting。 非常に小さなコード サンプルでは、この時点でコードを開くだけで、エラーが非常に簡単に見つかる可能性があります。 しかし、より大規模で複雑なプログラムで使用できる手法を説明するために、さらに調査する新しいブレークポイントを設定します。
GetCppConGreeting 関数のアクセス ブレークポイントの中断を設定する
[ブレークポイント] ウィンドウを使用して、既存のブレークポイントを右クリックして [削除] を選択することで、既存のブレークポイントをクリア します。
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)]
ba コマンドを使用して、メモリ アクセスにブレークポイントを設定します。 関数は実行のためにメモリから読み取られるだけなので、r - 読み取りブレークポイントを設定する必要があります。
0:000> ba r4 b61720
[ハードウェア読み取り] ブレークポイントが [ブレークポイント] ウィンドウでアクティブになっていることを確認します。
あいさつ文字列のサイズについて疑問に思っている場合は、sizeof(greeting) の値を表示するwatch ウィンドウを設定します。 [表示] リボンから [ ウォッチ ] を選択し、 sizeof(greeting) を指定します。
[タイム トラベル] メニューで、 タイム トラベルを使用してトレースを開始 するか、コマンドを
!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
[ホーム] メニューの [ 移動 ] を選択するか、 コマンドを
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
[ホーム] メニューの [ ステップ アウト] を選択するか、コマンドを
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)
根本原因が見つかったように見えます。 宣言した greeting 配列の長さは 50 文字ですが、GetCppConGreeting に渡す sizeof(greeting) は 100 0x64です。
サイズの問題をさらに見ると、メッセージの長さが 75 文字、文字列文字の末尾を含む 76 文字であることがわかります。
HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
コードを修正する方法の 1 つは、文字配列のサイズを 100 に拡張することです。
std::array <wchar_t, 100> greeting{};
また、このコード行で sizeof(greeting) を size(greeting) に変更する必要もあります。
GetCppConGreeting(greeting.data(), size(greeting));
これらの修正プログラムを検証するために、コードを再コンパイルし、エラーなしで実行されることを確認できます。
ソース ウィンドウを使用してブレークポイントを設定する
この調査を実行する別の方法は、任意のコード行をクリックしてブレークポイントを設定することです。 たとえば、ソース ウィンドウで std:array 定義行の右側をクリックすると、ブレークポイントが設定されます。
[タイム トラベル] メニューで、[ タイム トラベル] を使用して [開始 ] コマンドを使用してトレースの先頭に移動します。
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
[ホーム] リボンの [ 移動 ] をクリックして、ブレークポイントにヒットするまで戻ります。
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
応答メッセージ変数のアクセス ブレークポイントで中断を設定する
この調査を実行するもう 1 つの別の方法は、疑わしい変数にブレークポイントを設定し、変更しているコードを調べることです。 たとえば、GetCppConGreeting メソッドの greeting 変数にブレークポイントを設定するには、次の手順を使用します。
チュートリアルのこの部分では、前のセクションのブレークポイントにまだ配置されていることを前提としています。
[表示] と [ローカル] から。 ローカル ウィンドウでは、現在のコンテキストで greeting を使用できるため、メモリの場所を決定できます。
dx コマンドを使用して、グリーティング配列を調べます。
0:000> dx &greeting &greeting : 0xddf800 [Type: std::array<wchar_t,50> *] [+0x000] _Elems : "꽘棶檙瞝???" [Type: wchar_t [50]]
このトレースでは、 あいさつ は ddf800 のメモリ内にあります。
[ブレークポイント] ウィンドウを使用して、既存のブレークポイントを右クリックして [削除] を選択することで、既存のブレークポイントをクリア します。
書き込みアクセスを監視するメモリ アドレスを使用して 、ba コマンドでブレークポイントを設定します。
ba w4 ddf800
[タイム トラベル] メニューで、[ タイム トラベル] を使用して [開始 ] コマンドを使用してトレースの先頭に移動します。
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
[ホーム] メニューの [ 移動 ] を選択して、グリーティング配列のメモリ アクセスの最初のポイントに進みます。
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 コマンド。
dx コマンドを使用して、グリーティング配列を調べます。
0:000> dx &greeting &greeting : 0xddf800 [Type: std::array<wchar_t,50> *] [+0x000] _Elems : "꽘棶檙瞝???" [Type: wchar_t [50]]
このトレースでは、 あいさつ は ddf800 のメモリ内にあります。
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] ...
いずれかの出現箇所をクリックすると、そのメモリ アクセスの発生に関する詳細情報が表示されます。
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
[タイムトラベル]をクリックして、トレースをポイントインタイムに配置します。
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
トレースでの読み取り/書き込みメモリ アクセスの最後の出現に関心がある場合は、リスト内の最後の項目をクリックするか、 を追加します。Last() 関数を dx コマンドの最後まで実行します。
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
[Time Travel] をクリックすると、トレース内のその位置に移動し、このラボで前述した手法を使用して、その時点でのコードの実行をさらに確認できます。
TTD の詳細については、以下を参照してください。メモリ オブジェクトについては、「TTD」を参照してください 。Memory オブジェクト。
まとめ
この非常に小さなサンプルでは、数行のコードを見ることで問題が特定された可能性がありますが、大規模なプログラムでは、ここで示す手法を使用して、問題を見つけるのに必要な時間を短縮できます。
トレースが記録されると、トレースと再現の手順を共有でき、問題は任意の PC でオンデマンドで再現できます。