ユーザーモードのプログラムで、クラッシュダンプでは原因がわからない問題を、問題が発生するまでの経緯をさかのぼって調べたいと思ったことはありませんか?それを実現してくれるのがTime Travel Debugging (TTD) です。TTD は、まだプレビューですが、WDK 1803 で TTD の使い方についてのLab が以下のドキュメントとして公開されました。
Time Travel Debugging - Sample App Walkthrough
そこで、今回は、このLab の手順をご案内します。これにより、読者の方のデバッグの作業効率が上がれば幸いです。
※ ただし、あくまでもプレビューですので、今後上記ドキュメントの内容は更新される可能性がある点にご注意ください。
※ メモリダンプがスナップショットであるのに対し、TTD で取得できるトレースログはその連続となるため、必要なメモリやディスクの容量が多くなります。そのため、再現手順が確立していない問題や再現までに時間がかかる問題に対してなど、長期間にわたる実行には向かない点にもご注意ください。
手順の概要
-
- 失敗するプログラムのTime Travel Trace をキャプチャします。
-
- dx (Display Debugger Object Model Expression) コマンドを使って、Time Travel Trace に記録された例外イベントを見つけます。
-
- !tt (time travel) コマンドを使って、そのトレースの中の例外イベントの位置まで移動(travel) します。
-
- トレースのその位置から、失敗したコードまでステップ実行でさかのぼります。
-
- 失敗したコードで、ローカル変数を確認し、誤った値を含む変数の仮説を立てます。
-
- 誤った値を持つ変数のメモリアドレスを確認します。
-
- 疑わしい変数のアドレスに、メモリアクセス時のブレークポイントをba (Break on Access) コマンドを使ってセットします。
-
- g- を使って、疑われる変数へのメモリアクセスの最後の位置までさかのぼります。
-
- その位置、またはそれ以前のいくつかの命令が、失敗したコードフローの場所であれば、完了です。もし誤った値がほかの変数からのものであれば、別の変数にメモリアクセス時のブレークポイントをセットします。
-
- g- を使って、疑われる変数へのメモリアクセスの最後の位置までさかのぼります。その位置、またはそれ以前のいくつかの命令が、失敗したコードフローの場所であれば、完了です。
-
- この手順を、エラーを引き起こした誤った値をセットするコードが見つかるまで繰り返します。
Lab が前提とする環境
-
- Windows 10 が動作するラップトップまたはデスクトップ PC (ホスト)
-
- WinDbg Preview (インストール手順は、WinDbg Preview – Installation をご参照ください。概要としては、Microsoft Store のアプリを開いて “WinDbg Preview” で検索するか、 のリンクから入手します。)
-
- サンプルC++ コードをビルドできる Visual Studio
今回は、例として、WinDbg を動作させるホストPC では Windows 10 1803 x64 が動作しています。Visual Studio 2017 を使用します。
-
- サンプルコードのビルド
ここの手順では、Visual Studio を使って、サンプルコードをビルドします。
(1) Visual Studio で[ファイル] – [新規作成] – [プロジェクト] をクリックし、[Visual C++] をクリックします。[Windows コンソール アプリケーション] を選択します。プロジェクト名を “DisplayGreeting” として、[OK] をクリックします。
(2) DisplayGreeting プロジェクトを右クリックしてプロパティを開きます。[C/C++] をクリックし、[SDL チェック] を [いいえ(/sdl-)] に変更します。
(3) [OK] をクリックします。
(4) DisplayGreeting.cpp に以下のテキストを貼ります。
// DisplayGreeting.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include
#include
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
GetCppConGreeting(greeting.data(), sizeof(greeting));
wprintf(L"%lsn", greeting.data());
return 0;
} |
(5) DisplayGreeting プロジェクトを右クリックしてプロパティを開きます。[C/C++] –[コード生成] をクリックし、[基本ランタイム チェック] を[既定]、[セキュリティ チェック] を[セキュリティ チェックを無効にします(/GS-)] に変更します。[OK] をクリックします。
※上記の設定は推奨されませんが、今回のLab のために、誰かがコーディングの簡略化や特定のテスト環境構築のために、そのような設定を行った、というシナリオを想定しているものとします。
(6) メニューの[ビルド] –[ソリューションのビルド] をクリックします。以下のように正常終了します。
1>------ ビルド開始: プロジェクト: DisplayGreeting, 構成: Debug Win32 ------
1>cl : コマンド ラインwarning D9025: '/sdl-' より'/GS-' が優先されます。
1>stdafx.cpp
1>cl : コマンド ラインwarning D9025: '/sdl-' より'/GS-' が優先されます。
1>DisplayGreeting.cpp
1>DisplayGreeting.vcxproj -> C:BlogTTDDisplayGreetingDebugDisplayGreeting.exe
1>プロジェクト"DisplayGreeting.vcxproj" のビルドが終了しました。
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ========== |
(7) ビルドされたサンプルアプリの場所を確認します。[ソリューション エクスプローラー] で、DisplayGreeting プロジェクトを右クリックして、[エクスプローラーでフォルダーを開く]
をクリックします。例えば、C:BlogTTDDisplayGreetingDisplayGreeting というフォルダが開くので、一階層上に上がり、Debug フォルダ(C:BlogTTDDisplayGreetingDebug)
を開くと、DisplayGreeting.exe と、そのシンボルファイルDisplayGreeting.pdb ファイルがあるのを確認できます。
(8) DisplayGreeting.exe をダブルクリックして、サンプルアプリを実行します。
少し経つとこのウィンドウが閉じますが、実は例外が発生しています。実際、イベントビューアーのアプリケーションログを見ると、以下のようなエラーになっています。
以降の手順では、サンプルアプリの実行を記録し、この例外がなぜ発生したのか見ていきます。
-
- “DisplayGreeting” サンプルのトレースの記録
ここの手順では、誤った動作を行うDisplayGreeting アプリの TTD トレースログを記録します。
サンプルアプリを起動し、TTD トレースを記録するには、以下の手順を行います。一般的な手順は、Time Travel Debugging - Record a trace のドキュメントをご参照ください。
(1) WinDbg Preview を管理者権限で実行し、Time Travel トレースが取得できるようにします。
(2) WinDbg Preview 上で、[ファイル] –[Start Debugging] –[Launch executable (advanced)] を選択します。
(3) [Executable:] に、記録したい、ユーザーモード実行ファイルへのパスを入力するか、もしくは、[Browse] ボタンで実行ファイルを選択します。[Start directory:] にそのexe のあるフォルダへのパスを入力します。WinDbg Preview の launch executable メニューの詳細は、WinDbg Preview - Start a user mode session のドキュメントをご参照ください。
(4) [Record process with Time Travel Debugging] のチェックボックスにチェックを入れ、実行ファイルの起動時にトレースを記録できるようにします。Output directory には、トレースを出力するフォルダへのパスを入力します。
(5) [OK] をクリックすると、実行ファイルが起動し、記録が開始されます。
(6) トレースが記録されることを示すダイアログが表示され、しばらく後に、アプリケーションがクラッシュします。
(7) トレース ファイルがディスクに書き込まれます。今回の場合、C:BlogTTDDisplayGreeting01.run がトレース ファイルです。
(8) デバッガは自動的にトレースファイルを開き、インデックスを作成します。インデックスは、トレースファイルの効率的なデバッグのために行われます。トレースファイルが大きいほど、インデックスには時間がかかります。
0:000> !index
Indexed 2/2 keyframes
Successfully created the index in 86ms. |
Keyframe とは、インデックスに使われるトレース内の位置です。Keyframe は自動的に生成されます。大きなトレースファイルほど、多いKeyframe を含みます。
(9) この時点で、トレースファイルの最初にいることになります。時間を進めたり、さかのぼったりすることができます。
TTD トレースがあれば、トレースをリプレイしたり、トレースファイルを他の人と共有して協業することもできます。詳細は Time Travel Debugging - Working with Trace Files のドキュメントをご参照ください。
以降の手順では、トレースファイルを解析し、サンプルコードの問題を特定していきます。
-
- コードの問題特定のためのトレースファイルの解析
ここの手順では、コードの問題を特定するためにトレースファイルを解析します。
WinDbg 環境の設定
(1) サンプルアプリのシンボルの場所をシンボルパスに追加し、シンボルをリロードします。
0:000> .sympath+ C:BlogTTDDisplayGreetingDebug
Symbol search path is: srv*;C:BlogTTDDisplayGreetingDebug
Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols;c:blogttddisplaygreetingdebug
************* Path validation summary **************
Response Time (ms) Location
Deferred srv*
OK C:BlogTTDDisplayGreetingDebug
0:000> .reload
.........
|
(2) サンプルアプリのソースコードの場所をソースパスに追加します。
0:000> .srcpath+ C:BlogTTDDisplayGreetingDisplayGreeting
Source search path is: C:BlogTTDDisplayGreetingDisplayGreeting
************* Path validation summary **************
Response Time (ms) Location
OK C:BlogTTDDisplayGreetingDisplayGreeting |
(3) スタックやローカル変数の状態を表示できるようにするために、WinDbg Preview のリボンで、[View] タブの[Locals] と[Stack] をクリックします。ソースを表示する際に、これらのウィンドウの配置を調整して、ソースコードとCommand のウィンドウも同時に表示できるようにします。
(4) WinDbg Preview のリボンで、[Source] タブから[Open Source File] をクリックし、DisplayGreeting.cpp を開きます。
例外の確認
(1) トレースフ
This post first appeared on MSDN Blogs | Get The Latest Information, Insights, Announcements, And News From Microsoft Experts And Developers In The MSDN Blogs., please read the originial post: here