用 Application Verifier 打造 Windows 異常情境測試基礎架構
· 更新日期: · Go Komura · Windows 開發, 故障調查, 工業相機, Application Verifier, 失敗路徑測試, 控制代碼洩漏
更新紀錄(2 筆,最後更新 2026年09月04日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
引用本文(DOI: 10.5281/zenodo.21616257)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
Go Komura(2026)。〈用 Application Verifier 打造 Windows 異常情境測試基礎架構〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616257 https://comcomponent.com/zh-TW/blog/2026/03/11/003-application-verifier-abnormal-test-foundation-part2/
- DOI(最新版本)
- 10.5281/zenodo.21616257
- DOI(此版本)
- 10.5281/zenodo.22297086
Application Verifier 是一個很有力的工具,適合用在想提前把 Windows 原生程式碼與 Win32 邊界上發生的異常攤到檯面上的時候。 特別是想測試控制代碼異常、堆積毀損、低資源時的 failure path 的場合,它能把只靠正常情境測試看不到的問題相當早地攤開來。
前篇 工業相機長期運轉當機調查 - 控制代碼洩漏篇 梳理了一個案例:調查長時間運轉後當掉的控制應用程式,最後發現原因是控制代碼洩漏。 不過,只是把日誌加強,事情才做了一半。真正想要的,是能先試出 今後就算因為非預期的程式錯誤而發生記憶體洩漏、控制代碼洩漏、中途失敗或釋放漏掉,是否仍處於「知道發生了什麼」的狀態。
於是我們用上了 Application Verifier。 它是一個能對在 Windows 原生程式碼與 Win32 邊界上執行的處理,在執行階段插入檢查與 fault injection 的工具。實務上特別方便的地方是:就算沒有真的把機器的記憶體吃光,也能提前製造出像記憶體不足或資源不足那樣的壞法。
後篇要在工業相機控制應用程式的脈絡下,梳理 Application Verifier 是什麼、能做什麼,以及要怎麼把它編進異常情境測試基礎架構。
目錄
- 先講結論(一句話)
- 什麼是 Application Verifier
- 2.1. 一句話說明
- 2.2. 在什麼場面派得上用場
- 2.3. 好處在哪裡
- 2.4. 取得工具並在自己的機器上啟用
- 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.1. 目的不只是「找出 bug」
- 4.2. 製造像記憶體不足的現象
- 4.3. 確認控制代碼異常發生時追不追得動
- 怎麼製造記憶體不足或資源不足那樣的現象
- 5.1. Low Resource Simulation 的思路
- 5.2. 可以讓什麼失敗
- 5.3. 實務上怎麼套用
- 怎麼看控制代碼異常
- 6.1.
Handles檢查 - 6.2. 用
!htrace看 open / close 的堆疊 - 6.3. 怎麼和自製日誌搭配
- 6.1.
- 異常情境測試基礎架構怎麼做
- 7.1. 把執行單位收攏到 harness
- 7.2. 把測試選單分開
- 7.3. 要收集的東西
- 7.4. 合格條件
- 7.5. 注意事項
- 大致的取捨
- 總結
- 參考資料
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 17 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
1. 先講結論(一句話)
- Application Verifier 是一個讓 Windows 非受管理/原生邊界 上發生的誤用更容易在執行階段被找出來的工具
- 方便的地方不只是「找出 bug」,更在於 能提前製造出平常很難出現的異常情境
Handles可以偵測 invalid handle,Heaps可以讓堆積毀損顯現出來,Low Resource Simulation可以對記憶體不足或資源不足那樣的狀況做 fault injection- 把長時間常駐的 EXE 的 leak 調查全部丟給 Application Verifier,方向並不對,搭配
Handle Count與 resource lifecycle 的自製日誌才實際 - 在異常情境測試基礎架構裡,把 正常情境的 verifier run 與 fault injection run 分開跑,結果比較好讀
- 就算想測試 DLL,啟用 Application Verifier 的對象也是實際會執行那個 DLL 的 測試用 EXE
簡單說,Application Verifier 就是 把窩在 Windows native / Win32 周邊的「難纏 bug」硬拖到檯面上的工具。 特別是像裝置控制應用程式這種 native SDK、P/Invoke、Win32 API 本來就混在一起的世界,契合度相當高。
flowchart TB
accTitle: Application Verifier 的兩項作用
accDescr: 說明 Application Verifier 具有偵測原生邊界的誤用與提前製造平常難以出現的異常情境這兩項作用,可以偵測 invalid handle 與堆積毀損,也可以對像記憶體不足的狀況注入失敗的圖。
av["Application Verifier"] --> detect["偵測原生邊界的誤用"]
av --> inject["提前製造難以出現的異常情境"]
detect --> d1["invalid handle 與堆積毀損"]
inject --> d2["注入像記憶體不足的狀況"]
圖 1: Application Verifier 的作用有兩根支柱:「偵測誤用」與「提前製造異常情境」。
2. 什麼是 Application Verifier
2.1. 一句話說明
Application Verifier 是針對 Windows user-mode 應用程式的 執行階段驗證工具。 它會監控執行中應用程式如何使用 OS API、如何處理資源,藉此偵測可疑的用法,也可以刻意注入失敗。
和「靜態分析」、「單元測試」不同,它是用來看 實際走過那條程式碼路徑時會怎麼壞 的工具。 所以很適合把平常的功能測試看不到的 failure path 逼出來。
flowchart LR
A[測試 harness] --> B[控制應用程式 / SDK 包裝層]
B --> C[Application Verifier]
C --> D[Win32 API / native DLL / OS 資源]
C --> E[verifier stop]
C --> F[debugger output]
C --> G[AppVerifier logs]
B --> H[自製 structured log]
圖 2: Application Verifier 監控由測試 harness 啟動的控制應用程式,並以 verifier stop、debugger 輸出與日誌留下觀測結果。
2.2. 在什麼場面派得上用場
特別容易派上用場的,是下面這些場面。
- 正在呼叫 native DLL 或相機 SDK
- 跨越 P/Invoke 或 COM
- 直接或間接大量使用 handle、heap、lock、virtual memory
- 一般的正常情境幾乎不會當掉,但只有異常情境下生命週期管理看起來會垮
- 比起「當掉」,先出現的是「偶爾回傳奇怪的失敗」
反過來說,它 並不是拿來追蹤純受管理世界裡 object graph 的工具。 所以 C# 應用程式只要 native SDK 或 Win32 邊界夠厚,它一樣很有效,但這不代表純受管理的 heap leak 靠它一支就能全部看完。
flowchart TB
accTitle: 判斷 Application Verifier 是否派得上用場
accDescr: 說明對 native DLL、P/Invoke 與 Win32 邊界厚重的應用程式它派得上用場,但它不是用來追蹤純受管理世界裡 object graph 的工具,並呈現這條分界的圖。
q{"問題出在哪一層"}
q -->|"native SDK 或 Win32 邊界"| yes["Application Verifier 派得上用場"]
q -->|"純受管理的 object graph"| no["不在守備範圍〔改用別的工具〕"]
圖 3: 派不派得上用場的分界,在於 native / Win32 邊界有多厚,它不是只看純受管理世界的工具。
2.3. 好處在哪裡
實務上的好處,大致是下面 3 個。
- 能早一步擋下原生邊界的誤用
- invalid handle
- heap corruption
- lock misuse
- virtual memory API misuse 等
- 能提前製造只有低資源時才會出現的壞法
- 相當於
malloc的呼叫偶爾失敗 CreateEvent或CreateFile偶爾失敗VirtualAlloc失敗
- 相當於
- 搭配 debugger 就好追
!avrf!htrace!heap -p -a- verifier stop 的日誌
裝置控制應用程式最讓人頭痛的,是「異常情境下不知道發生了什麼」。 Application Verifier 對減少那種「搞不清楚」相當有效。
flowchart TB
accTitle: 實務上的三個好處
accDescr: 說明能早一步擋下原生邊界的誤用、能提前製造只有低資源時才會出現的壞法、搭配 debugger 就好追這三個好處的圖。
av["Application Verifier"] --> b1["能早一步擋下誤用"]
av --> b2["能提前製造壞法"]
av --> b3["用 debugger 好追"]
b3 -.-> t["avrf 與 htrace 等擴充命令"]
圖 4: 實務上的好處,可以收攏成早期偵測、提前製造異常情境、debugger 連動這三點。
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 的 hook |
也就是說 用哪一邊,做的事情都一樣。手動操作的第一次用 GUI,CI 或指令碼用命令列,這樣分工就好。
flowchart TB
accTitle: GUI 與命令列的關係
accDescr: 說明 GUI 與命令列都只是寫入相同的登錄檔設定,目標 EXE 啟動時會讀取該設定載入 verifier 的 DLL 並掛上 Win32 API 的 hook 的圖。
gui["GUI〔appverif.exe〕"] --> reg["把設定寫進登錄檔"]
cli["命令列"] --> reg
reg --> boot["目標 EXE 啟動時參照"]
boot --> hook["載入 verifier 的 DLL 並掛 hook"]
圖 5: GUI 與命令列都只是在寫同一份登錄檔設定,hook 是在目標 EXE 啟動時才掛上去。
GUI 的操作流程是:在畫面左邊的 Applications 欄位按右鍵,用「Add Application」加入目標 EXE,然後在畫面右邊的 Tests 欄位勾選 Basics 之類的項目,再按「Save」。要解除時,一樣在 Applications 欄位按右鍵選「Delete Application」,然後「Save」。
由此衍生出 2 個重要的限制。
- 無法對正在執行中的處理程序事後啟用。 hook 是在 DLL 載入時掛上去的,所以順序一定是先設定再啟動。
- 設定會一直留著,直到明確刪除為止。 抱著「只是試一次」的心態放著不管,那台機器上的那個 EXE 就會一直在 verifier 之下啟動。
另外,偵測到問題時的日誌預設會以二進位格式存放在 %USERPROFILE%\AppVerifierLogs,可以用 GUI 或命令列轉成 XML 之後彙總。
flowchart TB
accTitle: 啟用的順序與設定的殘留
accDescr: 說明 hook 是在 DLL 載入時掛上去因此無法對執行中的處理程序事後啟用,順序必須是先設定再啟動,而設定會一直留到明確刪除為止的圖。
set["寫入設定"] --> launch["啟動目標 EXE"]
launch --> on["在 verifier 之下執行"]
on --> keep["設定會留到刪除為止"]
keep -.-> warn["放著不管就會一直在 verifier 之下啟動"]
running["執行中的處理程序"] -.-> ng["無法事後啟用"]
圖 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 或非同步處理較多時的輔助 |
重點在於,不是「當掉之後再讀就知道」,而是「看到可疑的用法就當場停住」。 在長時間運轉型的缺陷上,這種提前相當有效。
flowchart TB
accTitle: Basics 提前偵測的思路
accDescr: 說明不是等當掉之後讀日誌來推測,而是看到可疑用法就當場停住,藉此把長時間運轉型的缺陷提前攤開來的圖。
use["可疑的 API 用法"] --> basics["Basics 的檢查群"]
basics --> stop["當場停住"]
stop --> early["提前把問題攤開"]
use -.-> later["過去只能等當掉之後再讀"]
圖 7: Basics 的價值,在於把「當掉之後再讀」換成「當場停住」。
3.2. Low Resource Simulation:提前製造記憶體不足與資源不足
實務上相當方便的就是這裡。 因為 就算沒有真的把 RAM 吃光,也能製造出接近記憶體不足或資源不足的現象。
思路很單純。
- 對某個 API 呼叫
- 以一定機率
- 刻意讓它失敗
這樣就能走到平常幾乎不會走的 error path。
具體來說,下面這些現象會變得容易刻意製造出來。
HeapAlloc或VirtualAlloc失敗CreateFile失敗CreateEvent失敗MapViewOfFile失敗SysAllocString這類 OLE/COM 系的配置失敗
比起真的想弄出記憶體不足而讓整台機器受苦,這種做法好處理得多。 而且,還可以 只鎖定特定 DLL 注入 fault。像裝置控制應用程式這種自製包裝層與 vendor SDK 混在一起的結構,這點相當貼近實務。
flowchart TB
accTitle: Low Resource Simulation 的機制
accDescr: 說明以一定機率刻意讓某類 API 呼叫失敗,藉此刻意走到平常幾乎不會走的 error path,並且可以把對象縮小到特定 DLL 的圖。
call["API 呼叫"] --> judge{"命中設定的機率了嗎?"}
judge -->|"是"| fail["刻意回傳失敗"]
judge -->|"否"| ok["照常處理"]
fail --> path["走到平常不會走的 error path"]
path -.-> dll["也可以只鎖定特定 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 不是萬能的魔杖,而是 依場面換刀刃的工具。
flowchart TB
accTitle: page heap 的取捨流程
accDescr: 說明先用 Basics 大範圍打,覺得堆積有問題就用 full page heap 在壞掉的瞬間停住,太重就降到 light page heap,等同正式環境的長時間測試則以自製日誌為主的圖。
s1["用 Basics 大範圍打"] --> s2{"堆積有問題嗎?"}
s2 -->|"是"| s3["用 full page heap 停住"]
s2 -->|"否"| s7["長時間測試以自製日誌為主"]
s3 --> s4{"太重了嗎?"}
s4 -->|"是"| s5["降到 light page heap"]
s4 -->|"否"| s6["在 debugger 之下做局部重現"]
圖 9: page heap 不是隨時都開著的工具,先用 Basics 大範圍打,再依場面換刀刃。
3.4. !avrf / !htrace / 日誌
先講一個名詞。前面出現過好幾次的 verifier stop,是 Application Verifier 判斷「這種用法不對」時發出的偵測事件。它不只是一行日誌,特徵是 只要是在偵錯器之下執行,它就會當場中斷。stop 都有編號,會顯示成 VERIFIER STOP 00000300 這樣的形式。有些 stop 可以直接繼續執行,有些則不能繼續(只能把處理程序結束掉)。
Application Verifier 並不是發出 stop 就結束了。 它有偵錯器擴充命令與日誌,所以更容易追出發生了什麼。
!avrf- 查看目前的 verifier 設定,以及現在發生的 stop
!htrace- 查看 handle 的 open / close / invalid reference 的堆疊
!heap -p -a- 搭配 page heap,追出壞掉的堆積區塊
- AppVerifier 的日誌
- 可以留下 stop 發生時的日誌
特別是啟用 Handles 時,handle tracing 會自動啟用,這點很值得感謝。
這樣就能事後追出「這個 handle 是在哪裡開的、在哪裡關的」。
flowchart TB
accTitle: 從 verifier stop 到調查的流程
accDescr: 說明偵測到不對的用法就會發出帶編號的 verifier stop,在偵錯器之下會當場中斷,接著可以用 avrf 查看設定與 stop、用 htrace 查看控制代碼的履歷的圖。
bad["偵測到不對的用法"] --> stop["verifier stop〔帶編號〕"]
stop --> brk["在偵錯器之下會中斷"]
brk --> avrf["用 avrf 查看設定與 stop"]
brk --> ht["用 htrace 查看 handle 履歷"]
stop -.-> cont["stop 分成可繼續與不可繼續"]
圖 10: verifier stop 不只是一行日誌,在偵錯器之下會當場中斷,成為調查的起點。
4. 這次為什麼導入
4.1. 目的不只是「找出 bug」
這次的目的,不只是「用 AppVerifier 找出 1 個 bug」。 用更貼近實務的說法,想確認的是下面這幾點。
- 將來又在別的 failure path 發生資源洩漏時
- 日誌裡有沒有好好留下脈絡
- 能不能配合偵錯器的資訊追到底
- 會不會落到「不知道發生了什麼」的狀態
換句話說,我們不只是把它當成 偵測器,也把它當成 觀測基礎架構的測試 來用。
4.2. 製造像記憶體不足的現象
在平常的開發機上真的弄出記憶體不足,其實相當麻煩。 而且整台機器一旦變得不穩定,測試本身就會充滿雜訊。
所以改成用 Low Resource Simulation,刻意去踩那些在記憶體不足或資源不足時可能會走到的 failure path。
這樣就比較容易回答下面這些問題。
CreateEvent失敗時,日誌裡還會留下cameraId與phase嗎- 在半吊子的初始化之後,clean up 有確實跑起來嗎
VirtualAlloc失敗時,重試會不會把東西弄壞- save path 的
CreateFile失敗時,控制代碼有回收嗎
要強調的是,製造異常本身不是目的,異常發生時能讀懂壞法才是目的。
flowchart TB
accTitle: 用 fault injection 檢驗觀測基礎架構
accDescr: 說明用 Low Resource Simulation 刻意踩到失敗,檢查日誌會不會留下脈絡、後續收拾會不會跑、重試會不會弄壞東西,目的不是製造異常而是壞了能讀懂的圖。
inject["刻意踩到失敗"] --> q1["日誌會留下脈絡嗎"]
inject --> q2["clean up 會跑嗎"]
inject --> q3["重試會不會弄壞"]
q1 --> goal["壞法讀得懂的狀態"]
q2 --> goal
q3 --> goal
圖 11: fault injection 的目的不是製造異常,而是確認異常時的壞法讀不讀得懂。
4.3. 確認控制代碼異常發生時追不追得動
前篇出現的控制代碼洩漏也是如此,控制代碼相關的問題 最後當掉的地方和真正的原因很容易對不上。
所以想確認的是下面這些事。
- 出現 invalid handle stop 時,能不能用
!htrace追 open / close - 能不能和自製日誌的
resourceId/sessionId/phase連起來 - 失敗之後 handle count 會不會回落
- 把 harness 做成短命處理程序時,洩漏的差異看不看得清楚
看到這個程度,就能從單純的「出 bug 了」,走到「是哪個職責的生命週期管理垮掉」。
flowchart TB
accTitle: 確認控制代碼異常時追不追得動
accDescr: 說明出現 invalid handle stop 時能否用 htrace 追 open 與 close、能否和自製日誌的脈絡連起來、handle count 會不會回落,並一路走到鎖定生命週期管理垮掉的職責的圖。
stop["invalid handle stop"] --> c1["用 htrace 追 open 與 close"]
stop --> c2["和自製日誌的脈絡連起來"]
stop --> c3["確認 handle count 是否回落"]
c1 --> goal["鎖定生命週期管理垮掉的職責"]
c2 --> goal
c3 --> goal
圖 12: 控制代碼異常不要停在「出 bug 了」,要確認能否追到是哪個職責的生命週期管理垮掉。
5. 怎麼製造記憶體不足或資源不足那樣的現象
5.1. Low Resource Simulation 的思路
Low Resource Simulation 就是所謂的 fault injection。 與其說是把低資源環境原樣重現,不如說是 人為地摻入低資源時會發生的代表性 API 失敗。
所以,它的用處相當明確。
- 檢查 failure path 的善後
- 檢查 retry / reconnect 夠不夠硬
- 檢查中途成功與中途失敗交錯的初始化
- 檢查「平常不會發生的失敗」是否也會留下日誌
這裡的訣竅是 不要一開始就什麼都讓它失敗。 一下子全開,日誌會爆掉,變得搞不清楚「自己在看什麼」。
flowchart TB
accTitle: fault injection 的縮小範圍方式
accDescr: 說明一開始就什麼都讓它失敗會導致日誌爆掉而搞不清楚自己在看什麼,因此要從最接近想看的 failure path 的失敗開始逐步打開的圖。
all["一開始就全部讓它失敗"] --> noise["日誌爆掉讀不下去"]
narrow["只打開想看的失敗"] --> clear["清楚知道自己在看什麼"]
圖 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
光是複製貼上而讀不懂用意就沒有意義,所以逐行寫下每一行在做什麼。
| 命令 | 做什麼 |
|---|---|
appverif /verify CameraHarness.exe |
對 CameraHarness.exe 啟用 Basics 這組測試 |
appverif /verify CameraHarness.exe /faults |
在上面的基礎上再啟用 fault injection。不過對象只有 OLE_ALLOC 與 HEAP_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 目的相同) |
引數的讀法也先弄清楚。
- 機率的單位是百萬分率。 可以指定的是 0 〜 1,000,000 的整數,
20000就是20000 / 1,000,000,也就是 2%,並不是「兩萬次失敗一次」。Microsoft 的文件也把-with registry=20000 file=20000當成讓登錄檔與檔案的 API 以 2% 失敗的範例。 /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
Include 與 Exclude 是對象模組的篩選,TimeOut 是啟動後不注入 fault 的時間。預設值會隨設定的寫法而變,所以不要憑印象,用這份輸出來查看最保險。
思路大致是這樣。
- 先只用
Basics跑正常情境 - 接著加上
Low Resource Simulation,跑有 fault injection 的版本 - 必要時只給
file或event等想看的失敗設定機率 - 只想鎖定特定 DLL 的話,就把注入範圍縮到那個 DLL
/faults 這個捷徑很方便,但只用它的話 會以 OLE_ALLOC 與 HEAP_ALLOC 為主。
想看 CreateFile 或 CreateEvent 的 failure path,還是寫到 -enable lowres -with file=... event=... 比較保險。
在裝置控制應用程式上,與其把 fault 撒滿整個應用程式,不如縮到 camera wrapper 或 save path 的 DLL,結果通常好讀得多。
flowchart TB
accTitle: 套用 fault injection 的順序
accDescr: 說明先只用 Basics 跑正常情境,接著加上 Low Resource Simulation 跑有 fault injection 的版本,只給想看的失敗設定機率,必要時再縮到特定 DLL 的階段式套用方式的圖。
s1["只用 Basics 跑正常情境"] --> s2["加上 Low Resource 再跑一次"]
s2 --> s3["只給想看的失敗設定機率"]
s3 --> s4["縮到特定 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.exe 的 Include 與 Exclude 這兩行查看。這裡還是 * 的話,對象就仍是整個處理程序。
flowchart TB
accTitle: 縮到 DLL 的 fault injection 如何運作
accDescr: 說明用 faults 的引數指定機率、寬限時間與對象模組後,啟動經過寬限時間才會只讓指定 DLL 發起的操作以指定機率失敗,並可用 query 的 Include 與 Exclude 查看篩選結果的圖。
arg["指定機率、寬限時間與 DLL 名稱"] --> grace["啟動後的寬限時間內不注入"]
grace --> target["只讓指定 DLL 發起的操作失敗"]
target --> check["用 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
- 生命週期垮掉,從別的執行緒去碰
在長時間運轉下看只是「偶爾出現奇怪的錯誤」的狀況,在 verifier 之下有時會當場停住。 這種提前相當有幫助。
flowchart TB
accTitle: Handles 檢查派得上用場的出包
accDescr: 說明重複使用已 close 的控制代碼、壞掉的控制代碼值、因中途失敗而未初始化的控制代碼、生命週期垮掉後從別的執行緒誤用等出包,在 verifier 之下可以當場停住的圖。
a1["重複使用已 close 的"] --> stop["當場 verifier stop"]
a2["壞掉的 handle 值"] --> stop
a3["未初始化的 handle"] --> stop
a4["生命週期垮掉的誤用"] --> stop
圖 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 av、sxd ch、sxd 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。最後一行就是摘要。
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
--------------------------------------
讀法很直接了當:只要 BAD REFERENCE 出現在 CLOSE 之後,就表示已經關掉的控制代碼又被用了一次。看 OPEN 的堆疊,也能知道那個控制代碼是在哪裡建立的。
控制代碼洩漏或 handle misuse 麻煩的地方,在於 最後跌倒的那個 API 並不是真正的原因。
有了 !htrace,就能相當具體地追出那個 handle 的履歷。
flowchart TB
accTitle: 用 htrace 讀控制代碼履歷的方法
accDescr: 說明 htrace 會把控制代碼的 OPEN、CLOSE、BAD REFERENCE 各自帶著堆疊排出來,只要 BAD REFERENCE 出現在 CLOSE 之後就是關掉的控制代碼被重複使用,看 OPEN 的堆疊也能知道建立的位置的圖。
open["OPEN〔建立的位置〕"] --> close["CLOSE〔關閉的位置〕"]
close --> bad["BAD REFERENCE"]
bad --> mean["判定為已關閉 handle 的重複使用"]
open -.-> stack["每一筆紀錄都帶著堆疊"]
圖 17: htrace 的讀法很直接了當,只要 BAD REFERENCE 排在 CLOSE 之後,就是已關閉控制代碼的重複使用。
6.3. 怎麼和自製日誌搭配
話雖如此,光有 Application Verifier 還不夠。 特別是長時間常駐的 EXE 的 leak 調查,只靠它做會相當吃力。
所以實務上會搭配下面這些。
- 定期記錄的
Handle Count sessionIdresourceIdphase- create/open 與 close/dispose 的 lifecycle log
- verifier stop 時的 dump 與 debugger 輸出
這樣一來,舉例來說可以這樣追。
- 從 heartbeat 發現
Handle Count的斜率不對勁 - 用 lifecycle log 鎖定有
Create卻沒有Close的 resource - 用 verifier run 提前把 invalid handle 與 misuse 逼出來
- 用
!htrace看 open / close stack
這套組合技一上,就好追多了。
flowchart TB
accTitle: 自製日誌與 verifier 的組合技步驟
accDescr: 說明先從 heartbeat 察覺 Handle Count 的斜率,再用 lifecycle log 鎖定沒有 Close 的資源,接著用 verifier run 提前逼出誤用,最後用 htrace 看 open 與 close 的堆疊的順序的圖。
s1["察覺 Handle Count 的斜率"] --> s2["用 lifecycle log 鎖定資源"]
s2 --> s3["用 verifier run 提前逼出誤用"]
s3 --> s4["用 htrace 看堆疊"]
圖 18: 斜率的偵測交給自製日誌,誤用的偵測交給 verifier,這樣的分工就照這個順序串起來。
7. 異常情境測試基礎架構怎麼做
7.1. 把執行單位收攏到 harness
Application Verifier 無法對正在執行中的處理程序事後啟用。 順序是先設定再啟動。
而且,設定會一直留到明確刪除為止。 所以實務上,把對象 從正式應用程式本體收攏到測試用的 harness EXE 會更好處理。
舉例來說,是這樣的結構。
flowchart LR
A[Scenario Runner] --> B[CameraHarness.exe]
B --> C[CameraSdkWrapper.dll]
C --> D[Vendor SDK]
B --> E[Structured Log]
B --> F[Dump / Debugger]
圖 19: 一個情境一個處理程序來跑的 harness 結構。verifier 的對象不是 DLL,而是驅動它的 harness EXE。
這樣一來,
- 可以一個情境一個處理程序來跑
- leak 的差異容易看
- AppVerifier 設定的 ON/OFF 容易切換
- 想測試 DLL 時也能從 EXE 這一側處理
有這些好處。
命令的樣子是這樣。
appverif /verify CameraHarness.exe
appverif /n CameraHarness.exe
/verify 是啟用 Basics,/n 是刪除設定(也請參照 5.3 的表)。
啟用要在啟動前,解除要明確執行。
把這一帶都放在 harness 的前提下運作,設定造成的出包也比較容易減少。
7.2. 把測試選單分開
在異常情境測試基礎架構裡,最好不要一次全部做完。 大致分成下面 3 條線,結果比較好讀。
- 正常情境 + Basics
- 不注入任何失敗
- 確認不會出現 verifier stop
- fault injection 系列
Low Resource Simulation- 鎖定
event/file/heap_alloc/virtual_alloc等讓它們失敗
- heap 深挖系列
Heaps- full page heap
- 在 debugger 之下做局部重現
把這裡分開, 「是平常的用法本身就在壞」 和 「只有低資源時才壞」 就不容易攪在一起。
特別是有沒有 fault injection,會讓實際走過的 code path 差很多。 所以 沒有 fault 的 run 和 有 fault 的 run 兩邊都跑比較好。
flowchart TB
accTitle: 分成三條線的測試選單
accDescr: 說明把測試分成不注入任何失敗的正常情境加 Basics、用 Low Resource Simulation 鎖定目標讓它失敗的 fault injection 系列、把 full page heap 放在 debugger 之下跑的 heap 深挖系列這三條線的圖。
menu["異常情境測試的跑法"] --> m1["正常情境與 Basics"]
menu --> m2["fault injection 系列"]
menu --> m3["heap 深挖系列"]
m1 -.-> p1["確認不會出現 stop"]
m2 -.-> p2["注入鎖定好的失敗"]
m3 -.-> p3["在 debugger 之下局部重現"]
圖 20: 不要一次全做完,分成三份選單,哪裡在壞就不會攪在一起。
7.3. 要收集的東西
最低限度,希望至少留下這些。
| 種類 | 想要的東西 |
|---|---|
| 應用程式日誌 | cameraId、sessionId、phase、handleCount、error code |
| 處理程序狀態 | 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 追查 - 不會變成「不知道發生了什麼」的失敗
這裡重要的是, 把 不會壞 和 壞了追得動 分開來評估。
flowchart TB
accTitle: 合格條件的兩個軸
accDescr: 說明把合格條件分成正常情境下不出現 verifier stop 且資源會被收拾掉的不會壞這一軸,以及預期中的失敗會留在日誌並能用脈絡與堆疊追查的壞了追得動這一軸來評估的圖。
pass["合格條件"] --> a["不會壞"]
pass --> b["壞了追得動"]
a --> a1["不出現 stop"]
a --> a2["資源會被收拾掉"]
b --> b1["失敗會留在日誌"]
b --> b2["可以用堆疊追查"]
圖 21: 只有「沒當掉」太弱,要把不會壞與追得動當成兩條不同的軸來評估。
7.5. 注意事項
Application Verifier 相當方便,但它不是魔法。
- 實際沒有走過的程式碼路徑不會被驗證
- full page heap 很重
- 有時 stop 會出在 third-party SDK 那一側
- 有沒有 fault injection,走過的程式碼路徑差很多
- 它不是用來單靠一支處理純受管理 heap leak 調查的工具
所以,它的定位是這樣。
- 長時間的斜率 交給自製日誌與 counters
- native 邊界的誤用 交給 Application Verifier
- 異常時的因果還原 交給 structured log + dump + debugger
這樣的分工最貼近實務。
flowchart TB
accTitle: 調查分工的全貌
accDescr: 說明長時間的斜率交給自製日誌與 counters、native 邊界的誤用交給 Application Verifier、異常時的因果還原交給 structured log 與 dump 與 debugger 的分工的圖。
q1["長時間的斜率"] --> t1["自製日誌與 counters"]
q2["native 邊界的誤用"] --> t2["Application Verifier"]
q3["異常時的因果還原"] --> t3["日誌與 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 - Overview
- Application Verifier - Testing Applications
- Application Verifier - Tests within Application Verifier
- Application Verifier - Debugging Application Verifier Stops
- Application Verifier - Features
- !htrace (WinDbg)
- !avrf (WinDbg)
- Download Debugging Tools for Windows
- Windows SDK 下載
- GetProcessHandleCount 函式 (processthreadsapi.h)
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
工業相機長期運轉當機調查 - 控制代碼洩漏篇
本文以工業相機控制應用程式的案例,從找出控制代碼洩漏與日誌設計這兩個角度,梳理 Windows 應用程式長時間運轉後突然當掉時該怎麼看。
TCP 重送導致工業相機通訊停頓的原因與釐清方法
本文梳理工業相機通訊因 TCP 重送而停頓數秒時的釐清方法,並一併說明封包遺失、RTO、RFC1323 timestamp 與 Wireshark 的檢查重點。
Time Travel Debugging ── 把長期運轉中無法重現的問題「錄下來」再倒回去
一個月才出現一次的問題,當機傾印只拍得到結果。本文說明如何用 WinDbg 的 Time Travel Debugging(TTD) 錄下執行並倒回,涵蓋 TTD.exe 的錄製設計、環形緩衝區、TTD.Calls 查詢,以及和傾印的分工。
從睡眠恢復就壞掉的應用程式 ── 電源事件的機制,以及耐得住恢復的業務應用程式寫法
打開筆電,業務應用程式的連線卻斷了──原因是設計從未把睡眠算進去。本文依一次資訊整理 WM_POWERBROADCAST 的通知流程、Modern Standby 的行為、斷線/重連設計、睡眠抑制,以及調查指令。
DllMain 與載入器鎖定 ── 「DLL 初始化時什麼都別做」真正的理由
為什麼不能在 DllMain 裡呼叫 LoadLibrary 或與其他執行緒同步?本文依據一次資料,從序列化所有 DLL 通知的載入器鎖定機制,一路說明到死結成立的典型情境、延遲初始化等正確設計,以及無回應的調查步驟。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
故障調查 & 長期運行故障
整理間歇性故障、通訊診斷、長期運行當機、失敗路徑測試基礎的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
故障調查 & 根本原因分析
Application Verifier 與異常情境測試基礎架構,正是推動故障重現與原因鎖定的故障調查與根本原因分析服務的核心主題。
技術諮詢 & 設計審查
如果想釐清異常情境測試與觀測點該納入設計到什麼程度,可以用技術諮詢與設計審查的形式一起討論。
常見問題
整理諮詢這個主題時常見的問題。
- 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 上更好處理。以一個情境一個處理程序來跑,洩漏的差異也比較容易看出來,設定的開關也比較好切換。