Application Verifierで作るWindows異常系テスト基盤

· 更新日: · · Windows開発, 不具合調査, 産業用カメラ, Application Verifier, 異常系テスト, ハンドルリーク

更新履歴(7件・最終更新 2026年08月22日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

追加した図のMermaidソースの字下げを記事内の規約に統一し、レビュー指摘のあった図の表現を本文の記述に合わせて調整しました。本文の文章は変えていません。
レビュー指摘に対応し、今日追加した図のうち幅が大きすぎたものを縦向きの構成に直し、一部の図とキャプションの表現を本文の記述に合わせて正確にしました。本文の文章は変えていません。
本文の流れ・構造を図でも追えるように、Mermaid図を20点追加しました(地の文500〜750字につき1図の規約に合わせたものです)。既存の2図にはキャプションを追加しています。本文の文章は変えていません。
記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
入手して手元で有効にするまでの節を新設しました(Windows SDK同梱、GUIとコマンドラインが同じレジストリ設定を書いているだけという関係、管理者グループ必須、実行中プロセスへの後付け不可、ログの場所)。あわせて`/faults`の引数`20000`が「2万回に1回」ではなく2%であることを出典付きで明記しました。verifier stopとは何か、WinDbgの`-xd`の意味、DLLの絞り込み方も追加しています。掲載した出力例は公式ドキュメントのもので、当社環境のものではない旨を本文に明記しました。
本文中の関連記事へのリンクの文言が、リンク先の現在のタイトルと食い違っていたのを、実際のタイトルに揃えました。本文の内容は変えていません。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21589611)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「Application Verifierで作るWindows異常系テスト基盤」合同会社小村ソフト. https://comcomponent.com/blog/2026/03/11/003-application-verifier-abnormal-test-foundation-part2/

DOI(登録済みアーカイブ)
10.5281/zenodo.21589611
DOI(前回登録した版)
10.5281/zenodo.21732633

Application Verifier は、Windows のネイティブコードや Win32 境界で起きる異常を前倒しで表面化させたいときに有力なツールです。 特に、ハンドル異常、ヒープ破壊、低リソース時の failure path をテストしたい場面では、通常系の試験だけでは見えない問題をかなり早く表に出せます。

前編の 産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編 では、長時間運転後に落ちる制御アプリを調べた結果、原因がハンドルリークだった事例を整理しました。 ただ、ログを強化しただけではまだ半分です。本当にほしいのは、今後もし想定外のプログラムミスでメモリリークやハンドルリーク、途中失敗、解放漏れが起きても、「何が起きたか分かる」状態になっているか を、先に試せることです。

そこで使ったのが Application Verifier です。 Windows のネイティブコードや Win32 境界で動く処理に対して、実行時にチェックや fault injection を入れられる道具です。実務で特に便利なのは、本当にマシンのメモリを食い尽くさなくても、メモリ不足や資源不足っぽい壊れ方を前倒しで起こせる ところです。

後編では、Application Verifier とは何か、どんなことができるか、それをどう異常系テスト基盤に組み込むかを、産業用カメラ制御アプリの文脈で整理します。

目次

  1. まず結論(ひとことで)
  2. Application Verifier とは何か
    • 2.1. ひとことで言うと何か
    • 2.2. どういう場面で効くか
    • 2.3. 何がうれしいか
    • 2.4. 入手して、手元で有効にするまで
  3. Application Verifier でどんなことができるか
    • 3.1. Basics: Handles / Heaps / Locks / Memory / TLS など
    • 3.2. Low Resource Simulation: メモリ不足や資源不足の前倒し
    • 3.3. Page Heap と debugger
    • 3.4. !avrf / !htrace / ログ
  4. 今回なぜ導入したか
    • 4.1. 目的は「バグを見つける」だけではない
    • 4.2. メモリ不足っぽい現象を起こす
    • 4.3. ハンドル異常が起きたときに追えるか確かめる
  5. メモリ不足や資源不足のような現象をどう起こすか
    • 5.1. Low Resource Simulation の考え方
    • 5.2. 何を失敗させられるか
    • 5.3. 実務での当て方
  6. ハンドル異常をどう見るか
    • 6.1. Handles チェック
    • 6.2. !htrace で open / close のスタックを見る
    • 6.3. 自前ログとどう組み合わせるか
  7. 異常系テスト基盤の作り方
    • 7.1. 実行単位を harness に寄せる
    • 7.2. テストメニューを分ける
    • 7.3. 収集するもの
    • 7.4. 合格条件
    • 7.5. 注意点
  8. ざっくり使い分け
  9. まとめ
  10. 参考資料

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

1. まず結論(ひとことで)

  • Application Verifier は、Windows の アンマネージド / ネイティブ境界 で起きる誤用を実行時に見つけやすくするツールです
  • 便利なのは「バグを見つける」だけでなく、普段は出にくい異常系を前倒しで起こせる ことです
  • Handles では invalid handle の検出、Heaps ではヒープ破壊の顕在化、Low Resource Simulation ではメモリ不足や資源不足っぽい状況の fault injection ができます
  • 長時間常駐する EXE の leak 調査を Application Verifier だけに丸投げするのは筋が悪く、Handle Count や resource lifecycle の自前ログと組み合わせるのが現実的です
  • 異常系テスト基盤では、通常系の verifier runfault injection run を分けて回した方が読みやすいです
  • DLL を試したい場合でも、Application Verifier を有効にする対象は、その DLL を実際に動かす テスト用 EXE です

要するに、Application Verifier は Windows の native / Win32 周りにいる“いやらしいバグ”を表面に引きずり出す道具 です。 特に、装置制御アプリのように native SDK、P/Invoke、Win32 API が普通に混ざる世界では、かなり相性がよいです。

Application Verifierの2つの働きApplication Verifierはネイティブ境界の誤用の検出と、普段は出にくい異常系の前倒しという2つの働きを持ち、invalid handleやヒープ破壊の検出とメモリ不足っぽい状況のfault injectionができる。Application Verifierネイティブ境界の誤用を検出出にくい異常系を前倒しinvalid handleやヒープ破壊メモリ不足っぽい状況の注入

図1: Application Verifierの働きは「誤用の検出」と「異常系の前倒し」の2本柱。

2. Application Verifier とは何か

2.1. ひとことで言うと何か

Application Verifier は、Windows の user-mode アプリに対する ランタイム検証ツール です。 実行中のアプリの OS API 利用や資源の扱い方を監視して、怪しい使い方を検出したり、意図的に失敗を注入したりできます。

「静的解析」や「単体テスト」と違って、実際にそのコードパスを通したときにどう壊れるか を見る道具です。 なので、普段の機能テストでは見えない failure path を炙り出すのに向いています。

テスト harness制御アプリ / SDK ラッパーApplication VerifierWin32 API / native DLL / OS 資源verifier stopdebugger outputAppVerifier logs自前 structured log

図2: テストharnessから動かす制御アプリをApplication Verifierが監視し、verifier stop・debugger出力・ログとして観測結果を残す。

2.2. どういう場面で効くか

特に効きやすいのは、次のような場面です。

  • native DLL やカメラ SDK を呼んでいる
  • P/Invoke や COM をまたいでいる
  • handle、heap、lock、virtual memory を直接または間接に多く使う
  • 普通の正常系ではまず落ちないが、異常系でだけ寿命管理が崩れそう
  • 「落ちる」よりも「たまに変な失敗を返す」が先に出る

逆に、純 managed の世界だけの object graph を追うための道具 ではありません。 なので、C# アプリでも native SDK や Win32 境界が厚いならかなり効きますが、純 managed heap leak をこれ 1 本で全部見る、という話ではないです。

Application Verifierが効く場面の見分けnative DLLやP/Invoke、Win32境界が厚いアプリには効くが、純managedの世界だけのobject graphを追う道具ではないという切り分けを示す。native SDKやWin32境界純managedのobject graphどの層の問題かApplication Verifierが効く対象外〔別の道具で見る〕

図3: 効くかどうかの分かれ目はnative / Win32境界の厚さで、純managedの世界だけを見る道具ではない。

2.3. 何がうれしいか

実務でうれしいのは、だいたい次の 3 つです。

  1. ネイティブ境界の誤用を早く止められる
    • invalid handle
    • heap corruption
    • lock misuse
    • virtual memory API misuse など
  2. 低リソース時にしか出ない壊れ方を前倒しできる
    • malloc 相当がたまに失敗する
    • CreateEventCreateFile がたまに失敗する
    • VirtualAlloc が失敗する
  3. debugger と組み合わせると追いやすい
    • !avrf
    • !htrace
    • !heap -p -a
    • verifier stop のログ

装置制御アプリで困るのは、「異常系で何が起きたか分からない」ことです。 Application Verifier は、その“分からなさ”を減らすのにかなり効きます。

実務でうれしい3つの点ネイティブ境界の誤用を早く止められる、低リソース時にしか出ない壊れ方を前倒しできる、debuggerと組み合わせると追いやすいという3つの利点を示す。Application Verifier誤用を早く止められる壊れ方を前倒しできるdebuggerで追いやすいavrfやhtraceなどの拡張

図4: 実務でのうれしさは、早期検出・異常系の前倒し・debugger連携の3つに集約される。

2.4. 入手して、手元で有効にするまで

先に、道具をそろえるところを片付けておきます。ここが抜けていると、以降の話が全部机上になります。

Application Verifier は Windows SDK に同梱されています。 Windows 単体では入っていないので、SDK のインストーラーを起動して、機能の選択画面で「Application Verifier」のチェックを入れてください。実行体の名前は appverif.exe です。

使ううえでの前提は 3 つあります。

  • 実行するユーザーが、そのマシンの Administrators グループのメンバーであること
  • ARM64EC はサポート対象外であること
  • 検証対象は アンマネージド(ネイティブ)コード であること

GUI とコマンドラインの関係は、次のように理解しておくと迷いません。

  何をしているか
GUI(appverif.exe 対象 EXE 名と、有効にするテストの組み合わせを レジストリに書き込む
コマンドライン(appverif -enable ... まったく同じレジストリ設定を、コマンドで書き込む
実行時 対象 EXE が起動するとき、その設定を見て verifier の DLL が読み込まれ、Win32 API のフックが入る

つまり どちらを使っても、やっていることは同じ です。手作業の初回は GUI、CI やスクリプトはコマンドライン、という使い分けになります。

GUIとコマンドラインの関係GUIもコマンドラインも同じレジストリ設定を書き込むだけで、対象EXEが起動するときにその設定を見てverifierのDLLが読み込まれ、Win32 APIのフックが入る。GUI〔appverif.exe〕レジストリに設定を書くコマンドライン対象EXEの起動時に参照verifierのDLL読込とフック

図5: GUIもコマンドラインも同じレジストリ設定を書いているだけで、フックが入るのは対象EXEの起動時。

GUI の操作は、画面左の Applications 欄で右クリックして「Add Application」で対象 EXE を追加し、画面右の Tests 欄で Basics などにチェックを入れて「Save」を押す、という流れです。解除は同じ Applications 欄で右クリックして「Delete Application」を選び、「Save」します。

ここから来る、重要な制約が 2 つあります。

  • 動いている最中のプロセスには、後から有効化できません。 フックは DLL の読み込み時に入るので、設定してから起動する、という順番になります。
  • 設定は明示的に消すまで残ります。 「1 回だけ試したつもり」で放置すると、そのマシンではその EXE が常に verifier 下で起動し続けます。

なお、検出時のログは既定で %USERPROFILE%\AppVerifierLogs にバイナリ形式で保存され、GUI かコマンドラインで XML に変換して集計できます。

有効化の順番と設定の残り方フックはDLLの読み込み時に入るため実行中のプロセスへは後付けできず、設定してから起動する順番になり、設定は明示的に消すまで残り続ける。設定を書く対象EXEを起動するverifier下で動く設定は消すまで残る放置すると常にverifier下で起動実行中のプロセス後からの有効化は不可

図6: 「設定してから起動」の順番は崩せず、設定は明示的に消すまでそのマシンに残り続ける。

3. Application Verifier でどんなことができるか

3.1. Basics: Handles / Heaps / Locks / Memory / TLS など

Application Verifier の基本セットは Basics です。 ここに、実務でよく使うチェックがまとまっています。

レイヤ 何を見るか 今回の文脈での使いどころ
Handles invalid handle の使用 close 済み / 壊れた handle を踏んでいないか
Heaps heap corruption native SDK 境界のバッファ破壊や use-after-free の炙り出し
Leak DLL unload 時点で解放されていない資源 短命 harness のテストや unload を含むケースの確認
Locks / SRWLock lock の誤用 reconnect と shutdown の競合確認
Memory VirtualAlloc / MapViewOfFile などの誤用 大きいバッファや共有メモリ周辺の異常確認
TLS Thread Local Storage API の誤用 スレッド境界が複雑な native コードの保険
Threadpool threadpool API や worker state の整合性 callback や非同期処理が多い場合の補助

ポイントは、「落ちたあとで読めば分かる」ではなく、「怪しい使い方をその場で止める」 ことです。 長時間運転型の不具合では、この前倒しがかなり効きます。

Basicsの前倒し検出の考え方落ちたあとでログを読んで推測するのではなく、怪しい使い方をその場で止めることで、長時間運転型の不具合を前倒しで表面化させる。怪しいAPIの使い方Basicsのチェック群その場で止める問題を前倒しで表面化従来は落ちたあとで読むだけ

図7: Basicsの価値は「落ちたあとで読む」を「その場で止める」に変えること。

3.2. Low Resource Simulation: メモリ不足や資源不足の前倒し

実務でかなり便利なのがここです。 本当に RAM を食い尽くさなくても、メモリ不足や資源不足に近い現象を起こせる からです。

考え方としては単純です。

  • ある API 呼び出しを
  • 一定確率で
  • わざと失敗させる

これで、普段はまず通らない error path を通せます。

具体的には、こういう現象を意図的に起こしやすくなります。

  • HeapAllocVirtualAlloc が失敗する
  • CreateFile が失敗する
  • CreateEvent が失敗する
  • MapViewOfFile が失敗する
  • SysAllocString のような OLE/COM 系割り当てが失敗する

本当にメモリ不足にしようとしてマシン全体を苦しめるより、こちらの方がずっと扱いやすいです。 しかも、特定の DLL だけを狙って fault injection を入れる こともできます。装置制御アプリのように自前ラッパーと vendor SDK が混ざる構成では、かなり実務向きです。

Low Resource Simulationの仕組みある種類のAPI呼び出しを一定確率でわざと失敗させることで、普段はまず通らないerror pathを意図的に通せるようにし、対象を特定DLLに絞ることもできる。はいいいえAPI呼び出し一定確率に当たった?わざと失敗を返す通常どおり処理普段通らないerror pathへ特定DLLだけに絞ることも可

図8: Low Resource Simulationの正体は、API呼び出しを一定確率で失敗させるfault injection。

3.3. Page Heap と debugger

ヒープ破壊を見るなら、Heaps と page heap の組み合わせが強いです。 特に full page heap は、guard page を使って 壊れた瞬間に止めやすい のが利点です。

ただし、これはだいぶ重いです。 長時間の総当たりというより、再現が近いシナリオに絞って debugger 下で回す 方が使いやすいです。

なので、運用としてはこんな分け方が現実的です。

  • まず Basics で広く当てる
  • ヒープが怪しくなったら full page heap を使う
  • 重すぎるときは light page heap に落とす
  • 本番相当の長時間試験は、自前ログ中心で見る

つまるところ、AppVerifier は万能杖ではなく、場面ごとに刃を替える工具 です。

page heapの使い分けの流れまずBasicsで広く当て、ヒープが怪しければfull page heapで壊れた瞬間に止め、重すぎればlight page heapに落とし、本番相当の長時間試験は自前ログ中心で見る。はいいいえはいいいえBasicsで広く当てるヒープが怪しい?full page heapで止める長時間試験は自前ログ中心重すぎる?light page heapに落とすdebugger下で局所再現

図9: page heapは常時使う道具ではなく、Basicsで広く当ててから場面ごとに刃を替える。

3.4. !avrf / !htrace / ログ

先に言葉を 1 つ。ここまで何度か出てきた verifier stop は、Application Verifier が「この使い方はおかしい」と判断したときに出す検出イベントです。単なるログ行ではなく、デバッガーの下で動かしていれば、その場でブレークする のが特徴です。stop には番号が振られていて、VERIFIER STOP 00000300 のように表示されます。そのまま続行できる stop と、続行できない(プロセスを終わらせるしかない)stop があります。

Application Verifier は、stop を出して終わりではありません。 debugger 拡張やログがあるので、何が起きたかを追いやすくなります。

  • !avrf
    • 現在の verifier 設定や、今起きている stop を見る
  • !htrace
    • handle の open / close / invalid reference のスタックを見る
  • !heap -p -a
    • page heap と組み合わせて、壊れたヒープブロックをたどる
  • AppVerifier のログ
    • stop が起きたときのログを残せる

特に Handles を有効にしていると、handle tracing が自動で有効になる のがありがたいです。 これで「この handle をどこで開いて、どこで閉じたか」を後から追いやすくなります。

verifier stopから調査までの流れおかしい使い方を検出するとverifier stopが番号付きで出て、デバッガー下ならその場でブレークし、avrfで設定とstopを、htraceでハンドルの履歴を確認できる。おかしい使い方を検出verifier stop〔番号付き〕デバッガー下ならブレークavrfで設定とstopを確認htraceでhandle履歴を確認続行できるstopとできないstop

図10: verifier stopは単なるログ行ではなく、デバッガー下ではその場でブレークして調査の起点になる。

4. 今回なぜ導入したか

4.1. 目的は「バグを見つける」だけではない

今回の目的は、単に「AppVerifier で 1 個バグを見つける」ことではありませんでした。 もっと実務的に言うと、確かめたかったのは次の点です。

  • 将来また別の failure path で資源漏れが起きたとき
  • ちゃんとログに文脈が残るか
  • debugger 情報と合わせて追い切れるか
  • 「何が起きたか不明」という状態にならないか

つまり、検出器 としてだけでなく、観測基盤のテスト として使いました。

4.2. メモリ不足っぽい現象を起こす

普段の開発機で本当にメモリ不足を起こすのは、なかなか面倒です。 しかも、マシン全体が不安定になると、今度はテスト自体が雑音だらけになります。

そこで、Low Resource Simulation を使って、メモリ不足や資源不足で起きそうな failure path を、狙って踏ませる 方向にしました。

これで、こんな問いに答えやすくなります。

  • CreateEvent が失敗したら、ログに cameraIdphase は残るか
  • 中途半端な初期化のあとで clean up はちゃんと走るか
  • VirtualAlloc が失敗したら、リトライで壊れないか
  • save path の CreateFile 失敗時にハンドルが戻るか

強調しておきたいのは、異常を起こすこと自体が目的ではなく、異常時に壊れ方が読めることが目的 だという点です。

fault injectionで確かめる観測基盤Low Resource Simulationで失敗を狙って踏ませ、ログに文脈が残るか、後始末が走るか、リトライで壊れないかを確かめる。目的は異常を起こすことではなく壊れ方が読めること。失敗を狙って踏ませるログに文脈は残るかclean upは走るかリトライで壊れないか壊れ方が読める状態

図11: fault injectionの目的は異常を起こすことではなく、異常時の壊れ方が読めるかを確かめること。

4.3. ハンドル異常が起きたときに追えるか確かめる

前編で出てきたハンドルリークもそうですが、ハンドルまわりは 最後に落ちた場所と本当の原因がずれやすい です。

なので、確認したかったのはこういうことです。

  • invalid handle stop が出たときに、!htrace で open / close を追えるか
  • 自前ログの resourceId / sessionId / phase と結びつくか
  • 失敗後に handle count が戻るか
  • harness を短命プロセスにしたとき、リークの差分が見やすいか

ここまで見えると、単なる「バグが出ました」から、「どの責務で寿命管理が崩れたか」まで行ける ようになります。

ハンドル異常時に追えるかの確認invalid handle stopが出たときにhtraceでopenとcloseを追えるか、自前ログの文脈と結びつくか、handle countが戻るかを確かめ、寿命管理が崩れた責務の特定まで到達する。invalid handle stophtraceでopenとcloseを追う自前ログの文脈と結びつけるhandle countの回復を確認寿命管理が崩れた責務の特定

図12: ハンドル異常では「バグが出た」で止めず、どの責務で寿命管理が崩れたかまで追えるかを確かめる。

5. メモリ不足や資源不足のような現象をどう起こすか

5.1. Low Resource Simulation の考え方

Low Resource Simulation は、いわゆる fault injection です。 低リソース環境を本物そっくりに再現するというより、低リソース時に起きる代表的な API 失敗を人工的に混ぜる イメージです。

なので、使いどころはかなりはっきりしています。

  • failure path の後始末確認
  • retry / reconnect の堅さ確認
  • 途中成功・途中失敗の混ざる初期化確認
  • 「普段は起きない失敗」でもログが残るか確認

ここでのコツは、最初から何でも失敗させない ことです。 いきなり全部盛りにすると、ログが爆発して「何を見ているか」が分からなくなります。

fault injectionの絞り方最初から何でも失敗させるとログが爆発して何を見ているのか分からなくなるため、見たいfailure pathに近い失敗から絞って開ける。最初から全部失敗させるログが爆発して読めない見たい失敗だけ開ける何を見ているかが明確

図13: fault injectionのコツは全部盛りにしないことで、見たいfailure pathに近い失敗から絞って開ける。

5.2. 何を失敗させられるか

Low Resource Simulation では、代表的には次の種類の API を確率的に失敗させられます。

種類 装置制御アプリでの例
Heap_Alloc ヒープ確保 一時バッファ、画像メタデータ、SDK ラッパー内部確保
Virtual_Alloc 仮想メモリ確保 大きめのフレームバッファ、リングバッファ
File CreateFile など 保存パスやログファイルの open
Event CreateEvent など frame ready 通知、stop/reconnect 同期
MapView CreateMapView など 共有メモリや memory mapped file
Ole_Alloc SysAllocString など COM / OLE 境界
Wait WaitForXXX 同期待機失敗まわり
Registry レジストリアクセス 設定読み書きやドライバ周辺設定

実務では、全部を同時に開けるより、今回見たい failure path に近いものから絞って開ける のが肝心です。

5.3. 実務での当て方

コマンドラインのイメージとしては、たとえば次のようになります。

appverif /verify CameraHarness.exe
appverif /verify CameraHarness.exe /faults
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 virtual_alloc=20000 file=20000 event=20000
appverif -query lowres -for CameraHarness.exe

コピペしても意図が読めないと意味がないので、1 行ずつ何をしているかを書いておきます。

コマンド 何をするか
appverif /verify CameraHarness.exe CameraHarness.exe に対して Basics のテスト群を有効にする
appverif /verify CameraHarness.exe /faults 上に加えて fault injection を有効にする。ただし対象は OLE_ALLOCHEAP_ALLOC だけ
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 ... lowres(Low Resource Simulation)を有効にし、失敗させる API の種類と確率を個別に指定する
appverif -query lowres -for CameraHarness.exe いま何がどの確率で設定されているかを表示する
appverif /n CameraHarness.exe その EXE の設定を削除する(-disable * -for-delete settings -for と同じ目的)

引数の読み方も押さえておきます。

  • 確率は 100 万分率です。 指定できるのは 0 〜 1,000,000 の整数で、2000020000 / 1,000,000 すなわち 2% です。「2 万回に 1 回」ではありません。Microsoft のドキュメントも、レジストリとファイルの API を 2% で失敗させる例として -with registry=20000 file=20000 を挙げています。
  • /faults の後ろには、確率・猶予時間・DLL 名を並べられます。 書式は /faults [確率 [猶予ミリ秒 [DLL ...]]] です。確率を省略すると 5%、猶予時間を省略すると 500 ミリ秒になります。猶予時間は「プロセス起動からこの時間は fault を入れない」という意味で、起動処理そのものが失敗して何も試せなくなるのを防ぐためのものです。
  • /n は解除です。 n は「no verifier」くらいの意味だと思ってください。有効化しっぱなしにしないための、対になるコマンドです。

-query lowres の出力は、だいたいこういう形で返ってきます。設定した確率が入っているか、狙っていない種類まで開いていないかを、ここで確認できます。

Settings for CameraHarness.exe:
Test [lowres] enabled.

Include = *
Exclude =
TimeOut = 2000 (0x7D0)
WAIT = 0 (0x0)
HEAP_ALLOC = 20000 (0x4E20)
VIRTUAL_ALLOC = 0 (0x0)
REGISTRY = 0 (0x0)
FILE = 20000 (0x4E20)
EVENT = 20000 (0x4E20)
MAP_VIEW = 0 (0x0)
OLE_ALLOC = 0 (0x0)
STACKS = false

IncludeExclude が対象モジュールの絞り込み、TimeOut が起動直後に fault を入れない時間です。既定値は設定の入れ方によって変わるので、思い込まず、この出力で確認するのが確実です。

考え方としては、こんな感じです。

  1. まず Basics だけで通常系を回す
  2. 次に Low Resource Simulation を足して fault injection ありで回す
  3. 必要なら fileevent など、見たい失敗だけ確率を持たせる
  4. 特定 DLL だけ狙いたいなら、その DLL に絞って入れる

/faults のショートカットは便利ですが、これだけだと OLE_ALLOCHEAP_ALLOC 中心 です。 CreateFileCreateEvent の failure path を見たいなら、-enable lowres -with file=... event=... まで書いた方が確実です。

装置制御アプリでは、アプリ全体に fault をばらまくより、camera wrapper や save path の DLL に絞ったほうが読みやすい ことが多いです。

fault injectionを当てる順序まずBasicsだけで通常系を回し、次にLow Resource Simulationを足してfault injectionありで回し、見たい失敗だけ確率を持たせ、必要なら特定DLLに絞るという段階的な当て方。Basicsだけで通常系Low Resourceを足して回す見たい失敗だけ確率指定特定DLLに絞って入れる

図14: いきなり狙い撃ちせず、Basicsの通常系から段階的にfault injectionを絞り込んでいく。

その「DLL に絞る」の具体的な書き方も置いておきます。/faults の 3 番目以降の引数が、対象モジュールの指定です。

appverif /verify CameraHarness.exe /faults 50000 1000 CameraSdkWrapper.dll

これで、CameraHarness.exe を起動したとき、起動から 1000 ミリ秒たった後、CameraSdkWrapper.dll から始まった操作だけ を 5%(50000 / 1,000,000)の確率で失敗させます。モジュール名は拡張子まで含めて書き、パスは付けません。.dll だけでなく .ocx のような読み込まれるモジュールも指定できます。

絞り込みが効いているかは、appverif -query lowres -for CameraHarness.exeIncludeExclude の行で確認できます。ここが * のままなら、まだプロセス全体が対象です。

DLLに絞ったfault injectionの動きfaultsの引数で確率と猶予時間と対象モジュールを指定すると、起動から猶予時間が過ぎた後、指定DLLから始まった操作だけが指定確率で失敗し、queryのIncludeとExcludeで絞り込みを確認できる。確率と猶予時間とDLL名を指定起動から猶予時間は注入しない指定DLL発の操作だけ失敗queryのIncludeとExcludeで確認

図15: DLLに絞ったfault injectionは、猶予時間の経過後、指定モジュールから始まった操作だけを失敗させる。

デバッガー下で走らせているなら、途中から範囲を変えることもできます。

!avrf -trg dll CameraSdkWrapper.dll
!avrf -skp dll VendorSdk.dll

-trg が「ここを狙う」、-skp が「ここは飛ばす」です。!avrf -flt で現在の fault injection 設定を確認したり、!avrf -flt stacks 10 で直近に注入された失敗のスタックを見たりもできます。

たとえば、こういうシナリオを作れます。

  • reconnect 開始直後の CreateEvent 失敗
  • 保存開始時の CreateFile 失敗
  • 一時バッファ確保失敗
  • COM 変換の SysAllocString 失敗
  • 待機 API の失敗経路確認

このへんは、普段の正常系テストだけではまず踏めません。 だからこそ、意図的に踏ませる価値があります。

6. ハンドル異常をどう見るか

6.1. Handles チェック

ハンドル周りでは、まず Handles を使います。 これで、invalid handle の使用を検出しやすくなります。

典型的に効くのは、こういう事故です。

  • close 済み handle をもう一度使う
  • 壊れた handle 値を渡す
  • 途中失敗で初期化されていない handle を使う
  • lifetime が崩れて別スレッドから触る

長時間運転で見ると「たまに変なエラーが出る」だけでも、verifier 下ではその場で止まってくれることがあります。 この前倒しはかなり助かります。

Handlesチェックが効く事故close済みハンドルの再使用、壊れたハンドル値、途中失敗で未初期化のハンドル、寿命崩れによる別スレッドからの誤用といった事故を、verifier下ではその場で止められる。close済みの再使用その場でverifier stop壊れたhandle値未初期化handle寿命崩れの誤用

図16: 長時間運転では「たまに変なエラー」で済んでしまう事故を、Handlesチェックはその場で止める。

6.2. !htrace で open / close のスタックを見る

Handles がありがたいのは、handle tracing と相性がよい ことです。

ここからはデバッガーを使うので、先に WinDbg を入れておきます。Debugging Tools for Windows として配布されていて、Application Verifier と同じ Windows SDK のインストーラーから入れられます。

windbg -xd av -xd ch -xd sov CameraHarness.exe
!avrf
!htrace 0x00000ABC

1 行目のオプションは、おまじないではありません。Application Verifier が検出時に投げる例外は 3 種類あります。

オプション 例外 いつ出るか
av アクセス違反(0xC0000005 heap のバッファ超過を検出したとき
ch 無効ハンドル(0xC0000008 invalid handle の使用を検出したとき
sov スタックオーバーフロー(0xC00000FD 初期スタックが足りないと判断したとき

そして -xd は、その例外を second chance で捕まえる という指定です。first chance は Application Verifier 自身が処理して stop の情報を組み立てるので、デバッガーが先に割り込むと都合が悪い、という事情です。すでに起動したデバッガーで設定するなら、sxd avsxd chsxd sov を打つのと同じことです。

!htrace で見たいのは、だいたいこのあたりです。

  • その handle がどこで open されたか
  • どこで close されたか
  • invalid handle として参照されたか
  • 想定より多く open が積み上がっていないか

実際の見え方も載せておきます。以下は公式ドキュメントに載っている出力例で、当社の環境のものではありませんが、形はそのままです。

invalid handle を踏むと、まずこう出ます。

Invalid handle - code c0000008 (first chance)
===================================================
VERIFIER STOP 00000300: pid 0x558: invalid handle exception for current stack trace
        C0000008 : Exception code.
        0012FBF8 : Exception record. Use .exr to display it.
        0012FC0C : Context record. Use .cxr to display it.
        00000000 :
===================================================
This verifier stop is continuable.
After debugging it use 'go' to continue.
===================================================

続けて !avrf を打つと、いま何が有効で、どの stop が起きているかが出ます。最後の 1 行が要約です。

0:000> !avrf
Global flags: 00000100
Application verifier global flag is set.
Application verifier settings (00000004):
   - no heap checking enabled!
   - handle checks
Page heap is not active for this process.
Current stop 00000300 : c0000008 0012fbf8 0012fc0c 00000000 .
    Using an invalid handle (either closed or simply bad).

そのハンドルの履歴を !htrace で見ると、OPEN / CLOSE / BAD REFERENCE がそれぞれスタック付きで並びます。

0:000> !htrace 7DC

--------------------------------------
Handle 0x7DC - BAD REFERENCE:
0x801902BE: ntoskrnl!NtSetEvent+0x6C
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - CLOSE:
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - OPEN:
0x77DE265C: KERNEL32!CreateEventA+0x66
0x010011A0: badhandle!main+0x20
--------------------------------------

読み方は素直で、CLOSE の後に BAD REFERENCE が来ていれば、閉じたハンドルをもう一度使っている ということです。OPEN のスタックを見れば、そのハンドルを作った場所も分かります。

ハンドルリークや handle misuse が厄介なのは、最後にこけた API が本当の原因ではない ことです。 !htrace があると、その handle の履歴をかなり具体的に追えます。

htraceによるハンドル履歴の読み方htraceはハンドルのOPEN、CLOSE、BAD REFERENCEをそれぞれスタック付きで並べ、CLOSEの後にBAD REFERENCEが来ていれば閉じたハンドルの再使用と分かり、OPENのスタックで作った場所も分かる。OPEN〔作った場所〕CLOSE〔閉じた場所〕BAD REFERENCE閉じたhandleの再使用と判明各記録にスタックが付く

図17: htraceの読み方は素直で、CLOSEの後にBAD REFERENCEが並んでいれば閉じたハンドルの再使用。

6.3. 自前ログとどう組み合わせるか

とはいえ、Application Verifier だけでは足りません。 特に、長時間常駐する EXE の leak 調査をこれだけでやるのは、だいぶしんどいです。

なので、実務では次を合わせます。

  • 定期 Handle Count
  • sessionId
  • resourceId
  • phase
  • create/open と close/dispose の lifecycle log
  • verifier stop 時の dump と debugger 出力

これで、たとえば次のように追えます。

  1. heartbeat で Handle Count の傾きが怪しいと分かる
  2. lifecycle log で Create はあるのに Close が無い resource を絞る
  3. verifier run で invalid handle や misuse を前倒しで出す
  4. !htrace で open / close stack を見る

この合わせ技で、ぐっと追いやすくなります。

自前ログとverifierの合わせ技の手順heartbeatでHandle Countの傾きに気づき、lifecycle logでCloseの無い資源を絞り、verifier runで誤用を前倒しで出し、htraceでopenとcloseのスタックを見るという順序で追う。Handle Countの傾きに気づくlifecycle logで資源を絞るverifier runで誤用を前倒しhtraceでスタックを見る

図18: 傾きの検出は自前ログ、誤用の検出はverifierという分業を、この順序でつなぐ。

7. 異常系テスト基盤の作り方

7.1. 実行単位を harness に寄せる

Application Verifier は、動いている最中のプロセスに後から有効化できません。 設定してから起動、です。

しかも、設定は明示的に消すまで残ります。 なので、実務では 本番アプリ本体より、テスト用 harness EXE に寄せたほうが扱いやすいです。

たとえば、こういう構成です。

Scenario RunnerCameraHarness.exeCameraSdkWrapper.dllVendor SDKStructured LogDump / Debugger

図19: 1シナリオ1プロセスで回すharness構成。verifierの対象はDLLではなく、それを動かすharness EXE。

これなら、

  • 1 シナリオ 1 プロセスで回せる
  • leak 差分を見やすい
  • AppVerifier 設定の ON/OFF を切り替えやすい
  • DLL を試す場合も EXE 側で扱える

という利点があります。

コマンドのイメージはこうです。

appverif /verify CameraHarness.exe
appverif /n CameraHarness.exe

/verifyBasics の有効化、/n が設定の削除です(5.3 の表も参照してください)。 有効化は起動前、解除は明示的に、です。 このへんを harness 前提で回すと、設定の事故も減らしやすいです。

7.2. テストメニューを分ける

異常系テスト基盤では、全部を 1 回でやらない方がいいです。 だいたい次の 3 本に分けると読みやすいです。

  1. 通常系 + Basics
    • 何も失敗を注入しない
    • verifier stop が出ないことを確認する
  2. fault injection 系
    • Low Resource Simulation
    • event / file / heap_alloc / virtual_alloc などを狙って失敗させる
  3. heap 深掘り系
    • Heaps
    • full page heap
    • debugger 下で局所的に再現する

ここを分けると、 「普段の使い方で壊れているのか」「低リソース時だけ壊れるのか」 がごちゃつきにくいです。

特に fault injection の有無では、通る code path がかなり変わります。 なので、fault なしの runfault ありの run は両方回した方がよいです。

3本に分けたテストメニュー何も注入しない通常系プラスBasics、Low Resource Simulationで狙って失敗させるfault injection系、full page heapをdebugger下で回すheap深掘り系の3本にテストを分ける。異常系テストの回し方通常系とBasicsfault injection系heap深掘り系stopが出ないことを確認狙った失敗を注入するdebugger下で局所再現

図20: 全部を1回でやらず3本のメニューに分けると、どこで壊れているのかがごちゃつかない。

7.3. 収集するもの

最低限、これだけは取っておきたいところです。

種類 ほしいもの
アプリログ cameraId, sessionId, phase, handleCount, error code
process 状態 Handle Count, Private Bytes, Thread Count
debugger 情報 !avrf, !htrace, 必要に応じて !heap -p -a
dump verifier stop 時、または異常終了時
AppVerifier ログ stop の記録、必要なら XML 化して集計

必要なら AppVerifier 側のログも XML にして集計できます。 ただ、そこだけ見ても原因は閉じないことが多いので、自前ログと並べて読む前提のほうが実務向きです。

ログが多いこと自体は偉くありません。 あとで因果がつながること が大事です。

7.4. 合格条件

合格条件も、「落ちなかった」だけでは弱いです。 今回の文脈では、少なくとも次は必要でした。

  • 通常系 + Basics で verifier stop が出ない
  • fault injection ありでも、想定した失敗はログに残る
  • 中途半端に初期化された資源がきちんと片付く
  • reconnect / retry 後に Handle Count が baseline 近くへ戻る
  • verifier stop が出たとき、sessionId / phase / stack で追える
  • 「何が起きたか分からない」失敗にならない

ここで大事なのは、 壊れないこと壊れたときに追えること を分けて評価することです。

合格条件の2軸合格条件を、通常系でverifier stopが出ず資源が片付くという壊れないことの軸と、想定した失敗がログに残り文脈とスタックで追えるという壊れたとき追えることの軸に分けて評価する。合格条件壊れないこと壊れたとき追えることstopが出ない資源が片付く失敗がログに残るスタックで追える

図21: 「落ちなかった」だけでは弱く、壊れないことと追えることを別の軸として評価する。

7.5. 注意点

Application Verifier はかなり便利ですが、魔法ではありません。

  • 実際に通していないコードパスは検証されない
  • full page heap は重い
  • third-party SDK 側で stop が出ることもある
  • fault injection あり/なしで通るコードパスはかなり違う
  • 純 managed heap leak の調査をこれ 1 本でやる道具ではない

なので、立ち位置としてはこうです。

  • 長時間の傾き は自前ログと counters
  • native 境界の誤用 は Application Verifier
  • 異常時の因果の復元 は structured log + dump + debugger

この分業がいちばん実務向きです。

調査の分業の全体像長時間の傾きは自前ログとcountersで、native境界の誤用はApplication Verifierで、異常時の因果の復元はstructured logとdumpとdebuggerで見るという分業を示す。長時間の傾き自前ログとcountersnative境界の誤用Application Verifier異常時の因果の復元ログとdumpとdebugger

図22: Application Verifierは万能杖ではなく、見たいものごとに道具を分業させる。

8. ざっくり使い分け

  • invalid handle や double close が怪しい
    • Handles + !htrace
  • heap corruption / use-after-free が怪しい
    • Heaps + full page heap + !heap -p -a
  • メモリ不足や資源不足っぽい現象を起こしたい
    • Low Resource Simulation
  • 長時間運転でじわじわ壊れる
    • まず自前の Handle Count / Private Bytes / lifecycle log
  • DLL を試したい
    • その DLL を呼ぶ harness EXE に対して Application Verifier を有効にする

最初から全部盛りにすると、だいたいログの霧になります。 見たい failure path に近い刃から当てる方が、ずっと分かりやすいです。

9. まとめ

Application Verifier の立ち位置は、Windows の native / Win32 境界の runtime verifier です。Handles / Heaps / Locks / Memory / TLS / Low Resource Simulation などを使って、普段は出にくい failure path を前倒しで踏ませられます。

今回の文脈で効いたのは、ハンドル異常が起きたときに !htrace で追いやすいこと、メモリ不足や資源不足っぽい現象をマシン全体を壊さずに起こせること、そして、そのとき自前ログが本当に役に立つかを確認できたことでした。

実務での回し方としては、通常系 + Basics と fault injection 系を分け、harness EXE を用意して短命プロセスでシナリオを回す。そのうえで自前ログ、dump、debugger 情報と組み合わせ、長時間 leak の傾きそのものは自前 counters で見る、という分担になります。

Application Verifier は、 「めったに出ない異常」を“たまたま待つ”のではなく、“こちらから迎えに行く”ための道具 です。

装置制御アプリでは、壊れないことも大事ですが、 壊れたときに 何が起きたかを説明できること が、同じくらい大事です。 その意味で、かなり実務向きの道具だと思います。

前編: 産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編

10. 参考資料

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

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

実際の整理や改善の進め方が近い事例ページです。

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

よくある質問

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

Application Verifierとは何ですか?
Windowsのuser-modeアプリに対するランタイム検証ツールです。実行中のアプリのOS API利用や資源の扱い方を監視して、invalid handleの使用やheap corruptionのような怪しい使い方を検出したり、意図的に失敗を注入したりできます。静的解析や単体テストと違い、実際にそのコードパスを通したときにどう壊れるかを見る道具なので、普段の機能テストでは見えないfailure pathを炙り出すのに向いています。
Application Verifierでメモリ不足を再現できますか?
Low Resource Simulationを使うと、本当にマシンのRAMを食い尽くさなくても、メモリ不足や資源不足に近い現象を前倒しで起こせます。仕組みはfault injectionで、HeapAlloc、VirtualAlloc、CreateFile、CreateEventなどのAPI呼び出しを一定確率でわざと失敗させます。特定のDLLだけを狙って失敗を注入することもできるため、自前ラッパーとvendor SDKが混ざる構成でも扱いやすいです。ただし最初から何でも失敗させるとログが読めなくなるので、見たいfailure pathに近いものから絞って開けるのがコツです。
ハンドルリークの調査にApplication Verifierは使えますか?
Handlesチェックを有効にすると、close済みハンドルの再使用などのinvalid handle使用を検出でき、handle tracingも自動で有効になるため、!htraceでそのハンドルのopen / closeのスタックを追えます。ただし、長時間常駐するEXEのリーク調査をApplication Verifierだけに丸投げするのは現実的ではありません。定期的なHandle Countの記録やresource lifecycleの自前ログと組み合わせ、傾きの検出は自前ログ、誤用の検出はverifierという分業が実務向きです。
Application VerifierをDLLのテストに使うにはどうすればよいですか?
Application Verifierを有効にする対象は、そのDLLを実際に動かすテスト用EXEです。実行中のプロセスに後から有効化はできず、設定してから起動する必要があります。また設定は明示的に消すまで残るため、本番アプリ本体よりテスト用のharness EXEに寄せたほうが扱いやすいです。1シナリオ1プロセスで回すとリークの差分も見やすく、設定のON/OFFも切り替えやすくなります。

著者プロフィール

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

小村 豪

合同会社小村ソフト 代表

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

ブログ一覧に戻る