Process Explorer / Handle / VMMap 實戰 ── 從「此刻」的狀態追查無回應、洩漏、「檔案使用中」
· Go Komura · Sysinternals, Process Explorer, VMMap, 控制代碼洩漏, 記憶體洩漏, 故障調查, Windows, 長時間運轉
「星期一早上重開機就會好了」── 這是我們在裝置連動應用程式的維護諮詢中,聽過無數次的一句話。剛啟動時運作順暢,但到了星期四左右畫面切換開始變得遲鈍,星期五傍晚甚至按下按鈕後要好幾秒才有反應。偶爾還會冒出從沒見過的「控制代碼無效」錯誤。而只要重新啟動,就會像什麼事都沒發生過一樣恢復正常。演變到這個地步,維運方式就固定成了「每週重開機」,而根本原因始終沒有人知道,就這樣過了好幾年。
這類症狀,無論再怎麼盯著記錄檔看,都找不到原因。真正需要的是直接查看「此刻,這個處理程序究竟抱著什麼、抱著多少」。上一篇「Process Monitor(ProcMon)實戰指南」處理的是記錄操作時間序列的 ProcMon。這次作為續篇、Sysinternals 實戰系列的第 2 彈,將以「看狀態」這一側的工具 ── Process Explorer、Handle、VMMap ── 沿著長時間運轉應用程式的三大症狀「愈用愈重」「檔案刪不掉」「無回應」來做整理。
本文的前提環境如下。
- OS ── Process Explorer 官方的執行需求,用戶端為 Windows 11 以上,伺服器為 Windows Server 2016 以上。1
- 位元數 ── 在 64 位元環境下啟動
procexp.exe,會自動展開並執行 64 位元版本。要正確查看 64 位元處理程序的堆疊與控制代碼,必須以這個 64 位元版本執行。 - 權限 ── 以調查為目的時,「以系統管理員身分執行」實質上是必要條件。若維持一般權限,僅限於其他使用者或服務的處理程序,執行緒堆疊與控制代碼清單都會出現「存取被拒」。CLI 版的
handle.exe官方文件明確要求必須具備系統管理員權限。2 - 首次啟動 ── 會出現 EULA(使用授權合約)同意對話方塊。在指令碼或無人執行的情況下,可用
/accepteula開關明確表示同意(第 8 章)。
另外,本文雖然說明的是 GUI 工具的操作,但並未附上畫面截圖。取而代之的是,文中直接沿用實際的選單名稱與快速鍵寫法,讓讀者可以一邊在手邊開啟工具、一邊閱讀。
1. 先講結論
- ProcMon 是「歷史」,Process Explorer 是「現在」。Process Explorer 是可以列出並搜尋每個處理程序所開啟的控制代碼與已載入 DLL 的工具,官方也明確表示它適合用於調查 DLL 版本問題與控制代碼洩漏。1
- 「檔案刪不掉、換不掉」時,Find Handle or DLL(Ctrl+F)是最快的捷徑。只要用路徑的一部分搜尋,就能在數秒內鎖定佔用該檔案的處理程序(第 3 章)。
- 「無回應」時,可在處理程序的 Properties → Threads 分頁查看執行緒堆疊。但若沒有設定符號(dbghelp.dll 與符號路徑),看到的就只會是一堆位址(第 4 章)。13
- 「愈用愈重」要靠新增欄位來觀察斜率。新增 Handle Count、USER Objects、GDI Objects、Private Bytes 這 4 個欄位,經過一段時間後,找出持續增加的項目。GDI/USER 物件都有各自的處理程序上限,一旦達到上限,繪圖與視窗建立就會開始出問題(第 5 章)。4
- CLI 版的 handle.exe 適合用指令碼做定點觀測。用
handle -s -p <處理程序>可以定期記錄各類別的控制代碼數。用-c強制關閉控制代碼一事,官方已警告可能導致不穩定,基本上不會使用。2 - 「Private Bytes 持續增加」時,用 VMMap 拆解其組成。若是 Heap 就懷疑原生程式碼,若是 Managed Heap 就懷疑 .NET,先決定好犯人所在的擂台,再進入專用工具(第 6 章)。5
- 當可疑的不是處理程序而是整個 OS 的記憶體時,就用 RAMMap。6 若是當機,則切換為用 ProcDump 採集傾印檔(「Windows 當機傾印檔採集入門」)。
- 不管哪個工具都不需要安裝,也能直接從 Sysinternals Live 執行。雖然容易帶入離線的裝置 PC,但唯獨符號需要事前準備(第 8 章)。7
2. Process Explorer 的基本 ── 以系統管理員身分執行、取代工作管理員、畫面判讀
Process Explorer 不需要安裝,只要解壓縮 ZIP 並執行 procexp.exe(64 位元環境會自動改跑 procexp64)即可。1 由於這是一款能看到其他使用者處理程序與服務內部狀態的工具,調查時務必「以系統管理員身分執行」。若以一般權限啟動,偏偏就是關鍵的那個處理程序會出現「存取被拒」,堆疊與控制代碼都看不到。
在負責調查的機器上,只要啟用 Options → Replace Task Manager,透過 Ctrl+Shift+Esc 或工作列叫出工作管理員時,就會直接改成開啟 Process Explorer。「緊急時刻打開的是自己用慣的工具」,這個效果看似不起眼,實際上影響不小,本公司的開發機也全數做了這項替換(要改回來,同樣從這個選單操作)。
畫面採上下兩窗格結構:上方是處理程序樹狀圖,下方是所選處理程序的詳細資訊。下方窗格可透過 View → Lower Pane View 切換Handles 檢視(已開啟控制代碼的清單)與DLLs 檢視(已載入 DLL 與記憶體映射檔案的清單)。1 首先該記住的是列的顏色代表的意義(可透過 Options → Configure Colors 確認、變更)。
| 顏色(預設) | 意義 |
|---|---|
| 綠色 | 剛啟動的處理程序(預設約 1 秒) |
| 紅色 | 即將結束的處理程序 |
| 淺藍色 | 與自己相同使用者帳戶的處理程序 |
| 粉紅色 | 承載服務的處理程序 |
| 紫色 | 疑似被封裝(壓縮、混淆)的執行檔 |
| 深灰色 | 已暫停的處理程序 |
「一瞬間冒出綠色的處理程序,隨即變紅然後消失」這種不斷重複發生的異常,光靠樹狀圖與顏色區分就能發現。由於處理程序的親子關係在樹狀圖中一目瞭然,「這個 conhost.exe 是誰的子處理程序」「啟動這個應用程式的是服務還是工作排程器」也能一眼看出。
3. 「檔案使用中無法刪除」── 用 Find Handle or DLL 鎖定犯人
「記錄檔刪不掉」「想更新卻發現 exe 使用中」「USB 隨身碟無法安全退出」── 這類症狀的調查,用 Find → Find Handle or DLL(Ctrl+F)就能結束。1 操作步驟共 5 步。
- 以系統管理員身分執行 Process Explorer(若忘記這一步,當佔用者是服務或其他使用者的處理程序時就看不到)
- 開啟選單的 Find > Find Handle or DLL…(快速鍵 Ctrl+F)
- 在搜尋字串輸入欄中,輸入檔案名稱或資料夾名稱的一部分(例如:
report.csv、D:\Data)。不需要輸入完整路徑,系統會以部分一致的方式搜尋 - 按下 Search。開啟了包含該名稱的控制代碼的處理程序,或已載入該 DLL 的處理程序,都會列在清單中
- 點擊結果中的某一列,上方窗格會選取對應的處理程序,下方窗格則會醒目提示對應的控制代碼
找到之後的正確做法,是「讓那個處理程序正常結束」。雖然也可以在 Process Explorer 上對控制代碼按右鍵,用 Close Handle 強制關閉,但基於與後述 handle -c 相同的理由,不建議在正式環境中這麼做。
請先掌握這種搜尋方式的限制。能命中的只有具名物件。無名的事件、Mutex、執行緒控制代碼,不管開了多少個,都無法用名稱搜尋。對於像「檔案刪不掉」這種存在路徑名稱的調查,這個方法最為有力,但在第 5 章的控制代碼洩漏調查中,這個方法就派不上用場,必須改用種類別統計(handle -s),以及將下方窗格的 Handles 檢視依 Type 排序的方法(第 7 章的案例研究就是實際範例)。
DLLs 檢視也可用來確認「實際載入的是哪個位置的哪個版本的 DLL」。要確認「掌握到的是舊版 DLL」「已部署的修正版沒有被讀取」這類 DLL 版本問題,這個畫面正好與前篇 ProcMon 文章 5.2 節「唯獨在這個環境無法啟動 ── 追蹤 DLL 的搜尋過程」相對應。那一節是依時間序列追蹤「按搜尋順序排列的 NAME NOT FOUND,最終在哪裡出現 SUCCESS(或者始終找不到)」,能得知搜尋過哪裡;而這裡的 DLLs 檢視,能得知最終掌握的是哪裡的什麼東西。詳情請參閱「Process Monitor(ProcMon)實戰指南」。
話說回來,「使用中的 exe/DLL 該如何安全替換」,這並不是調查問題,而是設計問題。同日發布的姊妹文章「如何替換使用中的 exe/DLL」處理了包含 Restart Manager 在內的替換設計,若您是負責打造更新機制的一方,請參閱該文。
4. 「無回應」── 用 Threads 分頁讀取執行緒堆疊
視窗變白、沒有回應。在強制結束之前,請先在 Threads 分頁查看執行緒的堆疊。操作步驟共 4 步。
- 在上方窗格中對目標處理程序按兩下(或按右鍵 → Properties…),即可開啟 Properties 視窗
- 切換到 Threads 分頁。畫面會列出每個執行緒的 CPU 使用率、循環數、起始位址
- 選擇要查看的執行緒。持續佔用 CPU 的執行緒,或起始位址落在應用程式本體 EXE 的執行緒(多數情況下這就是 UI 執行緒),是最先該檢查的候選
- 按下 Stack 按鈕。畫面會依呼叫的新舊順序,顯示該執行緒目前停在哪個函式呼叫的途中
大多數的無回應,都是因為「UI 執行緒正在等待某個東西」,所以只要在堆疊上方看到 WaitForSingleObject、EnterCriticalSection,或同步的網路、序列 I/O 等待,那就是直接原因。
不過,若未設定符號,堆疊會變成位址與「模組名稱+0x1234」的羅列,幾乎無法閱讀。請在 Options → Configure Symbols 中設定以下兩項。
Dbghelp.dll path:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll
※ 指定 WinDbg(Debugging Tools for Windows)所附帶的檔案
Symbols path:
srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
※ C:\Symbols 為本機快取。第 2 次以後會從這裡讀取
這裡有兩個要點。首先,System32 中內建的預設 dbghelp.dll 不支援從符號伺服器下載,因此必須指向 WinDbg 附帶的版本。其次,如官方文件所述,若要使用符號伺服器,指定的 dbghelp.dll 所在位置必須同時存在 symsrv.dll(若是 WinDbg 的安裝資料夾,兩者都會齊備)。1 符號路徑的 srv*快取*伺服器URL 這種寫法,以及 Microsoft 的公開符號伺服器 https://msdl.microsoft.com/download/symbols,都是與 WinDbg 共通的機制。3 要讀取自家應用程式的堆疊,也需要自家建置的 PDB,因此請在符號路徑中,用分號分隔,加上 PDB 存放資料夾的路徑。
這個 dbghelp.dll 該從哪裡取得,是這裡最先卡關的地方。上面路徑中出現的 C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\,是安裝 Debugging Tools for Windows 後產生的資料夾。取得途徑有 3 種。8
| 取得途徑 | 適合的場合 |
|---|---|
| 在 Windows SDK 安裝程式中只勾選「Debugging Tools for Windows」 | 對本文用途而言這是最快的做法。只要取消勾選其他所有功能再執行,就能不安裝 SDK 本體、只安裝偵錯器8 |
| 作為 Windows SDK / WDK 的一部分安裝 | 開發機上還有其他用途需要用到 SDK 時 |
單獨安裝 WinDbg(winget install Microsoft.WinDbg 或 Microsoft Store) |
想使用 WinDbg 本體時。不過因為是以套件形式安裝,不會像上面那樣有固定路徑9 |
由於 Process Explorer 中設定的是 dbghelp.dll 的檔案路徑,請在安裝完成後,先確認該資料夾內 dbghelp.dll 與 symsrv.dll 是否並列存在,再進行指定。
.NET 應用程式的情況下,Threads 分頁的堆疊以原生框架為主,受管理方法的名稱有時無法正確顯示。若要認真追查受管理的無回應、死結,在無回應期間採集傾印檔、以 WinDbg + SOS 查看會更可靠(「用 WinDbg + SOS 解讀當機傾印檔」)。Threads 分頁是「當場花 3 分鐘找出頭緒」的工具,傾印檔則是「帶回去確定原因」的工具,兩者的分工大致如此。另外,Threads 分頁的 Kill/Suspend 按鈕,請不要對正式環境的處理程序按下。原本只是想觀察,卻會變成介入。
5. 「愈用愈重」── 用控制代碼數、USER/GDI 欄位與 handle.exe 監控洩漏
長時間運轉後會逐漸劣化的應用程式,其經典原因就是控制代碼、GDI/USER 物件、記憶體的洩漏。Process Explorer 作為監控工具也表現優異,可透過 View → Select Columns 新增以下欄位。
- Process Performance 分頁 → Handle Count(核心物件的控制代碼數)
- Process Memory 分頁 → Private Bytes、Virtual Size、USER Objects、GDI Objects
要看的不是絕對值,而是斜率。健全的應用程式,控制代碼數會隨操作增減,但收斂在一定範圍內;一旦發生洩漏,就會呈現「每次操作都增加、再也不會減少」的右肩上升走勢。哪怕只是每隔 1 小時,或早晚各留一張截圖,隔天也能看出趨勢。GDI 物件與 USER 物件都有各自處理程序的上限(預設為 1 萬個,可透過登錄檔的 GDIProcessHandleQuota 等變更),以及整個工作階段層級 65,536 個的理論上限,一旦達到上限,筆刷、畫筆的建立與視窗的建立就會開始失敗。4 「一到星期五畫面就會亂繪」的真面目,多半就是這個。
在無法開啟 GUI 的環境,或想用指令碼做定點觀測時,可使用 CLI 版的 handle.exe。這是一款用於列出、搜尋控制代碼的命令列工具,需要系統管理員權限。2
:: 這個檔案/資料夾目前是誰佔用著(以名稱部分一致搜尋)
handle.exe /accepteula report.csv
handle.exe D:\Data
:: 傾印除檔案以外的所有種類控制代碼,並以處理程序名稱篩選
handle.exe -a -p MyEquipApp
:: 統計各類別的控制代碼數 ── 洩漏的定點觀測就靠這個記錄到日誌
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%date:~0,4%%date:~5,2%%date:~8,2%.log
補充說明第 3 行的 %date:~0,4%。這是環境變數的子字串展開,不管是在批次檔內還是直接在命令提示字元中輸入,寫法都是同一個 %(需要把 % 加倍寫成 %% 的,是 for 的變數,以及想輸入字面上的 % 時,這個寫法並不適用)。
不過,%date% 的內容取決於作業系統的短日期格式,所以像 ~0,4(前 4 個字元=年)這種偏移量,會隨環境而錯位。日文版 Windows 的預設格式是 yyyy/MM/dd,符合上面的例子,但若裝置 PC 的地區設定不同,產生出來的檔名就會是破碎的形式。若是要跨環境發布的定點觀測指令碼,至少日期的產生方式,請改用格式固定的方法。
:: 不依賴環境日期格式的寫法(寫在批次檔中時)
for /f %%d in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd"') do set TODAY=%%d
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%TODAY%.log
for 的變數,在批次檔內要寫成 %%d,直接輸入命令提示字元時則要寫成 %d,兩者需要分開寫。10 上面的範例是給批次檔用的,直接貼到命令提示字元不會動。若要直接測試,請把 %%d 改成 %d。由於定點觀測會透過工作排程器執行,實務上會採用批次檔的寫法。這裡很容易與前面的 %date:~0,4%(不管哪種情況都只用一個 %)搞混。
只要用工作排程器每小時把 -s 的輸出(Event: 1523、File: 88……這種按類別統計的結果)追加寫入日誌,即使是只能遠端維護的裝置 PC,也能用數字掌握「哪種控制代碼一天會增加多少」。只要知道類別,就能一口氣縮小容疑範圍:Event 的話懷疑事件釋放遺漏、File 的話懷疑忘記關閉、Thread 的話懷疑執行緒結束後控制代碼未清理,大致是這樣的對應。
另外,雖然可以用 -c 選項強制關閉指定的控制代碼,但官方文件本身就警告「關閉控制代碼可能導致應用程式或系統變得不穩定」。2 即使是作為解鎖的應急處置,本公司的原則也是正式環境基本上不使用。在確認「哪種類別在洩漏」之後,接下來鎖定「是哪段程式碼配置的」的步驟,將延續到「工業相機控制應用跑一個月後突然崩潰時(前篇) - handle leak 的找法與長時間運轉用的日誌設計」與「工業相機控制應用跑一個月後突然崩潰時(後篇) - 什麼是 Application Verifier 與異常系測試基盤的做法」。
6. VMMap ── 拆解「Private Bytes 持續增加」的組成
控制代碼持平,卻只有 Private Bytes 持續增加 ── 這時候該打開的就是 VMMap。這是一款分析處理程序虛擬記憶體與實體記憶體(工作集)的工具,能把已認可(commit)的虛擬記憶體按種類拆解顯示出來。5 啟動後選擇目標處理程序,上段會顯示以顏色區分的摘要,下段則是詳細的記憶體對應表。主要的分類判讀方式如下。
| 分類 | 內容 | 持續增加時的典型犯人 |
|---|---|---|
| Image | EXE/DLL 本體 | 外掛 DLL 一直沒卸載 |
| Heap | 原生堆積(new/malloc/HeapAlloc) | C/C++ 程式碼、裝置 SDK、Interop 的釋放遺漏 |
| Managed Heap | .NET 的 GC 堆積 | 受管理端的參照洩漏(事件處理常式等) |
| Private Data | 透過 VirtualAlloc 直接配置等 | 影像畫格緩衝區、SDK 內部緩衝區 |
| Stack | 執行緒的堆疊 | 執行緒建立後一直沒回收 |
| Shareable / Mapped File | 共用記憶體、記憶體映射檔案 | 跨處理程序協作用的區段(Section)釋放遺漏 |
操作步驟共 4 步。
- 以系統管理員身分執行 VMMap。啟動後會立即出現處理程序選擇對話方塊,選取目標處理程序後按下 OK
- 查看上段的摘要表。列對應上表中的 Type(Image / Heap / Managed Heap / Private Data / Stack……),欄則是 Size、Committed 等大小數值。這個時間點的數值,一定要先記下來,或從 File 選單匯出保存,這一步很關鍵
- 將有問題的操作(裝置的一次量測循環、畫面的開關等)重複執行固定次數
- 按 F5(Refresh)更新快照,並與步驟 2 記錄的數值比較。點擊有增加的 Type 那一列,下段就會顯示該區域的清單
使用方式的固定套路是「一邊按 F5 更新快照、一邊重複有問題的操作,觀察哪個分類在增加」。由於 VMMap 支援快照比較、時間軸顯示與結果匯出5,因此可以當場取得「量測 100 次後 Heap 增加 40MB,Managed Heap 持平」這樣的證據。一旦確定了擂台,接下來就輪到專用工具上場:若是 Managed Heap,就進行 .NET 端的調查(「用 PerfView 與 dotnet-trace 找出「變慢」的原因」);若是 Heap/Private Data,則進行原生端的調查(Application Verifier 或傾印檔解析)。在 .NET + 原生 SDK 構成的裝置應用程式中,最常見的繞遠路,就是不做這樣的切分,一開始就懷疑 GC,白白耗掉好幾天。
32 位元處理程序的情況下,片段化也是觀察對象。透過 Fragmentation View 能看到位址空間中空閒區塊的分散情形,可視化呈現「Free 合計有數百 MB,但最大連續空閒區塊(Largest)卻只有幾十 MB」這種狀態。「明明記憶體應該還夠用,卻發生 OutOfMemory」「只有大型影像緩衝區的配置會失敗」,真相多半就是這個,這也能作為對策(改用 64 位元、緩衝區重複使用設計)的佐證資料。
當可疑的不是單一處理程序、而是整個 OS 的記憶體(每個處理程序看起來都沒有變胖,卻發生記憶體不足)時,就改用能依分頁顯示整體實體記憶體用途的 RAMMap。由於連檔案快取、驅動程式、核心的使用量都看得到,「應用程式以外的犯人」就靠這個工具尋找。6
7. 案例研究 ── 拆解「每逢星期五就變重的裝置 PC」
用到目前為止介紹的工具,實際拆解開頭提到的裝置 PC,會是這樣的流程。由於維運方式是星期一早上重新啟動,星期五就是「運轉第 5 天」。也就是說,初步假設是:有某個東西會隨時間成比例增加。
- 大約在星期三放置 Process Explorer,先埋好欄位。新增 Handle Count、USER Objects、GDI Objects、Private Bytes,記錄目標處理程序的數值。同時把
handle -s -p 目標應用程式.exe的每小時日誌,登記到工作排程器中(第 5 章)。 - 隔天查看斜率。在這個案例中,Handle Count 一天約增加 2 萬,種類別日誌顯示 Event 控制代碼呈單調遞增。Private Bytes 與 GDI/USER 幾乎持平。到這個時間點,就能鎖定為「核心物件(事件)的釋放遺漏」。
- 在 Handles 檢視中查看實體。把下方窗格的 Handles 檢視依 Type 排序後,看到有數萬個無名的 Event。因為沒有名稱,無法用 Find Handle 追蹤,但只要知道類別與增加速度就已經足夠。從測量週期(這個案例中裝置每輪詢一次會增加 2 個)的對應關係,可以推導出「通訊函式庫的等待事件釋放遺漏」這個假設,並透過程式碼審查確定。若要用工具鎖定配置位置,接下來就輪到 Application Verifier 或
!htrace出場。 - 如果 Private Bytes 有增加,就取代步驟 3,改用 VMMap 拆解組成(第 6 章),Heap 分支到原生端調查,Managed Heap 分支到 .NET 端調查。
- 如果所有數字都持平、只有出現無回應,就在 Threads 分頁查看堆疊(第 4 章),鎖定等待的對象。
重點在於,修正前要先確保「圖表斜率」這項證據。修正後再做一次相同的測量,只要能證明斜率歸零,就能用數字報告「已經修好了」。對於需要等待數天才會重現的長時間運轉問題,這個測量的事前布置,才是調查的本體。
8. 帶入正式環境 PC 與維運注意事項 ── Sysinternals Live、EULA、離線環境的符號
Sysinternals 工具全都是不需要安裝的獨立執行檔,只要用 USB 隨身碟把 ZIP 帶進去,就能直接執行。首次啟動時會出現 EULA 同意對話方塊,若是無人執行或指令碼呼叫,可用 /accepteula 開關明確表示同意。若環境可以使用網路,還有一項名為 Sysinternals Live 的服務,可以不必下載,直接從 https://live.sysinternals.com/procexp.exe 這樣的網址,或 \\live.sysinternals.com\tools\<工具名稱> 的 UNC 路徑執行。7 這是「現在就想在這一台上看看」時最快的路徑。
離線裝置 PC 上會卡住的是符號(第 4 章的堆疊顯示無法使用)。因應方式有 2 種:(1)在能連上網路的開發機上,針對相同的 OS 組建先解析好符號,連同本機快取(C:\Symbols)一起複製到裝置 PC,並把符號路徑只指向那個本機資料夾。(2)放棄堆疊解析,現場只專注於採集傾印檔(ProcDump)與數值記錄,帶回開發機解析。確實可靠的是 (2),傾印檔的採集方式已整理在「Windows 當機傾印檔採集入門」中。
最後談維運層面。用 Process Explorer 或 VMMap 進行的觀察,負載低、風險低,但處理程序的 Kill/Suspend、執行緒的 Kill、Close Handle、handle -c 這類介入操作,正式環境中原則上請禁止。與前篇的 ProcMon 一樣,實施時請走與一般變更作業相同的核准流程,先決定好「何時、由誰、觀察什麼、什麼絕對不操作」,再上場會比較安全。
9. 實務判斷準則(判斷表)
| 症狀 | 使用的工具 | 查看位置 |
|---|---|---|
| 愈用愈重、幾天後變得不穩定 | Process Explorer | Handle Count / USER・GDI Objects / Private Bytes 欄位的斜率(第 5 章) |
| 檔案無法刪除、替換 | Process Explorer / handle.exe | 用 Find Handle or DLL(Ctrl+F)搜尋路徑 → 找出佔用的處理程序(第 3 章) |
| 無回應、沒有回應 | Process Explorer | Properties → Threads 分頁 → 執行緒堆疊(需設定符號,第 4 章) |
| Private Bytes 持續增加 | VMMap | Heap / Managed Heap / Private Data 哪一項在增加(第 6 章) |
| 每個處理程序看起來都沒有變胖,卻記憶體不足 | RAMMap | 用 Use Counts、File Summary 查看實體記憶體用途6 |
| 當機、因例外而中止 | ProcDump + WinDbg | 傾印檔採集 → WinDbg + SOS |
| CPU 用在哪裡、.NET 的 GC | PerfView / dotnet-trace | 「變慢」的定量調查 |
| 操作的時間序列(哪個檔案、何時、以什麼順序) | Process Monitor | 前篇文章 |
10. 總結
- ProcMon 記錄的是「操作的歷史」,而 Process Explorer / Handle / VMMap 則是查看「此刻狀態」的工具。調查長時間運轉的應用程式時,兩者都不可或缺。
- 「檔案刪不掉」用 Find Handle or DLL(Ctrl+F)幾秒鐘就能鎖定,「無回應」則用 Threads 分頁的堆疊找出頭緒。要讀懂堆疊,前提是設定好 dbghelp.dll(附帶 symsrv.dll 的那個位置)與符號路徑,而這個 dbghelp.dll 要透過安裝 Debugging Tools for Windows 取得。
- 能用名稱搜尋的,只有具名的物件。對於像檔案這種有名稱的東西是最強力的手段,但無名的事件或 Mutex 不會被搜尋命中。若把控制代碼洩漏的調查,從「用 Ctrl+F 找犯人」開始,會撲空,請改用種類別統計(handle -s)以及 Handles 檢視依 Type 排序。
- 「愈用愈重」要新增 Handle Count、USER/GDI Objects、Private Bytes 欄位,觀察斜率。若用 handle -s 做定期日誌,即使是無法開啟 GUI 的裝置 PC 也能取得數字。
- Private Bytes 的增加,先用 VMMap 拆分為 Heap(原生)/ Managed Heap(.NET)/ Private Data,再進入專用工具。若是 32 位元處理程序,也要懷疑片段化。
- 用 handle -c 或 Close Handle 強制關閉,正如官方所警告,是導致不穩定的根源。正式環境中請徹底貫徹「只做觀察、不做介入,要介入就一定要走核准流程」。
- 這些工具都是獨立執行檔,容易帶入現場,用 Sysinternals Live 還能直接執行。在離線環境中,則要靠事前快取符號,或把傾印檔帶回去解析來補足。
相關文章
- Process Monitor(ProcMon)實戰指南 ── 在10分鐘內找出「設定未被讀取」「ACCESS DENIED」的原因
- 如何替換使用中的 exe/DLL ── Restart Manager 與自動更新的「檔案使用中」問題
- 工業相機控制應用跑一個月後突然崩潰時(前篇) - handle leak 的找法與長時間運轉用的日誌設計
- 工業相機控制應用跑一個月後突然崩潰時(後篇) - 什麼是 Application Verifier 與異常系測試基盤的做法
- Windows 應用的 crash dump 收集入門 - 先搞清楚 WER / ProcDump / WinDbg 怎麼分工
- 用 WinDbg + SOS 解讀當機傾印檔 ── 收集之後的實務分析入門
- 用 PerfView 與 dotnet-trace 找出「變慢」的原因 ── .NET 效能調查實務入門
相關諮詢領域
合同會社小村軟體處理長時間運轉後會逐漸劣化的業務應用程式、裝置連動應用程式的洩漏調查,無回應、「檔案使用中」等現場故障的原因分析,以及證據採集程序的建立與再發防止的改修。
參考連結
-
Microsoft Learn,Process Explorer - Sysinternals。關於 Process Explorer 以雙窗格(handle 模式/DLL 模式)顯示處理程序已開啟的控制代碼,以及已載入的 DLL、記憶體映射檔案;可搜尋擁有特定控制代碼或 DLL 的處理程序;有助於追蹤 DLL 版本問題與控制代碼洩漏;使用符號伺服器時,dbghelp.dll 所在位置必須同時存在 symsrv.dll;可直接從 Sysinternals Live 執行;執行需求為用戶端 Windows 11 以上、伺服器 Windows Server 2016 以上的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn,Handle - Sysinternals。關於 handle.exe 是一款顯示已開啟控制代碼資訊的命令列工具,需要系統管理員權限;支援名稱部分一致搜尋;用 -a 可涵蓋所有種類的控制代碼;用 -p 可篩選處理程序;用 -s 可統計各類別的控制代碼數;用 -c 可關閉控制代碼,但官方警告「可能導致應用程式或系統變得不穩定」的說明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Microsoft Public Symbol Server。關於 Microsoft 公開符號伺服器的符號路徑,可用
srv*本機快取*https://msdl.microsoft.com/download/symbols的格式指定;下游存放區(本機快取)只會保存曾經存取過的符號,第 2 次以後會從本機讀取的說明。 ↩ ↩2 -
Microsoft Learn,GDI Objects。關於 GDI 控制代碼每個工作階段有 65,536 個的理論上限;每個處理程序存在預設上限,可透過登錄檔的 GDIProcessHandleQuota(256〜65,536 範圍)變更的說明。 ↩ ↩2
-
Microsoft Learn,VMMap - Sysinternals。關於 VMMap 是一款處理程序虛擬、實體記憶體分析工具,可顯示已認可虛擬記憶體按種類拆解的組成,以及各種類所分配到的實體記憶體(工作集);支援篩選與更新(快照)功能;支援資料匯出,以及透過命令列選項進行指令碼化的說明。 ↩ ↩2 ↩3
-
Microsoft Learn,RAMMap - Sysinternals。關於 RAMMap 是一款分析整個 OS 實體記憶體使用狀況的工具,會透過 Use Counts(種類別統計)、Processes(處理程序的工作集)、File Summary(RAM 上的檔案資料)等分頁顯示用途;支援快照的儲存與載入的說明。 ↩ ↩2 ↩3
-
Microsoft Learn,Sysinternals。關於 Sysinternals Live 是一項無須下載工具即可執行的服務,可透過 live.sysinternals.com/<工具名稱> 的網址,或 \\live.sysinternals.com\tools\<工具名稱> 的路徑直接執行;可在 live.sysinternals.com 瀏覽工具清單的說明。工具名稱>工具名稱> ↩ ↩2
-
Microsoft Learn,Debugging Tools for Windows SDK and WDK。關於 Debugging Tools for Windows 包含在 Windows SDK 與 WDK 之中;啟動 Windows SDK 安裝程式後,從功能清單只勾選「Debugging Tools for Windows」、取消其他所有項目,即可單獨安裝偵錯器;安裝程式的取得來源是 Windows SDK的說明。 ↩ ↩2
-
Microsoft Learn,Install WinDbg。關於 WinDbg 可用於解析當機傾印檔,以及使用者模式、核心模式的偵錯;安裝方式包括直接執行安裝程式、透過 Microsoft Store,以及用
winget install Microsoft.WinDbg安裝;舊版 WinDbg(classic)則包含在 Debugging Tools for Windows 中的說明。 ↩ -
Microsoft Learn,for。關於批次檔內要寫成
%%variable,直接在命令提示字元中執行時要寫成%variable;以及for /f可以剖析指令輸出結果的說明。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 的「記憶體使用量」代表什麼 ── 正確解讀 Working Set、Private Bytes、Commit 與分頁檔
工作管理員的記憶體、Working Set、Private Bytes、Commit 並非同一個數值。本文解說 Windows 虛擬記憶體與實體記憶體的關係、分頁檔的角色,以及在記憶體不足或洩漏調查時應該檢視的指標。
多執行緒的實務最佳實踐 .NET篇 ── 在增加執行緒之前先決定的事
針對 .NET/C# 整理能防止「建立執行緒後偶爾當機、卡死」的設計定石。內容涵蓋不自行建立執行緒而改用 Task、減少共享可變狀態、鎖的紀律、以 CancellationToken 設計停止機制,以及 UI 執行緒的處理方式。
使用 Get-WinEvent 有效率地調查事件記錄 ── 篩選的速度決定調查所需的時間
本文整理如何用 PowerShell 有效率地進行 Windows 事件記錄的調查。說明以 Where-Object 篩選為什麼慢、FilterHashtable 與 XPath 的使用區分、重新開機、登入、應用程式異常結束等調查手法,以及跨多台伺服器收集記錄的方法。
Windows的時間同步(w32time)與業務系統 ── 從機制原理解決「日誌時間戳記對不上」的問題
從Windows Time服務(w32time)的機制,解說裝置與PC之間日誌時間產生落差的原因。內容涵蓋網域階層與工作群組的預設行為、以w32tm指令進行的診斷、高精確度時間與虛擬機器的注意事項,一直到併用Stopwatch與UTC的日誌設計。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
故障調查 & 長期運行故障
整理間歇性故障、通訊診斷、長期運行當機、失敗路徑測試基礎的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
故障調查 & 根本原因分析
調查難以重現的故障、長時間執行後的問題、記憶體洩漏、通訊停滯等棘手的正式環境問題。
常見問題
整理諮詢這個主題時常見的問題。
- 明明有工作管理員,為什麼還要特地使用 Process Explorer?
- 因為工作管理員無法看到「哪個處理程序開啟了哪個檔案或物件」「每個執行緒現在停在哪段程式碼」。Process Explorer 可以列出每個處理程序的控制代碼、DLL 清單,還能用名稱搜尋控制代碼(Ctrl+F),甚至顯示執行緒堆疊,因此「檔案刪不掉」「無回應」的調查,會跟工作管理員完全是不同的層次。只要新增控制代碼數、USER/GDI 物件數等欄位,也能當成洩漏監控工具使用。啟用 Options 選單中的 Replace Task Manager,呼叫工作管理員時就會改成開啟 Process Explorer,所以在負責調查的機器上,直接替換掉是比較實務的做法。
- 用 handle.exe 的 -c 選項關閉控制代碼,可以馬上解除檔案的鎖定嗎?
- 技術上是可行的,但正式環境中基本上請不要使用。官方文件本身就警告「關閉控制代碼可能導致應用程式或系統變得不穩定」。因為這是無視處理程序的內部狀態、從外部強行奪取控制代碼,最壞的情況下,可能發生該處理程序之後再度使用同一個控制代碼時,卻指向了另一個物件而誤動作(控制代碼值被重複使用)這種事故。正確做法是鎖定佔用的處理程序,讓它正常結束。若是想替換使用中的 exe 或 DLL 這種情境,請考慮 Restart Manager 等設計層面的解決方案。
- Private Bytes 持續增加。最先該做的是什麼?
- 第一步是用 VMMap 開啟目標處理程序,查看是哪個分類在增加。若是 Heap 增加,很可能是 malloc/new/HeapAlloc 造成的原生洩漏,裝置廠商的 SDK 或 C++/CLI、Interop 程式碼會是嫌疑對象。若是 Managed Heap 增加,那就是 .NET 受管理堆積的問題,接下來要調查 GC 無法回收的參照(例如事件處理常式解除訂閱遺漏)。若是 Private Data(透過 VirtualAlloc 直接配置)增加,則要懷疑配置了影像緩衝區這類大型區塊的程式碼或 SDK。先完成這樣的切分,再進入各自的專用工具(WinDbg、PerfView 等),調查就不會迷路。
- 在離線的裝置 PC 上,Threads 分頁的堆疊會變成一堆位址而無法閱讀。該怎麼辦?
- 這是因為無法下載符號(PDB),因應方式只有「帶入符號」與「帶回傾印檔」二選一。帶入符號的做法,是在能連上網路的開發機上,針對相同的 OS 組建、相同的應用程式組態,先解析一次符號,再連同本機快取資料夾(例如 C:\Symbols)一起複製到裝置 PC,並將符號路徑只指向那個本機資料夾。帶回傾印檔的做法,則是在無回應期間採集傾印檔,帶回開發機用 WinDbg 解析,這樣更確實可靠,若想精確讀取受管理應用程式的堆疊,也更適合用這個方法。自家應用程式的 PDB 按建置版本保存,是一切的大前提。
- 把 Process Explorer 或 VMMap 帶進正式環境 PC、裝置 PC,有沒有問題?
- 這兩者都是不需要安裝的獨立執行檔,不需要登錄檔登記或服務常駐即可運作,因此帶入現場的門檻很低。首次啟動時需要同意 EULA,若從指令碼呼叫,可用 /accepteula 開關明確表示同意。若環境可以使用網路,也能不下載、直接從 Sysinternals Live(https://live.sysinternals.com)執行。不過,即使觀察本身風險低,處理程序的 Kill/Suspend、執行緒的 Kill、控制代碼的強制關閉這類「介入」操作,卻會直接導致事故,因此正式環境中請徹底只做觀察與記錄,並走一般的作業核准流程來實施。