快訊
2026-08-10
電腦疑難雜症

WinDbg 進階指令教學:用 !process、!irp、!thread 追出卡死的驅動程式與 I/O 請求

約 17 分鐘閱讀
廣告

⚡ 站長快讀:核心重點

  • 文章屬性:教學實戰(底層除錯)
  • 適用系統:Windows 10 / Windows 11(核心模式偵錯,不分版本)
  • 難易度 / 耗時:⭐⭐⭐ / 約 40 分鐘
  • 核心結論:三個指令分工——!process 找人、!thread 問供詞、!irp 查贓物。
  • 適用對象:遇過「滑鼠會動但整台沒反應」、已會開 WinDbg 讀 minidump 的人

📌 快速答案

一句話答案:卡死沒有藍屏就沒有傾印檔,先按住最右側 Ctrl 連按兩下 Scroll Lock 強制產生傾印,再用 WinDbg !process 0 17 列出所有執行緒堆疊,交叉 !thread 與 !irp 找出卡住的驅動。


🧰 開始前的準備

  • 系統需求:Windows 10 / Windows 11(依官方文件,PS/2 鍵盤強制當機自 Windows 2000 起支援、USB 自 Vista 起、Hyper-V 鍵盤自 Windows 10 1903 起)
  • 權限需求:系統管理員(改登錄檔、改傾印檔設定都需要)
  • 需要工具:WinDbg(Microsoft Store / winget)、微軟公開符號伺服器
  • 預計耗時:設定約 10 分鐘、重現卡死看運氣、分析約 30 分鐘
  • 難度門檻:看得懂十六進位、知道 IRP 是「I/O 請求封包」的縮寫就夠了

⚠️ 執行前必讀(高風險操作):本文步驟一會故意讓電腦當機。當機的瞬間所有未存檔的資料都會消失,而且會在系統磁碟寫入一個數 GB 的傾印檔。開始前請先存檔、關掉正在寫入的程式,並確認系統磁碟至少有 RAM 容量 + 10 GB 的可用空間。這台機器如果是正在服役的工作機或伺服器,請不要在上班時間做。

停止條件(符合任一,請立刻停手):①手上沒有另一台可用的電腦可以查資料 ②系統磁碟可用空間不足 ③這台機器啟用了 BitLocker 而你手邊沒有復原金鑰 ④公司或學校控管的設備且沒有 IT 授權 ⑤不確定自己在改的是哪一個登錄檔機碼。


🔍 為什麼你需要這個?

卡死和藍屏是兩種完全不同的問題,而網路上九成的 WinDbg 教學只教得了藍屏那一種。 藍屏會自己留下 minidump,你事後開起來跑 !analyze -v,分析器會直接把嫌疑模組名稱印在 MODULE_NAME 欄位裡。卡死不會。畫面凍住、滑鼠還能動但點什麼都沒反應、工作管理員叫不出來——這種狀態下系統其實還活著,只是某條執行緒抓著資源不放、或某個 I/O 請求送下去就再也沒回來,而它從頭到尾不會觸發任何 bug check,所以硬碟上什麼證據都不會留。

證據要自己製造。微軟官方文件《Forcing a system crash from the keyboard》講得很直白:設好一個登錄檔值之後,按住右邊 Ctrl 再連按兩下 Scroll Lock,系統就會呼叫 KeBugCheck 並發出 Bug Check 0xE2 MANUALLY_INITIATED_CRASH,接著把傾印檔寫下來。這一下按下去,你就把「卡死當下」那一秒的系統狀態整包凍結成檔案了。

拿到檔案之後才是真正的分歧點:這份傾印跑 !analyze -v 幫助有限(經驗推論,非官方明文)——官方只把 0xE2 定義為「使用者刻意從核心偵錯器或鍵盤發起的傾印」,並未評價 !analyze -v 在這種情境的效用;但停止碼既然是你自己按出來的,分析器順著它推出的結論自然指向這次人為中斷,而不是原本的卡死成因。要從這份檔案裡挖出卡死原因,得改用手動路徑,而 !process!thread!irp 這三個指令就是那條路徑的三段:先看在等、再看等什麼、最後看卡在哪個驅動手上

還沒設好符號路徑的話,先把WinDbg 安裝與符號路徑設定那一關過了再回來;沒有符號,下面所有指令印出來的都會是一堆 nt+0x3f1234,等於白做。


🛠️ 實戰步驟

步驟一:先製造一份「看得到全部執行緒」的傾印檔

這一步有兩個設定要同時做對,少做一個後面就會卡住:一是開啟鍵盤強制當機,二是把傾印檔型別調大。

先看第二件事,因為它比較容易被忽略。微軟官方《Varieties of Kernel-Mode Dump Files》列出五種核心模式傾印檔:完整記憶體傾印、主動記憶體傾印、核心記憶體傾印、自動記憶體傾印、小型記憶體傾印。差別只有一個字——大小。官方對小型記憶體傾印的描述是「only 64 KB in size」,而核心記憶體傾印「通常不含使用者模式記憶體」,完整記憶體傾印則「包含部分使用者模式記憶體」。

廣告

💡 為什麼要這樣做?(機制推論,非官方明文)官方沒有逐指令列出「哪種傾印跑得動 !process」,但推論並不難:!process 0 17 要走遍系統上每一個 EPROCESS 與其下所有執行緒堆疊,這些結構全在核心記憶體裡。小型傾印只留當機那一刻的少量現場,列不出全系統;核心傾印能列出核心那一半,使用者模式那一段堆疊會缺;要把使用者態呼叫鏈也一起看到,才需要完整或主動記憶體傾印。保險起見選「完整記憶體傾印」,代價是檔案大小約等於實體記憶體容量。

設定路徑(官方《Generate a kernel or complete crash dump》給的是控制台路徑):控制台 → 系統及安全性 → 系統 → 進階系統設定 → 「進階」索引標籤 → 啟動及修復「設定」 → 把「寫入偵錯資訊」改成完整記憶體傾印。Windows 11 也可從設定 → 系統 → 系統資訊 → 進階系統設定進到同一個對話框,但「進階」索引標籤這一步不能跳過

接著開啟鍵盤強制當機。依官方文件,要改的機碼看你的鍵盤是哪一種:

鍵盤型態登錄檔機碼(Parameters 之下)
PS/2(多數筆電內建鍵盤)...\Services\i8042prt\Parameters
USB 外接鍵盤...\Services\kbdhid\Parameters
Hyper-V 虛擬機鍵盤...\Services\hyperkbd\Parameters

三者都是在該路徑下建立名為 CrashOnCtrlScrollREG_DWORD,值設為 0x01。官方另外提醒:有些筆電內建鍵盤走 PS/2 驅動、又同時接了外接 HID 鍵盤,這種機器建議兩個機碼都建,哪支鍵盤都能觸發。

以 USB 鍵盤為例,系統管理員命令提示字元:

reg add "HKLM\SYSTEM\CurrentControlSet\Services\kbdhid\Parameters" ^
  /v CrashOnCtrlScroll /t REG_DWORD /d 1 /f

改完必須重新開機設定才會生效。之後遇到卡死,按住最右邊的 Ctrl 鍵、連按兩下 Scroll Lock 即可。

⚠️ 官方明載的失效情境,而且它本身就是線索:文件寫明「若電腦停止回應在高 IRQL(interrupt request level),鍵盤強制當機不會作用」,原因是負責讓傾印流程跑起來的 Kbdhid.sys 執行在比 i8042prt.sys 更低的 IRQL。所以按了沒反應不代表你設錯——它同時告訴你這次卡死很可能發生在高 IRQL,那是另一類問題(中斷風暴、DPC 逾時),追法也不一樣。

步驟二:!process 0 17 — 這串數字不是咒語,是四個位元

先講結論:0 是「所有處理程序」,17 是十六進位旗標,拆開來是 0x10x20x40x10 四個位元的組合。 官方 !process 文件把每個位元的意義列得清清楚楚:

廣告
位元官方定義
Bit 00x1顯示時間與優先權統計
Bit 10x2顯示執行緒與事件清單,以及它們的等待狀態
Bit 20x4顯示執行緒清單;與 Bit 1 併用時,每條執行緒會附上呼叫堆疊
Bit 40x10把處理程序內容切換成該處理程序,讓堆疊顯示更準確

四個位元加起來:0x1 + 0x2 + 0x4 + 0x10 = 0x17。所以 !process 0 17 的白話意思是:把系統上每一個處理程序、每一條執行緒的等待狀態與完整呼叫堆疊,全部印出來。

這裡有個很容易踩的細節:官方對 Bit 4 特別加註「This flag is only effective when used with Bit 0 (0x1)」——0x10 必須和 0x1 一起用才有效。所以想省事只打 !process 0 10 是沒用的,0x17 這個組合之所以流傳,正是因為它把該開的都開了。

另外官方也寫明 Process 參數的三種行為:省略時只顯示目前的系統處理程序、給 0 且不指定 ImageName 時顯示所有作用中的處理程序、給 -1 時顯示目前處理程序。

!process 0 17

輸出會非常長,不要從頭讀。 直接在輸出裡搜尋 WAIT:——每個等待中的執行緒後面括號裡就是等待原因。官方 !process 文件指出,WAIT 後面括號內的註解就是等待理由,而完整的等待理由清單可以用 dt nt!_KWAIT_REASON 印出來。

輸出量大到讀不動時,官方在同一頁建議了替代品:!stacks 會給出每條執行緒狀態的簡短摘要,文件明說它「特別適合偵錯多執行緒問題,例如資源衝突或死結」。!stacks 2 會連分頁出去的堆疊與完整參數一起印。卡死題我的習慣是先 !stacks 0 掃過一輪找可疑執行緒,再回頭對那幾條下 !process,比從頭讀六千行有效率。

步驟三:!thread — 鎖定單一執行緒,看它到底在等什麼

!process 給你的是全景,!thread 給你的是特寫。 從步驟二挑出可疑的執行緒位址(輸出裡 THREAD 後面那串十六進位),直接餵進去:

廣告
!thread ffffcb088f0a4480

官方文件說明,!thread 顯示目標系統上某條執行緒的摘要資訊,含 ETHREAD 結構,同樣只能在核心模式偵錯時使用。它的旗標與 !process 類似但不完全一樣,預設值是 0x6:

  • Bit 1(0x2):顯示執行緒的等待狀態
  • Bit 2(0x4):單獨使用無效,必須與 Bit 1 併用才會附上呼叫堆疊
  • Bit 3(0x8):加上返回位址與堆疊指標,並抑制函式引數顯示
  • Bit 4(0x10):把處理程序內容切成該執行緒所屬的處理程序,堆疊會更準

另外兩個好用的開關:-p 會順便顯示擁有這條執行緒的處理程序摘要;-t 表示你給的是執行緒 ID 而非位址

輸出裡要盯的欄位,官方逐項列在文件表格中:Cid 後面兩個十六進位數字是「處理程序 ID.執行緒 ID」;Owning Process 是擁有者 EPROCESS 位址;行末的 Thread State 才是關鍵——它會告訴你這條執行緒是 RUNNING 還是卡在某個等待上。

💡 為什麼要這樣做? 卡死案件裡真正有價值的資訊不是「誰在跑」,而是「誰在等、等了多久、等的東西誰握著」。Wait Start TickCount 搭配 Ticks 就能看出這條執行緒已經等了多久;如果 Ticks 大得離譜,它八成就是受害者或加害者之一。

步驟四:!irp — 追出送下去就沒回來的 I/O 請求

如果卡死的症狀是「檔案總管點某個磁碟就整個凍住」「USB 裝置插上去之後全系統卡住」,兇手多半躺在某個沒完成的 IRP 上。

官方定義很簡單:!irp Address [Detail]。位址給 IRP 的十六進位位址;Detail 只要填任何值(例如 1),輸出就會多出 IRP 狀態、記憶體描述元清單(MDL)位址、擁有者執行緒,以及每一層 I/O 堆疊的資訊,包含主要功能碼與次要功能碼的十六進位值。不填則只印摘要。

!irp ffffac598dc8 1

輸出第一行長這樣:Irp is active with 2 stacks 1 is current,而前面帶 > 符號的那一層就是目前這個 IRP 停在哪一層。每層會印出裝置物件、驅動名稱(例如 \Driver\disk\FileSystem\Ntfs)與完成常式。官方特別註明:Windows 10 起,IRP 的主要與次要功能碼會直接顯示文字,例如 IRP_MJ_FILE_SYSTEM_CONTROL,同時附上十六進位碼 (d);輸出裡的第三個引數是 IOCTL 碼,可以用 !ioctldecode 進一步解讀。

完成常式旁邊還會標 Success / Error / Cancel 三種條件,代表 IRP 完成時在什麼狀況下會呼叫該完成常式——這三種是官方 !irp 文件明確定義的。至於同一行常見的 pending 字樣,官方 !irp 參考頁並未解釋,它對應的是該堆疊位置被標記為「已回傳擱置」(驅動呼叫 IoMarkIrpPending 或回傳 STATUS_PENDING)——卡死案件裡,那個一直擱置又不往上走的層級,它掛的驅動名稱就是第一嫌疑人。

問題來了:IRP 位址從哪來? 兩條路。第一條是從步驟三的執行緒堆疊裡撈(等在 I/O 上的執行緒,堆疊會出現 IRP 位址)。第二條是官方的 !irpfind,它會列出目標系統上所有已配置的 IRP,或只列符合條件者:

!irpfind

!irpfind 的搜尋條件官方列了六種,常用的是 thread(找 Irp.Tail.Overlay.Thread 等於指定值的 IRP)與 device(找堆疊位置的 DeviceObject 等於指定值者)。這條就是把步驟三與步驟四接起來的橋:

!irpfind 0 0 thread ffffcb088f0a4480

意思是「在非分頁集區裡,找出屬於這條執行緒的所有 IRP」。!irpfind 預設搜尋非分頁集區(PoolType 0),其餘可選分頁集區 1、特殊集區 2、工作階段集區 4

WinDbg 卡死診斷三指令流程:!process 列出全系統執行緒、!thread 看等待狀態、!irp 追出停住的 I/O 請求

步驟五:驗證結果 — 三條線交叉才算數

單一指令的輸出都不算證據,三條線指向同一個驅動名稱才算。 收尾時照這個順序回頭對一次:

  1. !process 0 17 裡那條 Ticks 異常大的執行緒,WAIT 的原因是什麼?
  2. !thread 對這條執行緒單獨展開,Thread State 與呼叫堆疊裡出現哪個第三方驅動模組名稱?
  3. !irpfind ... thread <該執行緒位址> 找出它掛著的 IRP,!irp <位址> 1 展開後,標 pending 的那一層屬於哪個 \Driver\xxx?

三個答案指到同一支驅動,才有把握說「就是它」。如果 2 和 3 指到不同驅動,通常代表你看到的是一條受害鏈——真正的源頭在更下層(檔案系統 → 磁碟區 → 磁碟 → 埠驅動),順著 !irp 的堆疊往下讀就會找到。


🔬 底層機制:這個問題到底在系統哪一層?

卡死的本質,是核心裡某個「等待」永遠等不到通知。

Windows 的執行緒不是被動輪詢,而是把自己掛在同步物件上睡著,等別人喚醒。!process 輸出裡的 WAIT: (WrFreePage) 這種標記,括號裡就是等待原因,官方文件指向 dt nt!_KWAIT_REASON 可以列出全部種類。當一條執行緒的等待原因對應到一個永遠不會被設定的事件,它就會一直睡下去——這不是當機,系統完全正常運作,只是這條路上的人全部在排隊。

I/O 這一側的機制更具體。應用程式讀一個檔案,I/O 管理員會建立一個 IRP,這個 IRP 帶著多層 I/O 堆疊位置,依序往驅動堆疊下走:檔案系統 → 磁碟區管理 → 分割管理 → 磁碟 → 儲存埠驅動。官方 !irp 文件裡那則標明取自 Windows Vista 的八層範例輸出,把這條鏈印得一清二楚,從 \FileSystem\Ntfs\Driver\volmgr\Driver\PartMgr 一路到 \Driver\disk(同頁的 Windows 10 範例只有兩層,不要拿來對照層數)。每一層驅動可以選擇把 IRP 標成 pending 先放著,等硬體回應再往上完成。只要中間任何一層拿了不還,上面所有層級全部卡住,而發起 I/O 的那條執行緒就永遠醒不過來。

再往上一層看,執行緒之間還有資源鎖。!process 文件提到,執行緒資訊裡會列出該執行緒持有鎖的資源,並建議把這些位址拿去和 !locks 的清單比對,就能知道哪些執行緒對資源有獨佔鎖。兩條執行緒互相等對方手上的鎖,就是典型死結——這種案子 !process 0 17 一次就能看出來,因為兩條執行緒的等待原因會互相指向。

最後是 IRQL 這一層。步驟一提到的官方限制——高 IRQL 卡死時鍵盤強制當機失效——背後的意義是:CPU 在高 IRQL 上執行時,低 IRQL 的程式碼(包含負責寫傾印的鍵盤驅動)根本排不進去。所以「按了 Ctrl+Scroll Lock 沒反應」本身就是一個分層訊號:問題不在等待,而在某段程式碼霸佔著 CPU 不下來。 那類問題的入口是 CLOCK_WATCHDOG_TIMEOUT(0x101) 這種看門狗型 bug check,不是本文這三個指令。


🔙 萬一翻車:回退步驟

這篇改了兩樣東西:一個登錄檔值、一個傾印檔型別設定。兩樣都可以完整還原。

情境一:設定生效但按了沒反應

先確認你按的是最右邊那顆 Ctrl(官方寫的是 rightmost CTRL key),Scroll Lock 要連按兩下。還是沒反應就換一種可能:你的鍵盤走的驅動和你改的機碼不同。筆電內建鍵盤常走 i8042prt,外接鍵盤走 kbdhid,官方建議兩個都建。都建了還是沒反應,回頭看步驟一那則高 IRQL 警語——這種情況下該換工具,不是硬試。

情境二:想把設定改回去

登錄檔值直接刪除即可,系統管理員命令提示字元:

reg delete "HKLM\SYSTEM\CurrentControlSet\Services\kbdhid\Parameters" ^
  /v CrashOnCtrlScroll /f

PS/2 與 Hyper-V 機碼同理,把 kbdhid 換成 i8042prthyperkbd刪完要重開機才會生效。 傾印檔型別則回到啟動及修復 → 設定,把「寫入偵錯資訊」改回你原本的選項——改之前請先記下原值再動手,不要憑印象填,不同版本、不同版次的原廠預設值並不一致(常見預設為「自動記憶體傾印」,但這不是每台機器都成立的保證)。改完記得順手把系統磁碟裡幾 GB 大的 MEMORY.DMP 清掉。

情境三:系統無法正常開機

本文的兩個變更都不影響開機路徑,理論上不會走到這一步。真的開不起來,多半與你同時做的其他事有關(例如順手更新了驅動)。標準救援路徑是連續兩次開機失敗後自動進入 WinRE,選疑難排解 → 進階選項 → 啟動設定進安全模式,再用上面的 reg delete 還原;若連 WinRE 都進不去,用另一台電腦以官方媒體建立工具做一支開機 USB,從修復您的電腦進入。若這台機器啟用了 BitLocker,進 WinRE 會要求輸入復原金鑰——沒有金鑰就不要開始這篇的任何步驟。


💡 總結:進階玩法與底層邏輯

開頭那句「找人、問供詞、查贓物」不是順口溜,它就是執行順序:三者缺一都會停在「好像是這支驅動」的模糊地帶,湊齊了才敢寫進報告。站長我看過太多人只跑第一步就下結論,結果指到的是受害者不是加害者。

有幾個可驗證的錨點值得記起來,它們都寫在官方文件裡、任何人都能自己對:0x170x1|0x2|0x4|0x10 四個位元;!thread 的預設旗標是 0x6;!process 的 Bit 4 與 !thread 的 Bit 2 都有「單獨用無效」的但書;鍵盤強制當機發出的是 bug check 0xE2;!irpfind 預設只搜非分頁集區。這五件事記住,你在別人的教學文裡看到「就是要打 17」時,至少知道自己在打什麼。

進階延伸有兩條。 一條是 !stacks 2 加 FilterString,官方支援只顯示符號中含指定子字串的執行緒——追特定廠商驅動時,直接用它的模組名當過濾字串,六千行輸出瞬間變十行。另一條是 !ioctldecode,把 !irp 輸出的第三個引數丟進去,能直接把 IOCTL 碼還原成裝置類型與功能編號,對付「插上某台 USB 裝置就卡住」特別好用。

要誠實講清楚的是:本文所有指令語意、旗標定義與輸出範例,全部依微軟官方文件整理,不是站長在特定機器上的實測數據。 你手上那台機器的輸出長什麼樣、能不能重現,取決於你的傾印檔型別、符號是否完整、以及卡死的真實成因——這三件事我沒有你的機器,無法替你斷言。真的追不出來,把 !process 0 17 的輸出留檔,那是最有價值的一手證據。


❓ 常見問題

Q:我只有小型記憶體傾印(minidump),可以跑 !process 0 17 嗎?

不建議,而且大機率跑不出你要的東西。官方把小型記憶體傾印描述為五種傾印檔裡最小的一種(文件記為 64 KB),它保存的是當機瞬間的少量現場,不是全系統核心記憶體;!process 0 17 需要走遍所有 EPROCESS 與執行緒堆疊,資料不在檔案裡就印不出來。要追卡死,請在步驟一把型別改成核心或完整記憶體傾印,重新製造一份。

Q:一定要接第二台電腦做雙機偵錯嗎?

不用。官方在 !process!thread 兩頁都明寫「本延伸指令只能在核心模式偵錯期間使用」,!irp!irpfind 則同樣由核心偵錯延伸模組 Kdexts.dll 提供;而用 WinDbg 開啟核心模式傾印檔就屬於核心模式偵錯,不需要雙機連線。雙機的好處官方也講了:傾印檔是某個時間點的快照,即時偵錯才能隨程式執行觀察所有記憶體值——但對「找出卡死的驅動」這個目標,一份完整傾印通常就夠。

Q:!process 0 17 輸出太長,WinDbg 直接沒反應怎麼辦?

先確認不是符號還在下載(第一次連微軟符號伺服器可能要幾分鐘)。真的是輸出量問題,改用 !stacks 0 先掃摘要,官方就是把它定位成 !process 的快速替代品。另一招是用 .logopen 把輸出導到檔案再用文字編輯器搜尋,比在偵錯器視窗裡捲有效率得多。

Q:這個方法在舊版本也適用嗎?

!process!thread!irp 是行之有年的核心偵錯延伸指令,語法在 Windows 10 / 11 世代沒有變。要注意的是輸出格式差異:官方明載 Windows 10 起 !irp 才會把主要/次要功能碼顯示成文字(如 IRP_MJ_FILE_SYSTEM_CONTROL),更舊的系統只會印出 [ 3,34] 這種數字對,需要自己查功能碼對照表——官方 !irp 文件頁面就附有完整的對照表。鍵盤強制當機的支援起點也不同:PS/2 自 Windows 2000、USB 自 Windows Vista、Hyper-V 鍵盤自 Windows 10 1903。

Q:找到可疑驅動之後呢?

先確認它是不是第三方驅動(檔名不在 ntntoskrnlwin32k 這類系統模組裡),然後到裝置製造商官網找最新版更新;版本已經最新就試著回滾到前一版。如果懷疑是驅動記憶體誤用而非單純卡住,下一步工具是 Driver Verifier,那是另一條路線的開始。


📎 參考資料來源

📖 第一級|廠商官方:

⚠️ 本文核心事實以第一級為準。本文為官方文件整理與機制解讀,非站長第一手實測數據。

📅 本文查證戳記:2026-08-10 依微軟官方偵錯器文件撰寫。

若你在後續版本遇到步驟失效,歡迎在留言區回報,站長會更新文章。


🔗 延伸閱讀


廣告