Time Travel Debugging ── 長期稼働で再現しない不具合を「録画」して巻き戻す

· · WinDbg, Time Travel Debugging, デバッグ, 不具合調査, Windows, Windows開発, .NET, C++, 技術相談

更新履歴(初版のみ・2026年09月02日公開)
初版公開
この記事を引用する(DOI: 10.5281/zenodo.22249657)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「Time Travel Debugging ── 長期稼働で再現しない不具合を「録画」して巻き戻す」合同会社小村ソフト. https://doi.org/10.5281/zenodo.22249657 https://comcomponent.com/blog/windows-time-travel-debugging-ttd/

DOI(最新版)
10.5281/zenodo.22249657
DOI(この版)
10.5281/zenodo.22249658

「月に一度、深夜にだけ落ちる」「同じ操作をしても手元では二度と出ない」「ダンプは取れたが、落ちた場所を見ても『なぜその値になったか』が分からない」── 長期稼働するWindowsアプリの不具合調査で、いちばん時間を溶かすのはこの種の案件です。以前の記事「WinDbg + SOSでクラッシュダンプを読む」では、クラッシュダンプという1枚の写真をどう読むかを整理しました。この記事はその続きで、写真では足りない場面のための道具、Time Travel Debugging(TTD) を扱います。

TTDは、プロセスの実行を丸ごと録画し、あとから前にも後ろにも再生できるWinDbgの機能です。不具合が出るまで何度も再現を試みる代わりに、デバッガーのセッションを「巻き戻す」ことができます。1 対象読者は、ダンプとログを読む調査はひととおり経験していて、それでも原因に届かない案件を抱えているWindows / .NET / C++アプリの開発・保守担当です。前提環境はWindows 10/11またはWindows Server 2016以降と現行のWinDbgで、録画には管理者権限が要ります。12 難易度は中級です。

この記事の前提

項目 内容
対象読者 ダンプとログでは原因に届かない、長期稼働・断続的な不具合を抱えるWindowsアプリの開発・保守担当
前提知識 WinDbgでダンプを開いて !analyze -v!clrstack を打った経験。SOS解析記事の内容は既知とします
前提環境 Windows 10/11 または Windows Server 2016/2019/2022/2025、WinDbg(現行版)、TTD.exe、管理者権限2
扱わないこと Visual Studio EnterpriseのSnapshot Debugger連携、カーネルモード(TTDはユーザーモード専用3)

1. まず結論

  • ダンプは「状態」、TTDは「経路」を残します。公式ドキュメントも、ダンプは失敗に至った状態と実行経路を取り逃がしがちだと明記しています。1 落ちた瞬間の写真で原因が分からないなら、次に取るべきは写真の枚数ではなく録画です。
  • 録画は重い。録画中の対象プロセスは5〜20倍以上遅くなり、トレースファイルは活動中に毎秒5〜50MB成長し、上限はありません。24 長期稼働のアプリに無条件で貼り付ける道具ではありません。
  • 長期稼働に使うなら「録る範囲」を設計します。TTD.exeの -ring/-maxFile(最後のNMBだけ残す)、-module(自社モジュールの実行中だけ録る)、-recordmode Manual(アプリ側から録画区間を指定する)、-monitor(起動のたびに録る)の4つの入口を第5章で整理します。2
  • 再生の主役は3つ。位置(!tt)、イベント(dx @$curprocess.TTD.Events)、クエリ(TTD.Calls / TTD.Memory)です。ba(アクセスブレークポイント)と g-(逆方向実行)を組み合わせると、「その値を最後に書いたのは誰か」をデバッガーに直接答えさせられます。5
  • トレースにはメモリの中身が入ります。ファイルパス、レジストリ、メモリやファイルの内容など、個人情報・秘密情報を含み得ます。1 共有と保管は「機密ファイル」の扱いとして設計してください。
  • TTDにできないことも先に押さえます。カーネルモードは録れず、保護されたプロセス(PPL)には注入できず、一度アタッチすると自分では外れず、再生中はメモリを書き換えられません。32

この記事の知識マップ

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の知識マップTime Travel Debugging(TTD)がWinDbgを前提とし、管理者権限を要する録画で命令実行を.runトレースに記録し、TTD.exeがリングバッファ・モジュール限定・手動録画・監視モードで録画範囲を構成すること、録画のオーバーヘッドとトレースの肥大化をそれらが軽減すること、再生ではTTD.Events・TTD.Calls・TTD.Memoryとアクセスブレークポイント・逆方向実行で例外やデータ破壊・ハンドルリーク・ハングを確認できること、トレースが情報漏えいの原因になり得ること、PPLと両立しないこと、Application Verifier併用が非推奨なこと、ダンプ解析を先に行うべきことを示した図前提とする利用する前提とするに保存される利用する実装を担う利用する原因になり得る原因になり得るで構成できるで構成できるで構成できるで構成できる軽減する軽減する軽減する軽減する推奨される対応推奨される対応推奨される対応で確認できるで確認できるで確認できるより先に行うべき推奨される対応で確認できるで確認できる原因になり得る両立しない用いるのは非推奨より先に行うべき利用するTime Travel Debugging(TTD)WinDbgTTDの録画管理者権限TTDトレースファイル(.run)TTDインデックスファイル(.idx)TTD.exe(コマンドライン録画ツール)TTDの位置(position)TTD録画のオーバーヘッドTTDトレースの肥大化TTDのリングバッファ録画(-ring / -maxFile)TTDのモジュール限定録画(-module)TTDの手動録画(-recordmode Manual)TTDの監視モード(-monitor)長時間運転後クラッシュの切り分け断続的・再現しない不具合例外(exception)TTD.Events(イベント一覧)データ破壊(不正な値の書き込み)TTD.Memoryクエリ逆方向実行(g- / p- / t-)アクセスブレークポイント(ba)private symbolsTTD.Callsクエリハンドルリークアプリケーションのハング(応答なし)情報漏えい保護されたプロセス(PPL)Application Verifierダンプ解析SOS拡張

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全32件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. ダンプでは足りない場面

クラッシュダンプは、プロセスが落ちた(または止めた)瞬間のメモリとレジスターの写しです。SOS解析記事で書いたとおり、!clrstack でどこで落ちたか、!dumpheap -stat で何がヒープを食っているかは読めます。読めないのは、その状態に至るまでに何が起きたかです。

ダンプが写すものとTTDが写すものクラッシュダンプは落ちた瞬間の状態だけを写し、それ以前の経路は写らない。TTDは録画開始から終了までの命令実行を丸ごと残すので、状態に加えて経路が残るクラッシュダンプ:落ちた瞬間の状態なぜその値になったかは写らないTTDトレース:録画区間の命令実行値が書き換わった位置まで戻れるこの隙間が長期稼働の調査を長引かせる

図1: ダンプは状態、TTDは経路。長期稼働の不具合で欲しいのは、たいてい後者です。

典型的なのは次のような案件です。

  • ヒープ上の構造体の1フィールドがあり得ない値になっている。ダンプには壊れた値は写っていますが、誰がいつ書いたかは残っていません。
  • 例外の発生位置は分かるが、渡された引数がなぜ不正なのか分からない。呼び出し元をさらに遡ると、途中で値を作った関数はもうスタックにいません。
  • ハンドルやメモリが1か月かけて増える。ダンプ1枚では「増えている」までしか言えず、増やした呼び出し経路は写りません。
長期稼働の典型案件でダンプに写らないもの壊れた値は写るが書いた主体は写らず、例外の位置は写るが引数を作った関数はスタックにおらず、資源の増加は写るが増やした経路は写らない、という3種類の案件の共通点壊れたフィールド誰がいつ書いたかは無い不正な引数での例外値を作った関数はもうスタックに無い1か月かけて増える資源増やした呼び出し経路は無い共通点:状態はあるが経路が無い

図2: 3つの案件はどれも「状態はあるが経路が無い」。写真を増やしても経路は埋まらない。

Microsoftのドキュメントは、調査手段の長所と短所を次のように整理しています。1

手段 長所 短所
ライブデバッグ 対話的で、実行の流れが見え、状態を変えられる 利用者の作業を止める。再現を繰り返す手間。本番では使えないことが多い。失敗地点から原因へ遡るのが難しい
ダンプ 事前のコード変更が不要。侵襲性が低く、トリガーで採れる。使わなければオーバーヘッドはほぼゼロ 連続スナップショットでも「時間経過」の見え方は粗い
テレメトリ・ログ 軽量。業務シナリオと結びつく 想定外のコードパスにはログが無い。データの深さが足りず、コードに静的に埋め込まれる
TTD 複雑なバグに強い。事前のコード変更が不要。オフラインで何度でも再生でき、すべてを記録する 録画時のオーバーヘッドが大きい。必要以上のデータを集めることがある。ファイルが大きくなる

もうひとつ、TTDの公式ウォークスルーが指摘している重要な性質があります。失敗地点でデバッガーが止まったとき、そこは本当の原因から数ステップ進んだエラー処理コードの中であることが多い。5 ダンプは必ずこの「数ステップ後」の位置で撮られます。TTDなら、そこから1命令ずつ戻れます。

失敗地点と本当の原因のずれダンプが撮られる失敗地点は本当の原因から数ステップ進んだエラー処理の中であることが多く、TTDではその地点から命令単位で巻き戻して原因へ遡れるダンプTTD本当の原因(値を壊した命令)数ステップ進む失敗地点(例外・エラー処理)ここで固定されるp- / t- / g- で戻る

図3: ダンプは失敗地点に固定される。TTDは失敗地点から原因まで歩いて戻れる。

3. TTDの仕組みと代償

3.1 何を録っているのか

TTDは、対象プロセスに録画エンジンを注入し、実行された命令を命令単位で記録します。公式ドキュメントの表現では「完全な命令レベルのトレースを平均1命令あたり1バイト未満に符号化する」もので、実際には1命令あたり1ビットから1バイトの範囲に収まります。実行する関数の種類が少なく扱うデータも少ないプログラムほど小さく、逆なら大きくなります。24

録画の成果物は2つです。1

ファイル 役割 大きさの目安
.run トレース本体。録画中の命令実行を保存する 活動中は毎秒5〜50MB成長。アイドル中は成長しない4
.idx インデックス。WinDbgが効率よく再生・メモリ照会するための補助データ。録画停止時に作られ、WinDbgが .run を開いたときにも自動生成される トレースの1〜2倍4
TTDの録画から再生までの流れ対象プロセスに録画エンジンが注入されて命令実行が.runファイルに記録され、WinDbgが.runを開くとインデックス.idxを作り、位置・イベント・クエリで前後に再生する対象プロセス録画エンジンの注入(TTDRecordCPU).run(命令実行の記録)WinDbgで開く.idx(インデックス)を生成位置・イベント・クエリで前後に再生

図4: 録るのはTTD.exeまたはWinDbg、読むのはWinDbg。共有するのは .run だけで足りる。

.run の中の時刻は「位置(position)」で表します。12:01A0:12F のように16進数2つをコロンで区切った形で、前半がシーケンシング番号(シーケンシングイベントに対応)、後半がそのイベントからのおおよその命令数です。6 FFFFFFFFFFFFFFFE:0 はトレースの末尾を意味します。7 位置は第6章で主役になります。

トレース内の位置の表し方位置は16進数のシーケンシング番号とステップ数をコロンで区切った形で、先頭が0付近、末尾はFFFFFFFFFFFFFFFE:0で表され、パーセント指定でおおよその位置へも移動できる位置 xx:yy(16進数)xx:シーケンシング番号yy:そのイベントからの命令数末尾は FFFFFFFFFFFFFFFE:0!tt 50 のようなパーセント指定も可

図5: 位置は「イベント番号:命令数」。実時刻ではないが、第6章のとおり実時刻へ変換できる。

3.2 代償

TTDは「侵襲的な技術」だと公式ドキュメント自身が書いています。2 代償を数字で並べます。

項目 内容
速度 録画中の対象プロセスは5〜20倍以上遅くなる(アプリと録画オプションによる)。UIで気づかない場合もあるが、ファイルを開くダイアログのような重い処理では体感できる23
ファイル成長 活動中は毎秒5〜50MB。数分の録画で数GBになり得る。上限は設定されていない4
ディスク切れ 録画中にディスクが尽きると、TTDは最後のページを書いてから書けるようになるまで実質的に待つ。WinDbgは録画ダイアログを出したまま、エラーも警告も出さない。できあがるのは不完全なトレース4
メモリ 録画は対象プロセスのメモリに仮想CPU分のオーバーヘッドを載せる(既定でx64/ARM64は55、x86は32)。メモリ不足のときだけ -numVCpu で減らす2
外れない 一度アタッチすると、TTDは自分では外れない。録画を終えたらアプリを閉じるかプロセスを終了する。システムに不可欠なプロセスならOSの再起動が要る2
録画の代償と、それが長期稼働に与える影響録画中の速度低下、ファイルの成長、ディスク切れ時の静かな待ち、アタッチ後に外れないという4つの代償が、長期稼働アプリに無条件で貼り付けられない理由になるTTDの録画5〜20倍の速度低下毎秒5〜50MBの成長・上限なしディスク切れは静かに待つアタッチ後は外れない無条件の常時録画は不可

図6: 4つの代償のどれか1つでも、常時録画を成立させない。だから第5章の「録る範囲の設計」が要る。

3.3 できないこと

  • ユーザーモード専用。録れるのはプロセスのユーザーモードの実行だけで、ドライバーなどカーネルモードで実行されるコードはデバッグできません。3
  • 保護されたプロセス。Protected Process Light(PPL)などWindowsの保護プロセスには、TTDが自分を注入できません。3
  • 再生は読み取り専用。過去には戻れますが、歴史は変えられません。メモリを読むコマンドは使えますが、書き換えるコマンドは使えません。3
  • アンチウイルス・メモリ監視系ソフトとの非互換。TTDがプロセスにフックする方式のため、システムメモリ呼び出しを追跡・シャドウするソフトと衝突します。録画時に権限不足のようなエラーが出るなら、一時的に無効化して切り分けます。Electronフレームワークも既知の衝突例で、録れたとしても対象プロセスのデッドロックやクラッシュがあり得ます。3
  • UWPアプリは起動して録画できない(すでに動いているUWPアプリへのアタッチは可能)。別セッション・別セキュリティコンテキストで動く「通常でないプロセス」も現時点では対象外です。8
TTDで録れないもの・できないことカーネルモードのコード、保護されたプロセス、UWPアプリの起動録画、別セッションや別セキュリティコンテキストのプロセスは録れず、再生中はメモリを書き換えられず、アンチウイルスやElectronとは衝突し得るTTDの制約録れないもの制約・衝突カーネルモードのコード(ドライバー等)保護されたプロセス(PPL)UWPの起動録画(アタッチは可)別セッション・別コンテキスト再生は読み取り専用アンチウイルス・Electronと衝突し得る

図7: 制約は「録れない」と「衝突する」の2種類。後者は環境ごとに切り分けが要る。

最後の項目は、サービスを録りたい人には引っかかります。TTD.exeのドキュメントは -attach を「サービスや長時間稼働するアプリの調査」向け、-monitor を「プログラムやサービスが起動するたびに録画」する用途として説明しており2、トラブルシューティングのページの記述と単純には噛み合いません。実務では、本番と同じ構成で ping.execmd.exe を録れることを確認し、次に対象プロセスで試すという順で、環境ごとに確かめるのが安全です。8

4. 録画する ── WinDbg UIとTTD.exe

録画の入口は2つあります。WinDbgのUIから録るか、コマンドラインの TTD.exe で録るかです。

4.1 WinDbg UIから録る

WinDbgを管理者として実行し(TTDには昇格が必須です1)、File > Start debugging > Launch executable (advanced) で実行ファイルを指定して Record with Time Travel Debugging にチェックを入れます。Configure and Record を選ぶと、トレースファイルの保存先と、Record subset of execution(録画するモジュールを notepad.exe,kernelbase.dll のようにカンマ区切りで限定する)を設定できます。すでに動いているプロセスなら File > Start debugging > Attach to process で同様に Record Process with Time Travel Debugging をチェックします。9

録画中は「Stop and Debug」と「Cancel」のボタンを持つ小さなダイアログが出ます。アプリが終了(またはクラッシュ)するとトレースが閉じられ、WinDbgが自動で開いてインデックスを作ります。5

WinDbg UIからの録画の流れ管理者として起動したWinDbgでLaunch executable (advanced)またはAttach to processを選び、Record with Time Travel Debuggingにチェックを入れ、Configure and Recordで保存先とモジュール限定を設定し、録画中ダイアログを経てアプリ終了時にトレースが閉じられ自動でインデックスが作られるWinDbgを管理者として起動Launch executable(advanced)/ Attach to processRecord with Time Travel Debugging にチェックConfigure and Record:保存先・モジュール限定録画中ダイアログ(Stop and Debug)アプリ終了でトレースを閉じ、自動でインデックス

図8: UIからの録画は5手順。「管理者として起動」を飛ばすと最初のダイアログで止まる。

4.2 TTD.exeで録る

WinDbgを入れられないPCで録る、録画を自動化する、といった場面では TTD.exe 単体を使います。https://aka.ms/ttd/download からApp Installer経由で入り、インストール後は ttd.exe -help で確認します。オフライン環境向けには、MSIXバンドルを手で展開してバイナリだけを取り出す手順(PowerShellスクリプト付き)も公式に用意されています。2 録画には管理者権限が要り、通常は管理者コマンドプロンプトから実行します。2

録画のモードは3つです。2

TTD.exeの3つの録画モードlaunchは引数を渡して新しいプロセスを起動して録画するが昇格した権限で走る。attachは動いているプロセスにPIDで付く。monitorは指定プログラムが起動するたびに録画し、起動が正常な権限で行われるTTD.exe の録画モード-launch:起動して録る-attach:動いているPIDに付く-monitor:起動のたびに録る管理者権限で起動される通常の権限のまま通常の起動経路・自動化向き

図9: 引数を渡せるのは -launch だけだが、昇格した権限で走る。本番に近い挙動で録るなら -attach-monitor

:: 起動して録る(既定モード。-launch は省略可)
TTD.exe -out C:\traces MyApp.exe --config prod.json

:: 動いているプロセスに付く(出力先ディレクトリは先に作っておく)
TTD.exe -attach 21440 -out C:\traces\MyApp.run

:: 起動のたびに録る(Ctrl+C で監視終了。-out にフルパスが必須)
TTD.exe -out C:\traces\ -monitor MyApp.exe
  • -launch は引数を渡せる唯一のモードですが、プログラムはTTD.exeと同じ(管理者の)権限で起動されます。権限の違いで挙動が変わるアプリでは、-attach-monitor で通常の権限のまま録ります。2
  • -attach は出力先ディレクトリが存在している必要があります。ファイル名を指定する場合、その名前のファイルが存在していてはいけません。2
  • -monitor はプロセス起動監視ドライバーを入れ、指定したプログラム(複数指定可)が起動するたびに録画します。再起動まで有効で、Ctrl+Cで止めます。起動を自分で組み立てなくてよく、対象は通常の権限で動き、スクリプトによる自動化に向くのが利点です。-cmdLineFilter "文字列" を付けると、コマンドラインにその文字列を含む起動だけを録ります。2
  • -children を付けると子プロセスも録りますが、プロセスごとに別の .run になり、WinDbgは一度に1つしか開けません。2

録画中は「Tracing Off」(録画を止めてアプリは続行)と「Exit App」(アプリを閉じて録画終了)の2ボタンの小さなUIが出ます。自動化では -noUI で消し、EULAは -accepteula で受諾します。2 録画のログは .run と同じ場所の .out ファイルに残り、録画開始・終了の実時刻、録画セッションの長さ(simulation time)、起動かアタッチか、OSバージョンが読めます。録画がうまくいかないとき、.out にしか出ないエラーメッセージがあります。2

5. 長期稼働向けの録画設計

ここが本記事の本丸です。第3章の代償を前提にすると、「いつ起きるか分からない不具合を、何日も動くプロセスで捕まえる」には録画範囲の設計が要ります。TTD.exeが用意している手段は4つで、症状の性質で選びます。

長期稼働の症状に応じた録画範囲の選び方いつ起きるか分からないならリングバッファで最後の部分だけ残し、疑わしいモジュールが分かっているならそのモジュールだけ録り、アプリを改修できるなら手動録画APIで区間を指定し、起動時や特定の起動でだけ出るなら監視モードで起動のたびに録るいつ起きるか不明起動時・特定の起動だけ疑わしいモジュールが明確アプリを改修できる症状の性質は?-ring / -maxFile:最後だけ残す-monitor:起動のたびに録る-module を重ねる-recordmode Manual で区間指定

図10: 4つは排他ではない。-ring-module を重ねるのが、長期稼働では最も現実的な組み合わせになる。

5.1 リングバッファ ── 最後のNMBだけ残す

-ring を付けると、トレースは -maxFile で指定した大きさのリングバッファに書かれ、ファイルはその上限を超えて成長しません。残るのは、その大きさに収まる録画の最後の部分だけです。2 -maxFile の単位はMBで、リングバッファモードの既定は2,048MB、最小1MB、最大32,768MBです(32ビットプロセスのインメモリリングの既定は256MB)。2

:: 動いている監視アプリに付き、直近4GB分の実行だけを残す
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run

:: 症状を検知したら、録画を止める(アプリは走り続ける)
TTD.exe -stop 21440

-stop はプロセス名・PID・all を受け取り、その録画を止めます。-wait <秒> はシステム上のすべての録画セッションが終わるまで待ち(-1 で無期限)、自動化スクリプトで「止めてからファイルを回収する」順序を作るのに使います。2

リングバッファ録画のタイムラインアタッチして録画を始め、古い部分はリングバッファから押し出されていき、症状が出た時点でstopすると、直近のmaxFile分だけがトレースとして残る-attach -ring で録画開始古い区間は押し出される症状が出る-stop で録画停止直近の maxFile 分だけ残る症状の直前がバッファに収まる大きさに

図11: リングバッファは「症状の直前」を残す装置。-maxFile は「症状に気づいて止めるまでの時間 × 毎秒の成長量」から逆算する。

設計上の要点は2つです。

  1. バッファは「気づいて止めるまで」を吸収できる大きさにする。アクティブなプロセスで毎秒5〜50MB成長する4ので、4GBのリングは高負荷時で1〜2分、低負荷時で10分強に相当します。症状の検知(ログの特定行、カウンターの閾値、監視の異常通知)から -stop までのラグを、この時間に収める仕組みが必要です。
  2. 止めても外れない。-stop で録画は止まりますが、TTDは対象プロセスから自分では外れません。2 録画を完全にやめるにはプロセスの終了が必要なので、「トレースを回収したら次のメンテナンス時刻に再起動する」ところまで運用に含めておきます。
監視と連動したリングバッファ録画の停止監視側がログやカウンターで症状を検知したらTTD.exeのstopを呼び、確定した.runファイルを回収し、その後のメンテナンス時刻にプロセスを再起動してTTDを外す対象プロセスTTD.exe監視(ログ・カウンター)対象プロセスTTD.exe監視(ログ・カウンター)症状を検知-stop PID録画を停止(プロセスは継続).run が確定.run を回収・暗号化して保管次回メンテナンス時刻に再起動

図12: 検知と -stop のラグがバッファに収まって初めて、症状の直前がトレースに残る。再起動までが運用の一部。

5.2 モジュール限定 ── 自社コードが動いている間だけ録る

-module <モジュール名> は、指定したモジュール(実行ファイル自身でも読み込まれるDLLでもよく、複数指定可)と、そのモジュールが呼び出すコードだけを録画します。対象プロセスは指定モジュールのコードが実行されるまでフルスピードで走り、モジュールに入ると録画が始まり、モジュールから出ると録画が止まってまたフルスピードに戻ります。録画のオン・オフは高コストなので、指定モジュールがプロセス内の他モジュールを呼び出している間は録画をオンのまま保ちます。2

:: 自社の計測ロジックDLLが動いている間だけ録る(リングと併用)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\

この方式のトレースは、録画が止まっていた区間を「次の命令は録画再開後の最初の命令」として飛ばすだけで、デバッグの仕方は全体を録ったトレースと変わりません。2 長期稼働のアプリでは、UIのアイドルループやフレームワークの内部処理が実行命令の大半を占めがちなので、自社モジュールに限定するだけでオーバーヘッドとファイルサイズは大きく減ります。

モジュール限定録画の動き対象プロセスは指定モジュール外ではフルスピードで走り、指定モジュールのコードに入ると録画が始まり、そのモジュールが呼ぶ他モジュールの間も録画が続き、モジュールから出ると録画が止まる指定モジュール外:フルスピード指定モジュールに入る:録画開始呼び出し先の他モジュール:録画継続指定モジュールから出る:録画停止

図13: 自社DLLが呼ぶWin32 APIやランタイムの中まで録れるので、「自社コードがOSに何を渡したか」を追うには十分。

5.3 手動録画 ── アプリ側から区間を指定する

-recordmode Manual を指定すると、TTDが注入された後もプロセスはフルスピードで走り、プログラムがTTDのインプロセス録画APIを呼んだときだけ録画します(既定の Automatic は注入直後から録画)。2 APIのドキュメントとサンプルはGitHubのWinDbg-Samplesリポジトリにあります。10

アプリを改修できるなら、これが最も無駄の少ない方式です。「通信のリトライが3回連続で失敗した」「キューの滞留が閾値を超えた」のように、アプリ自身が異常の予兆を知っている場面で録画を始め、正常に戻ったら止める、という組み込みができます。Job Objectの記事で扱ったような監視プロセス構成なら、監視側の判断でこの区間を決めることもできます。

5.4 監視モード ── 起動のたびに録る

起動直後や特定の起動でだけ出る不具合には -monitor が向きます。公式の使い分け表でも、監視モードは「断続的な問題や起動時の問題を捕まえる」用途とされています。2 同じプログラムを何度も録る場合は、既定の連番ファイル名(MyApp01.runMyApp02.run……)が既存ファイルの走査で非効率になるので、-timestampFilename でタイムスタンプ付きの名前にします。同時に走る録画の数は -maxConcurrentRecordings で抑えられます。2

監視モードの動きmonitorオプションはプロセス起動監視ドライバーを入れ、指定プログラムが起動するたびにコマンドラインフィルターで対象を絞って録画し、起動ごとに別のトレースファイルを作り、Ctrl+Cまたは再起動まで続くはいいいえ起動監視ドライバーを導入指定プログラムの起動を検知-cmdLineFilter に一致?その起動を録画(起動ごとに別ファイル)録画しない次の起動を待つ(Ctrl+C か再起動まで)

図14: 監視モードは「起動を待ち伏せる」方式。起動のたびに出る不具合と、起動条件で絞れる不具合に向く。

5.5 ディスクの置き場

長期稼働の録画では、トレースの置き場を専用ボリュームにし、.run の成長を監視に入れておきます。第3.2節のとおり、ディスクが尽きても録画は静かに待つだけで、エラーは出ません。公式の回避策も「エクスプローラーで空き容量を見る」「.run が定期的に成長しているかを見る」という原始的なものです。4 空き容量の監視をしていないと、いちばん欲しい瞬間が書かれていない不完全なトレースを手にすることになります。

ディスク切れが不完全なトレースを生む経路録画中にディスクが尽きるとTTDは最後のページを書いて静かに待ち、エラーも警告も出ないため、その後に起きた症状は記録されず、開けるが肝心の部分が無い不完全なトレースになる。専用ボリュームと.runの成長監視で防ぐ防ぐディスクが尽きる最後のページを書いて静かに待つエラーも警告も出ないその後に症状が出る症状の無い不完全なトレース専用ボリューム+.run成長の監視

図15: 「開けるのに肝心の部分が無い」トレースは、ディスク監視の欠落から生まれる。

6. 再生する ── 位置・イベント・巻き戻し

6.1 開く

.run をWinDbgで開くと、インデックスが無ければ自動で !index が走り、keyframe(インデックス用に自動生成されるトレース内の位置。大きいトレースほど多い)を数えながら .idx を作ります。大きいトレースほど時間がかかります。5 インデックスの状態は !index -status で確認でき、「Index file loaded」以外なら !index -force で作り直します。それでも駄目なら、デバッガーを閉じて .idx を削除し、.run を開き直します。インデックスの再作成は .run を変更しないので、データは失われません。8

注意点として、TTD 1.11.611で大きなトレースのインデックス作成が改善された際にインデックス形式が変わり、既存のトレースは再インデックスが必要になっています。11 古い .idx を持ち回っている場合は、ここでつまずきます。

インデックスの確認と作り直しトレースを開いたら!index -statusで状態を確認し、Index file loaded以外なら!index -forceで作り直し、それでも駄目ならデバッガーを閉じて.idxを削除して.runを開き直す。作り直しは.runを変更しないIndex file loadedそれ以外失敗成功トレースを開く!index -status解析へ進む!index -force で作り直す閉じて .idx を削除し .run を開き直す

図16: インデックスの作り直しは .run に触らない。迷ったら削除して開き直せばよい。

6.2 位置で移動する

!tt に位置を渡すと、その時点へ移動します。6

!tt 0          ; トレースの先頭
!tt 50         ; おおよそ50%の位置
!tt 100        ; トレースの末尾
!tt 1A0:12F    ; 位置 1A0:12F へ

位置は シーケンシング番号:ステップ数 の16進数2組です。6 Position オブジェクトには Percent(トレース内の割合)、SequenceSteps のプロパティと、その位置へ移動する SeekTo()おおよその実時刻(UTC)を返す ToSystemTime() があります。7 長期稼働の調査ではこの ToSystemTime() が効きます。アプリのログに残った時刻と、トレース内の位置を突き合わせられるからです。TTD.Calls の結果にも SystemTimeStart / SystemTimeEnd が出ます。12

位置とログ時刻の突き合わせアプリのログにある時刻から、Positionオブジェクトのおおよその実時刻をたどってトレース内の位置を特定し、その位置へSeekToで移動して周辺の実行を読むアプリのログ:異常の時刻ToSystemTime の実時刻が近い位置を探すSeekTo でその位置へ周辺の呼び出しと値を読む

図17: 「ログのこの行の直前に何をしていたか」を、位置と実時刻の対応で引ける。

位置は共有にも使えます。トレースを同僚に渡すときに !tt x:y の位置を添えれば、相手は同じ時点から見始められます。バグ票に位置の範囲を書いておく運用も公式に推奨されています。2

6.3 巻き戻す

通常のステップ実行コマンドの末尾に - を付けると、時間を逆向きに進みます。13

コマンド 意味 リボンのボタン
p- 1命令(またはソース1行)戻る。関数呼び出しは1ステップ扱い Step Over Back
t- 1命令(またはソース1行)戻る。関数呼び出しの中も辿る Step Into Back
g- 逆方向に実行する。ブレークポイントに当たるか、イベントで止まるか、トレースの先頭で止まる Go Back

g- を止めるイベントは、順方向の g を止めるものと同じです。13 つまり ba(アクセスブレークポイント)や bp を仕掛けて g- を打てば、「その条件が最後に成立した位置」まで一気に戻れます。これが第7章の基本動作です。

逆方向コマンドの使い分けp-は関数呼び出しをまたいで1ステップ戻り、t-は関数の中まで1命令ずつ戻り、g-はブレークポイントかイベントかトレース先頭まで一気に戻る。順方向のgを止める条件はg-も止めるp-t-g-現在の位置呼び出しをまたいで1つ戻る関数の中まで1命令戻る次に止まる条件まで一気に戻るba / bp / イベント / トレース先頭

図18: 近くを丁寧に見るなら t-、遠くの原因へ飛ぶなら ba + g-

6.4 イベントから入る

どこから読み始めるか迷ったら、まずイベント一覧です。@$curprocess.TTD.Events には、スレッドの作成・終了、モジュールのロード・アンロード、例外がイベントとして並んでいます。14

dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)

例外イベントには、位置、種類(Software / Hardware)、例外コード、発生時のプログラムカウンターが入り、出力の [Time Travel] リンクを押すとその位置へ移動します。15 公式ウォークスルーはこの流れで、アクセス違反(0xc0000005)の位置へ飛び、スタックポインターとベースポインターが食い違っていることからスタック破壊を疑い、t- で3命令戻って値を確かめています。5 WinDbgの Timelines ウィンドウは、例外・ブレークポイント・メモリアクセス・関数呼び出しをタイムラインとして可視化し、例外をダブルクリックすると同じ SeekTo() が発行されます。16

6.5 スレッドと位置

!positions は、現在の位置におけるアクティブなスレッドすべてと、それぞれのトレース内位置を表示します。17 ここに1つ落とし穴があります。~<番号>s でスレッドを切り替えてもトレース内の位置は動きません。デバッガーがメモリを読むときに使う位置は変わらないので、別スレッドの「その時点」のメモリを見たいなら、!positions の出力にある位置のリンクか !tt x:y で移動します。13

再生の基本動線トレースを開いてインデックスを作り、イベント一覧から例外の位置へ移動し、逆方向のステップで原因へ遡り、必要ならpositionsで他スレッドの位置へ移るトレースを開く・インデックスTTD.Events で例外を探す[Time Travel] で位置へ移動t- / p- / g- で遡る!positions で他スレッドの位置を確認~s ではトレース位置は動かない

図19: 「イベントで入り、逆方向に歩く」が再生の基本動線。スレッド切り替えは位置移動ではない。

7. クエリで「いつ」を探す ── TTD.Calls と TTD.Memory

再生の本当の強みは、トレース全体を問い合わせできることです。デバッガーのデータモデル(dx コマンド)にTTDのオブジェクトが載っていて、LINQ風に絞り込み・並べ替え・集計ができます。18

7.1 TTD.Calls ── 関数呼び出しを検索する

@$cursession.TTD.Calls("module!symbol") は、指定した関数の呼び出しをトレース全体から集めます。ワイルドカードが使え、各呼び出しには開始・終了位置(TimeStart / TimeEnd)、スレッドID(再利用されない UniqueThreadId 付き)、引数(Parameters[])、戻り値(ReturnValue)、戻り先アドレス(ReturnAddress)が入ります。12

公式ドキュメントの例は GetLastError です。戻り値が0以外の呼び出しをエラーコードごとに集計すれば、トレース中に何のエラーが何回出たかが一覧になります。18

dx -g @$cursession.TTD.Calls("kernelbase!GetLastError").Where(x => x.ReturnValue != 0).GroupBy(x => x.ReturnValue).Select(x => new { ErrorNumber = x.First().ReturnValue, ErrorCount = x.Count() }).OrderByDescending(p => p.ErrorCount),d

「最後に出たMessageBoxはどこから呼ばれたか」なら、OrderBy(c => c.TimeStart).Last() で最後の呼び出しを取り、その TimeStart[Time Travel] リンクで移動します。18

TTD.Callsクエリの組み立て関数名で呼び出しを集め、戻り値や引数で絞り、エラーコードなどで集計し、時刻で並べ、目的の呼び出しの位置へTime Travelリンクで移動するTTD.Calls(関数名・ワイルドカード)Where:戻り値・引数で絞るGroupBy:エラーコード等で集計OrderBy:時刻で並べるTimeStart の [Time Travel] で移動

図20: 「どこで」ではなく「いつ・何回・どの引数で」から入るのがクエリの使い方。

シンボルとの関係を押さえておきます。TTDはPDBのシンボル情報から、関数の引数の数と型、戻り値の型、呼び出し規約を決めます。private symbolsがあれば関数名と正しい引数が出ます。public symbolsだけなら関数名と既定の引数(64ビット符号なし整数4つ)になります。シンボルがまったく無いモジュールは関数名が UnknownOrMissingSymbols になります。1218 PDBの種類と保管については「PDBとは何か」を参照してください。

シンボルの有無とTTD.Callsの結果private symbolsがあれば関数名と正しい引数が得られ、public symbolsだけなら関数名と既定の4つの64ビット整数引数になり、シンボルが無ければ関数名がUnknownOrMissingSymbolsになるprivate symbolspublic symbols無しモジュールのシンボルは?関数名+正しい引数・戻り値関数名+既定の引数(64ビット整数×4)UnknownOrMissingSymbols

図21: 自社モジュールのPDBを保管しているかどうかが、クエリの実用性をそのまま決める。

Calls は計算を伴うのでトレースが大きいほど時間がかかり、CPU使用率が上がります。結果はメモリにキャッシュされ、同じ関数への2回目以降のクエリは速くなります。12 クエリが何も返さないときの原因は4つで、呼び出しの書き方(モジュール名は x コマンドで確かめる。大文字で返ってきたらそれを使う)、対象DLLがその位置ではまだロードされていない(ロード後の位置へ移動して再実行)、関数がインライン展開されている(追跡不能)、ワイルドカードが広すぎる(絞る)、です。18

7.2 TTD.Memory ── メモリアクセスを検索する

@$cursession.TTD.Memory(開始アドレス, 終了アドレス, "アクセス種別") は、指定範囲のメモリへのアクセスをトレース全体から集めます。種別は r(読み)、w(書き)、rwe(実行)、rweec(実行/変更)です。19 「この変数を最後に書いたのは誰か」は、"w" で集めて .Last() を取り、その位置へ移動すれば答えが出ます。5

dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()

現在位置から前後に探すだけなら、@$curprocess.TTD.PrevMemoryAccess("w", アドレス, サイズ) / NextMemoryAccess が軽く、複数の範囲を一度に指定できます。レジスターが変わった位置は @$curthread.TTD.PrevRegisterWrite("rcx") で探せます。6

7.3 ba + g- ── 「誰が壊したか」を答えさせる

公式ウォークスルーが示す手順を、長期稼働の調査に使える形に縮めると次のようになります。5

  1. TTD.Events で例外の位置へ移動する
  2. t- で戻りながら、壊れた値を持つ変数を仮定する
  3. その変数のアドレスを dx &変数 で取る
  4. ba w4 <アドレス> で書き込みブレークポイントを置く
  5. g- で、その変数が最後に書かれた位置まで一気に戻る
  6. そこ(または数命令前)が原因かを見る。書いた値が別の変数由来なら、その変数にまた ba を置いて g- する
  7. 壊した命令に届くまで繰り返す
baとg-で値の出所を遡る壊れた値のアドレスに書き込みブレークポイントを置いて逆方向に実行し、最後に書いた命令に止まり、その値が別の変数由来なら同じ手順を繰り返して、壊した命令に届くまで遡るはいいいえ壊れた値のアドレスを特定ba w に書き込みブレークポイントg- で逆方向に実行最後に書いた命令で止まる値は別の変数由来?壊した命令=原因

図22: ダンプでは「壊れている」で終わる調査が、TTDでは「誰が壊したか」まで機械的に進む。

TTD 1.11.553以降には、フレームのローカル変数の値の履歴を返す @$curframe.TTD.VariableHistory() も加わりました。変数名の一覧と、各変数がどの値をどの位置範囲で持っていたかを表で見られます。11 スタック破壊のように「いつから値がおかしいか」を知りたい場面で、ba を置く前の当たりを付けるのに使えます。

8. 長期稼働の不具合に当てはめる

ここまでの道具を、冒頭に挙げた3種類の案件に当てはめます。

長期稼働の不具合の型と、TTDでの入口断続的な例外やデータ破壊はイベントからbaとg-で遡り、資源の増加はTTD.Callsで取得と解放の呼び出しを突き合わせ、応答なしや待ちの連鎖はpositionsと待機APIの呼び出しの実時刻で追う断続的な例外・データ破壊TTD.Events → ba + g-ハンドル・メモリの増加TTD.Calls で取得と解放を突き合わせ応答なし・待ちの連鎖!positions と待機APIの実時刻-module で自社DLLに限定して録る

図23: 型ごとに入口が違う。共通するのは、録画範囲を先に絞ってから読み始めること。

型1: 断続的な例外・データ破壊。第5.1節のリングバッファで症状の直前を残し、第6.4節のイベント一覧から例外の位置へ飛び、第7.3節の ba + g- で値の出所を遡ります。ダンプ調査との違いは、「壊れている変数」を見つけた時点で調査が終わらず、そこから機械的に前へ進めることです。

型2: ハンドル・メモリの増加。ハンドルリークで1か月後に落ちる」で解剖した種類の案件です。1か月分を録ることはできないので、第5.2節の -module で自社DLLに限定し、-ring と重ねて「増えている最中の数分」を残します。トレース上では TTD.Calls("kernelbase!CreateFileW")TTD.Calls("kernelbase!CloseHandle") を集め、CreateFileW の戻り値(ハンドル値)と CloseHandle の第1引数(Parameters[0])を突き合わせます。CloseHandleReturnValue は成否を表すブール値なので、突き合わせには使えません。閉じられていないハンドル値が残ったら、その CreateFileW 呼び出しの ReturnAddress(呼び出し元)を見れば、漏らしている呼び出し元が引けます。12 ただし、リークの「有無」と「量」の観測はApplication Verifierやハンドルカウントのほうが軽く、TTDは「どの経路が漏らしているか」を確定させる段で使うのが分担として正しいです。なお、Application Verifierを有効にしたまま録画すると、メモリの使い方の関係で再生時の性能が目立って悪くなるので、録画時は無効にします。8

型3: 「応答なし」・待ちの連鎖。「応答なし」の正体」で書いたように、ハングは「誰が誰を待っているか」の問題です。トレースはアイドル中には成長しない4ので、カーネルで待っているスレッドの待ち時間そのものは命令として写りません。写るのは待ちに入る直前の呼び出しと、戻ってきた後の命令です。!positions で各スレッドの位置を見て17TTD.Calls("kernelbase!WaitForSingleObject") などの SystemTimeStart / SystemTimeEnd から「どのスレッドが、いつ、何を待ち始めたか」を並べると、ログの時刻と付き合わせて待ちの連鎖を組み立てられます。12 DllMainとローダーロック条件変数のスプリアスウェイクアップのような「順序依存の不具合」は、順序そのものが録画に残るTTDと相性がよい領域です。

9. .NETアプリでのTTD

TTDの公式ドキュメントは、64ビットモードで動くSOS拡張(sos.dll)を使って、WinDbgのTTDでマネージドコードをデバッグできると述べています。1 読み込み方はSOS解析記事の第3章と同じで(.loadby sos coreclr または自動読み込み)、トレースの各位置で !clrstack!pe が使えます。基本の動線は、TTD.Events で例外の位置へ移動してから !clrstack でマネージドのスタックを読む、という組み合わせです。

.NETアプリのTTDトレースを読む構成TTDトレースをWinDbgで開き、64ビットのSOS拡張を読み込んで、例外イベントの位置へ移動してから!clrstackや!peでマネージドの状態を読む。TTD.Callsはネイティブ境界の呼び出しに使うTTDトレース(.run)WinDbgSOS拡張(64ビット)TTD.Events で例外の位置へ!clrstack / !pe で読むTTD.Calls はネイティブ境界の呼び出しに

図24: マネージドの状態はSOSで、呼び出しの検索はネイティブ境界で。役割を分けると迷わない。

注意点を2つ。第一に、公式が保証しているのは「64ビットモードのSOS」です。x86でビルドした.NETアプリについては明記が無いので、可能なら調査対象をx64で動かしてください。第二に、TTD.Calls はPDBのシンボル情報を前提にします。12 JITコンパイルされるマネージドメソッドを名前で検索する用途は当てにせず、P/Invoke先のネイティブDLL、COM、Win32 API といったネイティブ境界の呼び出しを追う使い方が確実です。「.NETでGC待ちとメモリリークを見分ける」のようなマネージドヒープの問題は、まず dotnet-counters / dotnet-gcdump / ダンプの !gcroot で追い、TTDはネイティブ境界が絡む段で出す、という順序です。

10. ダンプ・ログ・ETW・TTDの使い分け

TTDは万能ではなく、既存の道具を置き換えるものでもありません。当社の記事で扱ってきた道具と並べます。

知りたいこと まず使う道具 TTDの出番
落ちた瞬間の状態 クラッシュダンプ(WER LocalDumps / ProcDump) ダンプで「壊れている」までは分かったが、誰が壊したか分からないとき
どのファイル・レジストリで失敗したか Process Monitor 失敗したAPIに渡した引数がどう作られたかまで要るとき
PC全体・長時間の性能 WPR/WPAPerfView(ETW) 性能ではなく正しさの問題で、命令単位の順序が要るとき
業務上の経過 アプリのログ ログの無いコードパスで起きたとき(TTDは事前のコード変更なしにすべてを記録する1)
誰がその値を書いたか、呼び出しの順序 TTD ──
調査手段の選び方まず軽いダンプとログで状態を掴み、失敗したAPIはProcMon、性能はETWで調べ、それでも「誰がいつ何を渡したか」が要るときにTTDへ進むはいいいえ症状ダンプ・ログで状態を掴む(軽い)失敗したAPIは ProcMon性能は WPR/WPA・PerfView誰が・いつ・何を渡したかが必要?TTD で録画範囲を設計して録る軽い道具で決着

図25: 軽い道具で「結果」を掴んでから、「経路」が要ると分かった案件にだけTTDを出す。

順序は「軽いものから」です。ダンプとログは採取コストがほぼゼロなので常設し、TTDは「ダンプを読んだ結果、経路が要ると分かった」案件で、第5章の設計をしてから出します。TTDのオーバーヘッドと、トレースが機密情報を含む性質を考えると、この順序を逆にする理由はありません。

11. 運用上の注意

  • トレースは機密ファイルとして扱う。録画にはメモリ内容が含まれ、ファイルパス・レジストリ・メモリやファイルの中身など、個人情報・セキュリティ関連情報が入り得ます。12 顧客環境で録るなら、何が含まれ得るか(接続文字列、トークン、顧客データ)を事前に把握し、暗号化した経路と保管先、保持期間を決めます。
  • 共有するのは .run だけ。.idx.run と同程度に大きく、WinDbgが開くときに自動生成されます。.run は圧縮がよく効きます。TTD自体の不具合を報告するときは .out も添えます。2
  • バージョンをそろえる。TTDはWinDbgと一緒に更新され続けており、1.11.611ではAVX/AVX512対応プログラムの録画クラッシュ修正やインデックス形式の変更が入っています。11 録画側と再生側で古い版が混ざると、再インデックスや再録画になります。
  • 異なるCPUで再生するなら -replayCpuSupport を確認する。既定は可搬性を優先した設定で、録画したCPUと再生するCPUが違う(Intelのトレースをarm64で再生するなど)場合は MostConservative が用意されています。逆に同等以上のCPUで再生すると分かっていれば、より小さく速い録画も選べます。2
  • Windows Serverでも録れる。TTD.exeはWindows Server 2016/2019/2022/2025をサポートします。2
  • 録れないときの切り分け。まず ping.execmd.exe が録れるかを試し、録れなければアンチウイルスやアプリ仮想化などの侵襲的なソフトとの衝突を疑います。8
トレースの取り扱い手順録画したトレースはメモリ内容を含むため、含まれ得る情報を把握し、.runだけを圧縮して暗号化した経路で渡し、保管先と保持期間を決め、解析側では同じ版のWinDbgで開いてインデックスを作る録画完了(.run / .idx / .out)含まれ得る情報を把握.run だけを圧縮し暗号化して渡す保管先と保持期間を決める同じ版の WinDbg で開き .idx を生成

図26: トレースの受け渡しは「機密ファイルの受け渡し」。手順を決めてから録る。

12. まとめ

  • ダンプは「状態」、TTDは「経路」。壊れた値の出所や呼び出しの順序が要る案件で、TTDは調査を機械的な作業に変える
  • 録画は5〜20倍遅く、毎秒5〜50MB成長し、外れない。長期稼働に使うなら -ring/-maxFile-module-recordmode Manual-monitor で録る範囲を先に設計する
  • 再生は「イベントで入り、逆方向に歩く」。TTD.Events[Time Travel]t-/g-、そして ba + g- で「誰が壊したか」を答えさせる
  • TTD.CallsTTD.Memory はトレース全体への問い合わせ。ToSystemTime()SystemTimeStart でログの時刻と突き合わせる
  • .NETは64ビットのSOSで状態を読み、TTD.Calls はネイティブ境界に使う
  • トレースは機密ファイル。共有は .run だけ、経路と保管を決めてから

ダンプは取れているのに原因に届かない、再現しないので手が止まっている、という案件は、録画範囲の設計さえできればTTDで決着することが少なくありません。録画の設計から .run の解析までを引き受けることもできますので、ダンプとログを添えてご相談ください。

関連記事

関連する相談領域

合同会社小村ソフトでは、長期稼働後や断続的にしか発生しないWindowsアプリの不具合について、クラッシュダンプ・ログ・TTDトレースを組み合わせた原因調査、録画範囲の設計を含む調査体制の構築、ネイティブ境界(COM・P/Invoke・装置SDK)が絡む障害の切り分けを扱っています。「ダンプは取れたが原因に届かない」の段階からご相談ください。

参考リンク

  1. Microsoft Learn, Time Travel Debugging - Overview. TTDがプロセスの実行を録画して前後に再生できること、ダンプが失敗に至る状態と実行経路を取り逃がしがちなこと、録画に管理者権限が必要なこと、録画に個人情報・セキュリティ関連情報が含まれ得ること、調査手段の比較表、.run/.idx の役割、64ビットモードのSOS拡張でマネージドコードをデバッグできることについて。  2 3 4 5 6 7 8 9 10

  2. Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. 5〜20倍以上の速度低下、アタッチ後に自分では外れないこと、Windows Server 2016〜2025のサポート、インストールとオフライン展開、-launch/-attach/-monitor の3モード、-out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode の各オプション、.out ファイルの読み方、トレース共有の助言について。  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

  3. Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. アンチウイルスやメモリ監視ソフト・Electronとの非互換、ユーザーモード専用であること、再生が読み取り専用であること、保護されたプロセス(PPL)に注入できないこと、録画時の10〜20倍程度の性能影響について。  2 3 4 5 6 7

  4. Microsoft Learn, Time Travel Debugging - Working with Trace Files. トレースサイズの要因(1命令あたり1ビット〜1バイト)、活動中は毎秒5〜50MB成長しアイドル中は成長しないこと、最大サイズの上限が無いこと、インデックスがトレースの1〜2倍になること、ディスク切れ時の録画・インデックス作成の挙動と回避策について。  2 3 4 5 6 7 8 9

  5. Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. 例外イベントの位置へ移動し、bag- で不正な値を最後に書いた位置まで遡る一般手順、失敗地点が本当の原因から数ステップ進んだエラー処理の中にあることが多いこと、クラッシュ時にトレースが閉じられWinDbgが自動でインデックスを作ること、TTD.Memory.Last() の使い方について。  2 3 4 5 6 7

  6. Microsoft Learn, !tt (time travel). !tt の位置指定(パーセント・xx:yy)、位置の2要素(シーケンシング番号とステップ数)の意味、TTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess について。  2 3 4

  7. Microsoft Learn, TTD Position Objects. Positionオブジェクトの Percent/Sequence/StepsSeekTo()、おおよその実時刻(UTC)を返す ToSystemTime()FFFFFFFFFFFFFFFE:0 がトレース末尾を表すことについて。  2

  8. Microsoft Learn, Time Travel Debugging - Troubleshooting. 昇格が必要なこと、UWPアプリの起動録画が未対応なこと、別セッション・別セキュリティコンテキストの「通常でないプロセス」が対象外なこと、ping.exe/cmd.exe で切り分けること、Application Verifier併用時に再生が遅くなること、!index -status/!index -force によるインデックスの再作成について。  2 3 4 5

  9. Microsoft Learn, Time Travel Debugging - Record a trace. WinDbg UIの Launch executable (advanced) / Attach to process からの録画、Record with Time Travel Debugging チェックボックス、Configure and Record による保存先の設定、Record subset of execution によるモジュール限定について。 

  10. Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). -recordmode Manual と組み合わせて、プログラム側から録画の開始・停止を制御するインプロセス録画APIのドキュメントについて。 

  11. Microsoft Learn, Time travel debugging release notes. 1.11.611でのAVX/AVX512対応プログラムの録画修正とインデックス形式の変更(再インデックスが必要)、1.11.553で追加された @$curframe.TTD.VariableHistory() について。  2 3

  12. Microsoft Learn, TTD Calls Objects. TTD.Calls の引数と、ThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd の各プロパティ、PDBのシンボル情報が無い場合の既定(64ビット符号なし整数4引数・UnknownOrMissingSymbols)、計算に時間がかかり結果がキャッシュされることについて。  2 3 4 5 6 7

  13. Microsoft Learn, Time Travel Debugging - Replay a trace. p-/t-/g- による逆方向の実行、g- が順方向と同じイベントで止まること、!positions~s がトレース内の位置を変えないことについて。  2 3

  14. Microsoft Learn, TTD Event Objects. イベントの種類(ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception)と、Position・Module・Thread・Exceptionの子オブジェクトについて。 

  15. Microsoft Learn, TTD Exception Objects. 例外オブジェクトの Type(Software/Hardware)、ProgramCounterCodeFlagsPosition について。 

  16. Microsoft Learn, WinDbg: Timelines. Timelinesウィンドウが例外・ブレークポイント・メモリアクセス・関数呼び出しを可視化し、例外のダブルクリックで Position.SeekTo() が発行されることについて。 

  17. Microsoft Learn, !positions. アクティブなスレッドすべてと、それぞれのトレース内位置を表示することについて。  2

  18. Microsoft Learn, Introduction to Time Travel Debugging objects. @$curprocess.TTD / @$cursession.TTD のオブジェクト、OrderBy/Where/Select/GroupBy によるクエリ、GetLastError のエラー集計と MessageBoxW の最後の呼び出しの例、UnknownOrMissingSymbols の意味、Calls が何も返さない4つの原因について。  2 3 4 5

  19. Microsoft Learn, TTD Memory Objects. TTD.Memory のアクセス種別(r/w/rw/e/rwe/ec)と、結果の [Time Travel] リンクで位置へ移動できることについて。 

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

Time Travel Debugging(TTD)とクラッシュダンプは何が違いますか?
クラッシュダンプは「落ちた瞬間のメモリの写真」で、その時点の状態は分かりますが、そこに至った経路は写りません。TTDはプロセスの命令実行を丸ごと録画したもので、あとから前にも後ろにも再生でき、「その変数を最後に書き換えたのは誰か」「この例外の直前に何を呼んでいたか」を巻き戻して直接確かめられます。Microsoftの公式ドキュメントも、ダンプは失敗に至った状態と実行経路を取り逃がしがちだと明記しています。代償として録画中は5〜20倍遅くなり、トレースファイルは活動中に毎秒5〜50MB程度成長します。
何日も動かし続ける本番アプリにTTDを貼り付けておけますか?
そのままでは無理です。TTDは録画中の速度低下が大きく、アクティブなプロセスではトレースが毎秒5〜50MB成長し、ファイルサイズに上限もありません。長期稼働に使うなら、TTD.exeの-ring(リングバッファ)と-maxFileで「最後のNMBだけ残す」、-moduleで自社モジュールが動いている間だけ録る、-recordmode Manualでアプリ側から録画区間を指定する、といった録画範囲の設計が前提になります。また、一度アタッチしたTTDは自分では外れないので、録画をやめるにはプロセスを終了(再起動)する運用まで含めて決めておく必要があります。
サービスや別セッションのプロセスも録画できますか?
TTD.exeのドキュメントは、-attachを「サービスや長時間稼働するアプリの調査」向け、-monitorを「プログラムやサービスが起動するたびに録画」する用途として説明しています。一方でトラブルシューティングのページには、別セッションや別のセキュリティコンテキストで動く「通常でないプロセス」は現時点では録画対象外だという記述もあります。実際に録れるかは環境次第なので、本番と同じ構成でまずping.exeやcmd.exeのような単純なプロセスを録画し、次に対象プロセスで試して確認してから運用に組み込んでください。
.NETアプリのトレースでもTTDは使えますか?
使えます。公式ドキュメントは、64ビットモードで動くSOS拡張(sos.dll)をWinDbgのTTDトレース上で使ってマネージドコードをデバッグできると述べています。トレースの各位置で!clrstackや!peといったSOSコマンドを実行し、例外イベントの位置へ移動してからマネージドのスタックを読む、という組み合わせが基本です。TTD.Callsクエリでシンボル名から呼び出しを検索する機能はPDBのシンボル情報を前提とするので、.NETアプリではP/InvokeやCOM、Win32 APIといったネイティブ境界の呼び出しを追う使い方が確実です。
トレースファイル(.run)を他社や社外に送っても大丈夫ですか?
そのままでは危険です。TTDの録画にはプロセスのメモリ内容が含まれ、ファイルパス、レジストリ、メモリやファイルの中身など、個人情報や秘密情報が入り得ると公式ドキュメントが明記しています。送るなら、何が録画されているか(接続文字列、トークン、顧客データなど)を把握したうえで、暗号化した経路と保管先を決めてください。共有するのは.runだけで十分で、インデックスファイル(.idx)はWinDbgが開くときに自動生成されます。
TTD.Callsで関数を検索しても何も返りません。なぜですか?
原因は主に4つです。第一にシンボルの問題で、PDBが無いモジュールの関数は「UnknownOrMissingSymbols」という名前になり、モジュール名も大文字のことがあるので、xコマンドで実際のシンボル名を確認します。第二に、対象DLLがその時点でまだ読み込まれていない場合で、DLLがロードされた後の位置へ移動してから再度クエリします。第三に、関数がインライン展開されているとクエリエンジンが追跡できません。第四に、ワイルドカードが広すぎて一致する関数が多すぎる場合で、パターンを絞ります。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る