Time Travel Debuggingの知識マップ(知識グラフ)
Time Travel Debugging(TTD)は、プロセスの命令実行を丸ごと録画してあとから前後に再生できるWinDbgの機能で、録画は管理者権限を要する操作で、記録した命令実行は.runトレースファイルに保存され、WinDbgが再生用にインデックスファイル(.idx)を作って使います。録画は5〜20倍の速度低下とトレースの肥大化を招くため、TTD.exeのリングバッファ(-ring / -maxFile)・モジュール限定(-module)・手動録画(-recordmode Manual)・監視モード(-monitor)で録る範囲を構成し、リングバッファとモジュール限定は肥大化を、モジュール限定と手動録画はオーバーヘッドを軽減します。いつ起きるか分からない長期稼働の不具合にはリングバッファを、起動時や特定の起動でだけ出る断続的な不具合には監視モードを勧めます。再生ではトレース内の位置を単位に移動し、TTD.Eventsで例外の位置を確認し、TTD.Memoryとアクセスブレークポイント(ba)からの逆方向実行(g-)でデータ破壊を書いた命令まで遡り、TTD.Callsでハンドルリークの取得・解放やハングの待機呼び出しを確認できますが、TTD.Callsで正しい引数と戻り値を読むにはprivate symbolsを用意することを勧めます。64ビットのSOS拡張を組み合わせれば.NETアプリのマネージド状態も読めます。トレースはメモリ内容を含むため情報漏えいの原因になり得て、保護されたプロセス(PPL)は録画できず、Application Verifierを有効にしたままの録画は再生が遅くなるので勧めません。採取コストの低いダンプ解析を先に行い、経路が要ると分かった案件にTTDを出す順序を勧めます。
長時間運転後クラッシュの切り分け
アプリケーションのハング(応答なし)
Time Travel Debugging(TTD)
プロセスの命令実行を丸ごと録画し、あとから前にも後ろにも再生できるWinDbgの機能。ダンプが取り逃がす失敗までの実行経路を残す。
タイムトラベルデバッグ
WinDbg
クラッシュダンプやハングしたプロセスの状態をカーネル/ユーザーモードで解析するためのMicrosoft製デバッガー。SOS拡張によるマネージドヒープ解析や、Application Verifierの検出結果の解析にも使われる。
TTDの録画
対象プロセスに録画エンジンを注入し、実行された命令を命令単位で記録する操作と、その記録内容。管理者権限を要し、対象プロセスを大きく遅くする。
TTD録画
トレースの記録
管理者権限
コンピューター全体の設定を変更できる権限。
昇格
TTDトレースファイル(.run)
TTDの録画中の命令実行を保存するファイル。活動中は毎秒5〜50MB成長し、上限は無い。メモリ内容を含む。
トレースファイル
TTDインデックスファイル(.idx)
WinDbgがTTDトレースを効率よく再生・メモリ照会するための補助データ。トレースの1〜2倍の大きさで、.runを開くと自動生成される。
インデックスファイル
TTD.exe(コマンドライン録画ツール)
WinDbgを入れずにTTDトレースを録画するためのコマンドラインツール。launch・attach・monitorの3モードと、録画範囲を絞るオプションを持つ。
TTDの位置(position)
TTDトレース内の時刻を表す、シーケンシング番号とステップ数の16進数2組(xx:yy)。!ttやSeekTo()で移動し、ToSystemTime()でおおよその実時刻に変換できる。
トレース内の位置
TTD録画のオーバーヘッド
TTDで録画中の対象プロセスが被る性能影響。アプリと録画オプションによって5〜20倍以上遅くなる。
録画中の速度低下
TTDトレースの肥大化
TTDトレースが録画中に毎秒5〜50MB成長し、上限無くディスクを消費すること。ディスクが尽きると録画は静かに待ち、不完全なトレースになる。
トレースファイルの成長
TTDのリングバッファ録画(-ring / -maxFile)
TTD.exeのオプション。トレースを指定サイズのリングバッファに書き、その大きさに収まる録画の最後の部分だけを残す。
リングバッファ録画
TTDのモジュール限定録画(-module)
TTD.exeのオプション。指定したモジュールとそれが呼び出すコードだけを録画し、それ以外の実行はフルスピードで走らせる。
モジュール限定録画
TTDの手動録画(-recordmode Manual)
TTD.exeのオプション。プログラムがTTDのインプロセス録画APIを呼んだときだけ録画し、それまではフルスピードで走らせる。
インプロセス録画API
TTDの監視モード(-monitor)
TTD.exeのモード。指定したプログラムが起動するたびに、通常の権限のまま録画する。断続的な問題や起動時の問題を捕まえる用途。
監視モード
数日〜数週間の連続稼働後にだけ発生する障害を、資源の増え方と失敗経路の観測から原因究明する調査手法。
断続的・再現しない不具合
発生条件が分からず、手元で再現できない不具合。起動のたびや長期稼働中にまれに起きるものを含む。
再現しない不具合
間欠的な不具合
例外(exception)
プロセス内で発生したハードウェア例外(アクセス違反など)またはソフトウェア例外。アプリが後で処理したかどうかは問わない。
ハードウェア例外
ソフトウェア例外
SEH例外
TTD.Events(イベント一覧)
TTDトレース内のスレッド作成・終了、モジュールのロード・アンロード、例外をイベントとして列挙するデータモデルのコレクション。例外の位置へ移動する入口になる。
TTDのイベントオブジェクト
データ破壊(不正な値の書き込み)
変数や構造体が本来あり得ない値に書き換えられる不具合。ダンプには壊れた値だけが写り、書いた主体は写らない。
不正な値
値の破壊
メモリの上書き
TTD.Memoryクエリ
指定したアドレス範囲への読み・書き・実行アクセスをTTDトレース全体から集めるクエリ。その値を最後に書いた位置を特定できる。
TTDのMemoryオブジェクト
逆方向実行(g- / p- / t-)
TTDトレース上で時間を逆向きに進めるWinDbgのコマンド。g-はブレークポイントかイベントかトレース先頭まで、p-とt-は1命令ずつ戻る。
アクセスブレークポイント(ba)
指定したアドレスへの読み・書き・実行アクセスで止まるWinDbgのブレークポイント。TTDでは逆方向実行と組み合わせて、値を最後に書いた位置まで戻るのに使う。
メモリアクセスブレークポイント
private symbols
ローカル変数・型・引数などを含む、完全に近い内部情報を持つシンボル情報。
TTD.Callsクエリ
指定した関数の呼び出しをTTDトレース全体から集め、位置・スレッド・引数・戻り値・実時刻でLINQ風に絞り込み・集計できるクエリ。PDBのシンボル情報を前提とする。
TTDのCallsオブジェクト
ハンドルリーク
参照カウントやハンドルカウントの数え違いなどにより、不要になったハンドル(ファイル・イベント・スレッド・GDIオブジェクト・アクセストークンなどカーネルオブジェクトへの参照)が解放されずに積み上がっていく状態。
ウィンドウが応答なしになり、UIスレッドなどが何らかの待機から戻らない状態である。
ハング
応答なし
情報漏えい
本来外部に出るべきでない情報が組織の外へ流出すること。
保護されたプロセス(PPL)
Windowsが改変や注入から保護するプロセスの種類。TTDは自分を注入できないため録画できない。
保護プロセス
Application Verifier
Windowsのuser-modeアプリに対するランタイム検証ツールで、OS API利用や資源の扱い方を監視し誤用の検出や意図的な失敗の注入を行う。
ダンプ解析
クラッシュやハングなどで取得したプロセスのダンプファイルから、スタックや変数、型を人間が読める名前やソース行に変換して調べる作業。
SOS拡張
マネージドヒープの情報収集、ヒープ破損の検出、ランタイム内部データ型の表示など、CLRの内部をデバッガー上から読むためのデバッガー拡張である。
TTDはWinDbgに統合されており、トレースの再生(位置の移動、逆方向の実行、イベントとクエリ)はWinDbgで行う。録画もWinDbgのUIから行える。
TTDは、対象プロセスの命令実行を録画する操作と、その記録をWinDbgで前後に再生する操作から成る。録画はWinDbgのUIまたはTTD.exeで行う。
録画には昇格した権限が必要で、WinDbgは管理者として実行し、TTD.exeは管理者コマンドプロンプトから実行する。既存のトレースを開いて再生するだけなら昇格は要らない。
録画で記録した命令実行は.runトレースファイルに保存される。共有するのはこの.runだけで足りる。
WinDbgはトレースを効率よく再生・メモリ照会するために.idxインデックスファイルを使う。録画停止時に作られ、.runを開いたときにも自動生成され、!index -statusで状態を確認し!index -forceで作り直せる。
TTD.exeはWinDbgを入れないPCでの録画や自動化のためのコマンドライン録画ツールで、launch・attach・monitorの3モードでTTDの録画を行う。再生はWinDbgで行う。
トレース内の時刻はシーケンシング番号とステップ数の16進数2組で表す位置で扱い、!ttやSeekTo()で移動し、ToSystemTime()でおおよその実時刻に変換してログの時刻と突き合わせられる。
録画中の対象プロセスは、アプリと録画オプションによって5〜20倍以上遅くなる。
トレースは活動中に毎秒5〜50MB成長し、数分で数GBになり得て、最大サイズの上限は無い。ディスクが尽きると録画は静かに待ち、不完全なトレースになる。
-ringと-maxFileで、トレースを指定サイズのリングバッファに書き、録画の最後の部分だけを残すように構成できる。
-moduleで、指定したモジュールとそれが呼び出すコードだけを録画し、それ以外はフルスピードで走らせるように構成できる。
-recordmode Manualで、プログラムがインプロセス録画APIを呼んだときだけ録画するように構成できる。
-monitorで、指定したプログラムが起動するたびに通常の権限のまま録画するように構成でき、-cmdLineFilterでコマンドラインにより対象を絞れる。
リングバッファ録画ではファイルが-maxFileの上限を超えて成長せず、その大きさに収まる直近の録画だけが残る。
モジュール限定録画では指定モジュール外の実行がフルスピードで走るため、録画のオーバーヘッドが大きく減る。
モジュール限定録画では指定モジュール外の命令が記録されないため、トレースファイルのサイズが大きく減る。
手動録画ではアプリがAPIで録画を始めるまでフルスピードで走るため、異常の予兆がある区間だけ録れば録画のオーバーヘッドとトレースサイズを抑えられる。
いつ起きるか分からない長期稼働の不具合では、動いているプロセスにリングバッファで付き、監視が症状を検知したら-stopで録画を止めて直近の区間だけを残す設計を本記事は勧める。バッファは検知から停止までのラグを吸収できる大きさにし、停止後もTTDは外れないので再起動まで運用に含める。
起動直後や特定の起動でだけ出る断続的な不具合には、起動のたびに録画する監視モードを本記事は勧める。公式の使い分け表でも監視モードは断続的な問題や起動時の問題を捕まえる用途とされる。
再現しない・断続的な不具合でダンプが結果しか示さないとき、録画範囲を設計したうえでTTDで経路を残すことを本記事は勧める。録画のオーバーヘッドと肥大化があるため、無条件の常時録画は勧めない。
@$curprocess.TTD.EventsのExceptionイベントに例外の位置・種類(Software/Hardware)・コード・プログラムカウンターが入り、[Time Travel]リンクでその位置へ移動できる。イベントはアプリが後で処理した例外も含み、処理されたかどうかは区別しない。
TTD.Memoryで対象アドレス範囲への書き込みアクセスを集め、.Last()の位置へ移動すれば、その値を最後に書いた命令が分かる。
壊れた値のアドレスにアクセスブレークポイントを置いてg-で逆方向に実行すると、その変数が最後に書かれた位置で止まり、値が別の変数由来なら同じ手順を繰り返して壊した命令まで遡れる。
g-を止めるイベントは順方向のgと同じなので、baでアクセスブレークポイントを置いてからg-を打つ順序で、条件が最後に成立した位置まで一気に戻れる。
TTD.CallsはPDBのシンボル情報から引数の数と型・戻り値の型・呼び出し規約を決める。public symbolsだけでも関数名と既定の4つの64ビット整数引数で問い合わせでき、シンボルが無ければ関数名がUnknownOrMissingSymbolsになるが動く。正しい引数と戻り値を読むには、公式ドキュメントも本記事もprivate symbols(完全なPDB)を勧める。
自社DLLに限定して録ったトレースでCreateFileWとCloseHandleの呼び出しを集め、CreateFileWの戻り値(ハンドル値)とCloseHandleの第1引数(Parameters[0])を突き合わせると、閉じられていないハンドル値が残る。そのCreateFileW呼び出しのReturnAddressで漏らしている呼び出し元が分かる。リークの有無と量の観測は別の軽い道具が向く。
待機APIの呼び出しをTTD.Callsで集め、SystemTimeStart / SystemTimeEndと!positionsの各スレッドの位置から、どのスレッドがいつ何を待ち始めたかを並べて待ちの連鎖を組み立てられる。アイドル中はトレースが成長しないため、待ち時間そのものは命令として写らない。
トレースにはプロセスのメモリ内容が含まれ、ファイルパス・レジストリ・メモリやファイルの中身など個人情報や秘密情報が入り得るため、共有と保管を機密ファイルとして設計しないと情報漏えいにつながる。
Protected Process Light(PPL)などWindowsの保護プロセスにはTTDが自分を注入できず、録画できない。
Application Verifierを有効にしたまま録画すると、そのメモリの使い方の関係で再生時の性能が目立って悪くなるため、録画時は無効にする。
採取コストがほぼゼロのダンプとログで状態を掴み、経路が要ると分かった案件にだけ録画範囲を設計してTTDを出す順序を本記事は勧める。TTDのオーバーヘッドとトレースが機密情報を含む性質から、逆の順序を取る理由は無い。
64ビットモードで動くSOS拡張をWinDbgのTTDトレース上で使ってマネージドコードをデバッグでき、例外イベントの位置へ移動してから!clrstackや!peでマネージドの状態を読める。