⚡ 本文重點
- 文章屬性:教學實戰
- 適用系統:WinDbg 需 Windows 11 或 Windows 10 1607 以上
- 難易度 / 耗時:★★ / 約 20–40 分鐘
- 核心結論:
!locks列出的全是共用持有,也可能有執行緒被擋,因為依ExAcquireResourceSharedLite的說明,已有執行緒在等獨佔時,尚未持有該資源的新共用請求也得排隊;官方頁對!locks預設顯示範圍的說法不一,懷疑漏列時加-d,沒有可疑輸出也不代表沒有死結。 - 適用對象:電腦卡死、手上有核心傾印的進階使用者與 IT 人員
📌 快速答案
一句話答案:WinDbg !locks 列出核心 ERESOURCE 鎖的持有者;查死結時依序用
!locks記下持有者、!stacks 2 AcquireResource與!stacks 2 AcquireShared篩出排隊取鎖的執行緒、!thread看持有者狀態,再整理成表找出彼此互等的循環。– 需要核心、自動、主動或完整記憶體傾印;小型傾印檔以當機那條執行緒的資料為主,不適合查死結
🧰 開始前的準備
- 系統需求:WinDbg 支援 Windows 11 各版本與 Windows 10 1607 以上,處理器架構為 x64 與 ARM64。產生傾印檔的電腦,處理器與 Windows 版本不必和分析用的電腦相同;手邊電腦不符合需求時,把傾印檔複製到符合需求的電腦再分析
- 傾印檔類型(先確認再開始):要用核心記憶體傾印、自動記憶體傾印(內容與核心記憶體傾印相同)、主動記憶體傾印(Windows 10 起提供)或完整記憶體傾印,四者預設都寫入
%SystemRoot%\Memory.dmp。%SystemRoot%\Minidump裡的小型傾印檔以當機那條執行緒的資料為主,不適用本文流程。檔案不在預設位置或改過檔名時,依步驟一處理 - 權限需求:
%SystemRoot%底下的傾印檔,建議用系統管理員帳戶複製到工作資料夾;之後的分析只需要讀得到那份副本 - 需要工具:WinDbg(官方安裝頁提供安裝檔、Microsoft Store 與
winget install Microsoft.WinDbg三種方式),以及能連上網路的環境(.symfix需要連網下載符號) - 還沒有傾印檔時:電腦卡死卻沒有藍屏,就不會自動寫出傾印。微軟文件列出的刻意觸發當機方式有從偵錯器、從鍵盤與用電源鍵三種,三種都要事先準備:從偵錯器觸發要已在進行核心模式偵錯,用電源鍵觸發要先在登錄設定
PowerButtonBugcheck(Windows 10 1809 起提供),鍵盤方式則要改登錄並重新開機。以鍵盤為例,要在系統還能操作時先啟用傾印檔並把類型設為核心、自動、主動或完整記憶體傾印(設成小型傾印只會得到不適用本文流程的檔案),再依鍵盤類型(PS/2、USB 或 Hyper-V)在對應的登錄機碼建立CrashOnCtrlScroll並重新開機,等下次卡死時再按住最右側的 Ctrl 鍵、連按兩下 Scroll Lock,系統會以停止碼 0xE2 當機並寫出傾印;官方也警告強制當機會讓系統立即停止,可能遺失資料。依這些前置條件推論,以這三種方式而言,事前沒有設定時,眼前這次卡死拿不到傾印,要先完成設定,等問題再次發生時再觸發。這些設定會改動系統,不在本文流程內,完整做法見 WinDbg 進階指令教學:用 !process、!irp、!thread 追出卡死的驅動程式與 I/O 請求。系統還能操作時,也可以改用 LiveKd 直接分析執行中的核心(目前版本支援 Windows 11 以上與 Windows Server 2016 以上),做法見 LiveKd 教學:不必真的當機,用 WinDbg 分析正在執行中的 Windows 核心 - 本文流程的範圍:本文的步驟只讀取傾印檔(以 WinDbg 分析副本),不修改任何系統設定
- 預計耗時:約 20–40 分鐘;第一次下載符號檔時,網路慢會更久
- 難度門檻:會在 WinDbg 指令列輸入指令,看過
!analyze -v報告或呼叫堆疊
🔍 為什麼你需要這個?
電腦卡住的樣子不只一種:滑鼠游標能動卻點不了、畫面整個凍結,或是對滑鼠鍵盤完全沒反應、磁碟也不再讀寫。微軟文件把這些列為「系統凍結卻沒有真正觸發核心當機」的症狀,這時不會有藍屏,也不會自動留下傾印。就算用鍵盤強制當機拿到傾印,停止碼 0xE2 MANUALLY_INITIATED_CRASH 的官方說明是使用者刻意觸發、沒有任何參數,停止碼本身不指向哪個驅動程式。要找出卡住的原因,得看當下每條執行緒握著什麼、又在等什麼。
WinDbg !locks 回答前半題:依微軟文件,它顯示核心 ERESOURCE 鎖的資訊,系統發生死結時很有用。微軟〈Analyze a kernel-mode dump file by using WinDbg〉也把它列為分析核心傾印時特別好用的擴充指令之一。站內 !process、!irp 那篇追的是「I/O 請求卡在哪」,本文補上「鎖被誰握住」這一條路。
官方 !locks 參考頁說明了持有者的標示方式,以及以 Windows 2000 為背景的判斷原則,但沒有說明怎麼找出正在排隊取鎖的執行緒;判讀死結還得把 ERESOURCE 的取得規則、傾印檔的收錄範圍與其他指令一起看。
- 同名指令有兩個。在核心模式輸入
!locks,偵錯器自動套用 kdexts 前綴,列出 ERESOURCE;在使用者模式輸入!locks,套用的是 ntsdexts,列出目前行程的臨界區段(critical section)。官方參考頁特別註明兩者不要混淆,參考網路上的範例前,先分清楚是哪一種。 - 持有者看
!locks,排隊的執行緒另外找。官方參考頁說明的是持有者的標示方式,<*>代表這條執行緒是持有者之一。要找出正在排隊取鎖的執行緒,本文改用!stacks篩選堆疊,再用!thread逐一確認。 - 共用鎖也會把人擋住。ERESOURCE 可以讓多條執行緒同時共用,但依
ExAcquireResourceSharedLite的官方說明,只要已有執行緒在等獨佔存取,新的共用請求也要進入等待。畫面上全是 Shared,不代表沒人被擋。 - 小型傾印檔與空白輸出都要小心。小型傾印檔以當機那條執行緒的資料為主,官方提醒其他原因造成的錯誤可能分析不出來;
!locks又只看 ERESOURCE,佇列自旋鎖要用!qlocks、KMDF 自旋鎖要用!wdfkd.wdfspinlock。
📖 什麼是 ERESOURCE?死結是怎麼形成的?
ERESOURCE 是 Windows 核心提供給驅動程式的讀寫鎖:任何數量的執行緒都能以共用方式取得,獨佔同時只能有一條,而且要在沒有任何共用持有者時才拿得到。依微軟驅動程式文件,ERESOURCE 結構不透明、成員保留給系統使用,儲存空間必須從非分頁集區配置;系統會維護一份所有使用中 ERESOURCE 的清單,並為每個 ERESOURCE 維護獨佔等待者與共用等待者兩份等待清單。
| 取得方式 | 可以立刻取得的情況 | 取不到時 |
|---|---|---|
共用(ExAcquireResourceSharedLite) | 沒人持有;已由其他執行緒共用持有且沒有獨佔等待者;或呼叫端自己已持有這個資源 | Wait 為 TRUE 時進入等待,為 FALSE 時立刻回傳 FALSE |
獨佔(ExAcquireResourceExclusiveLite) | 沒人持有;或呼叫端自己已獨佔持有,可遞迴取得 | Wait 為 TRUE 時排進等待清單,為 FALSE 時回傳 FALSE;呼叫端只持有共用時也會進入等待 |
- 共用可以多條執行緒同時持有,獨佔同時只能一條。
- 已有獨佔等待者時,新的共用請求也要排隊,例外是呼叫端自己已經持有這個資源。
- Wait 參數決定取不到時是排隊等待,還是立刻回傳 FALSE。
- 以
ExAcquireResourceSharedLite為例,官方要求在 IRQL 小於等於 APC_LEVEL 時呼叫,呼叫前還必須先停用一般核心 APC。 - 依
ExAcquireResourceExclusiveLite的說明,只持有共用的呼叫端要先釋放,才能改要求獨佔;兩條執行緒各自共用持有同一個資源、又都不釋放就要求獨佔,會互相等待而死結。
死結是執行緒之間互相等待對方放手,誰也無法繼續。微軟〈Debugging a Deadlock〉的定義是兩條以上的執行緒,以互不相容的順序要求兩個以上資源的鎖;例子裡,執行緒一先鎖住資源 A、再要求 B,執行緒二先鎖住 B、再要求 A。ERESOURCE 另有只牽涉單一資源的情形,也就是表格下方最後一點提到的共用升級死結。官方說明核心模式的死結,是同一個行程或不同行程的多條執行緒,互相擋住對方存取同一份核心資源;使用者模式的說明中也提到,多數死結只牽涉兩條執行緒,少數情況是三條以上的執行緒繞成一圈。
📖 WinDbg !locks 語法與參數
WinDbg !locks 的語法是 !locks [Options] [Address],不帶位址時顯示系統中的 ERESOURCE 鎖。這個擴充指令位於 Kdexts.dll;核心模式下直接輸入 !locks,偵錯器會自動套用 kdexts 前綴,等同 !kdexts.locks。
!locks [Options] [Address]| 參數 | 作用(依官方參考頁) |
|---|---|
-v | 顯示每個鎖的詳細資訊 |
-p | 顯示所有可取得的資訊,包含效能統計 |
-d | 顯示所有鎖;不加時只顯示有爭用(contention)的鎖 |
Address | 指定一個 ERESOURCE 的十六進位位址;填 0 或省略時,顯示系統中所有 ERESOURCE 鎖的資訊 |
- 選項可以任意組合,例如
!locks -v <位址>查單一個鎖的詳細資訊。 - 官方頁面對預設顯示範圍的說法不一致:
-d的說明寫不加時只列有爭用的鎖,Address的說明寫省略時顯示所有 ERESOURCE 鎖,Remarks 則說它顯示執行緒持有的鎖。懷疑有鎖沒列出來時,加-d看全部。 -d的輸出會很長;官方範例那台系統的統計是 2263 個鎖,其中 3 個被持有。- 使用者模式同名的
!ntsdexts.locks列的是臨界區段,不適用本文流程。
🛠️ 實戰步驟
本文的步驟只讀取傾印檔(以 WinDbg 分析副本),不修改任何系統設定。步驟一到三是準備工作,步驟四到七是判讀死結的主流程。
步驟一:確認傾印檔類型,複製一份再用 WinDbg 開啟
先確認手上是核心、自動、主動或完整記憶體傾印,再複製一份到工作資料夾開啟。這四種傾印預設都寫在 %SystemRoot%\Memory.dmp;核心與完整記憶體傾印的官方說明都寫到,再發生一次錯誤檢查時舊檔會被覆寫,自動與主動記憶體傾印也寫在同一個路徑,分析前先複製一份比較保險。檔案來自 %SystemRoot%\Minidump 的是小型傾印檔,遇到它,或檔案是別人傳來、改過檔名、不在預設路徑而無法確認是上述四種傾印時,就停在這一步,改用「開始前的準備」提到的方式重新取得核心傾印。
在 WinDbg 按 Ctrl+D(Open dump file)選擇副本即可開啟;也可以從命令列執行 WinDbgX,用 -z 參數指定傾印檔,路徑含空白時要加引號。
WinDbgX -z D:\dumps\Memory.dmp步驟二:設定符號路徑
核心模式除錯需要 Windows 公用符號,先執行 .symfix 把符號路徑指向微軟符號伺服器,再用 .reload 重新載入。.symfix 需要偵錯器執行時能連上網路;符號檔保存函式名稱等除錯用的資料,後面要靠堆疊裡的常式名稱判讀,這一步不能省。符號路徑的完整設定與常見問題,見 WinDbg Preview 安裝與符號路徑設定完整教學。
.symfix
.reload步驟三:用 .bugcheck 確認這份傾印的來由
執行 .bugcheck 顯示錯誤檢查代碼與參數,先確認這份傾印是怎麼來的。
.bugcheck- 代碼是 0xE2(MANUALLY_INITIATED_CRASH):官方說明這代表使用者從核心偵錯器或鍵盤刻意觸發,沒有任何參數。停止碼不指向驅動程式,直接進入步驟四看鎖。
- 代碼是 0x1C8(MANUALLY_INITIATED_POWER_BUTTON_HOLD):官方說明這是按住電源鍵強制觸發的當機,同樣直接進入步驟四看鎖。
- 其他代碼:微軟建議多數情況先從
!analyze開始,先照!analyze -v的報告處理(做法見文末延伸閱讀的!analyze -v教學)。報告沒有指向明確的驅動程式,當機前又有卡住的症狀時,再回到步驟四查鎖。

步驟四:執行 !locks,記下被持有的鎖與持有者
**輸入 !locks,把每一筆 Resource @ 後面的位址、共用或獨佔,以及標了 <*> 的執行緒位址抄下來。**
!locks官方範例中的一筆輸出來自 32 位元系統,所以位址比較短。
Resource @ 0x80e23f38 Shared 1 owning threads
Threads: 80ed0023-01<*> *** Actual Thread 80ed0020<*>代表這條執行緒是持有者之一。- 執行緒位址帶有偏移時,後面會另外標出
*** Actual Thread真正的位址,後續步驟一律用這個位址。 - 輸出最後一行統計總共幾個鎖、目前幾個被持有,欄位的完整解讀見下方「判讀鎖狀態」一節。
輸出沒有任何被持有的鎖,加 -d 也一樣時,ERESOURCE 就不是這次的線索:改用步驟五不加篩選字串的 !stacks 2 看所有被擋住的執行緒停在哪裡;懷疑佇列自旋鎖時執行 !qlocks;執行緒卡在等待 I/O 請求時,改走前面提到的 !process、!irp 教學。列出來的全是共用持有時,官方提醒多數鎖本來就是共用的,照樣進入步驟五,看有沒有執行緒在等獨佔存取。
步驟五:用 !stacks 篩出正在排隊取鎖的執行緒
執行 !stacks 2 AcquireResource 與 !stacks 2 AcquireShared,只列出堆疊符號中含有這兩個字串的執行緒,找出正在排隊取 ERESOURCE 的執行緒。
!stacks 2 AcquireResource
!stacks 2 AcquireShared- 官方說明
!stacks的篩選字串只顯示「符號中含有該子字串」的執行緒;2表示顯示所有堆疊的完整參數,包含已被分頁出去的堆疊。 - 取得 ERESOURCE 的
ExAcquireResourceSharedLite、ExAcquireResourceExclusiveLite名稱都含AcquireResource;ExAcquireResourceSharedLite參考頁的另見清單還有ExAcquireSharedStarveExclusive與ExAcquireSharedWaitForExclusive,名稱不含這個字串,所以第二行再用AcquireShared篩一次。 - 依官方說明,Wait 為 TRUE 而取不到資源時,呼叫端會進入等待狀態;據此推論,正在排隊的執行緒,堆疊中通常看得到上述常式名稱。
- 每一列依序是「行程 ID.執行緒 ID」、ETHREAD 位址、執行緒狀態(如 blocked、running)與堆疊最上層的位址。記下 ETHREAD 位址,和步驟四的持有者名單對照。
兩行都篩不到任何執行緒時,改執行不加篩選字串的 !stacks 2,逐一看狀態為 blocked 的執行緒停在哪個常式;要連同每個行程的細節一起看,可以改用 !process 0 7,輸出會很長。
步驟六:用 !thread 檢查每一個持有者在做什麼
對步驟四的每個持有者執行 !thread <位址>,確認它正在執行還是停在等待,以及堆疊停在哪個常式。
!thread 80ed0020!thread預設的 flags 為 0x6,會顯示執行緒的等待狀態與堆疊。- 第一行結尾是執行緒狀態:執行中會顯示 RUNNING;在等待會顯示 WAIT,括號內是等待原因。官方
!locks -v範例中的持有者就是WAIT: (WrQueue) KernelMode Non-Alertable。 - 以官方範例的堆疊來看,最上面是
nt!KiSwapContext,最下面是堆疊中最早的框架nt!KiThreadStartup,由上往下是由新到舊的呼叫(!thread輸出另有Start Address欄位標示執行緒的起始常式,官方範例為nt!ExpWorkerThread)。依這個順序推論,持有者的堆疊裡若也出現步驟五的取鎖常式,代表它手上握著鎖,同時又在排隊取鎖(等的可能是另一個資源,也可能是自己共用持有的同一個資源);從該常式往下看,第一個不屬於 Windows 核心的模組,是優先追查的驅動程式。 - 也可以對單一資源執行
!locks -v <Resource 位址>,官方範例會把持有者的執行緒細節與堆疊一併列出。
持有者的狀態是 RUNNING 時,它不是停住的那一方。官方的判斷原則是找「沒有在執行、卻持有獨佔鎖」的執行緒,所以回到清單檢查下一個持有者。持有者在等待,堆疊裡卻沒有取鎖常式時,它等的是別的東西,例如 I/O 或事件,問題不在 ERESOURCE 這一層,改走前面提到的 !process、!irp 教學追查 I/O。
步驟七:整理持有與等待關係,驗證是否構成死結
把步驟四到六的結果整理成一張表,找出同時持有又在排隊、而且彼此互等的執行緒,這就是死結。官方描述的死結是執行緒一鎖住 A 再要 B、執行緒二鎖住 B 再要 A;共用升級死結則是兩條執行緒都共用持有同一個資源、都在等獨佔。表格填完後,還要確認每條執行緒各自在等哪個資源,才能確認有循環。官方〈Resource Time Outs〉以 32 位元系統示範讀法:看等待中執行緒的堆疊(官方範例用 kb;步驟六的 !thread 輸出也有 Args to Child 欄),找到以 ExpWaitForResource 開頭的函式(官方內文列出 ExpWaitForResourceExclusive、ExpWaitForResourceShared,同頁範例的堆疊顯示為 _ExpWaitForResource),那一行 Args to Child 欄的第一個值就是正在等的鎖,對這個位址執行 !locks <位址> 就能找到下一條要檢查的執行緒。照這個方法一條條追下去,追回起點就是循環;追到一條不再等待其他資源的執行緒時,官方說明那條執行緒多半就是問題源頭。官方沒有說明這個讀法在 x64、ARM64 等 64 位元傾印上是否一樣適用,所以讀出的值要是步驟四 !locks(懷疑漏列時加 -d)列出的某個 Resource @ 位址才採用;讀不出位址、讀出的值不在清單中,或對它執行 !locks 沒有列出持有者時,改對步驟四的候選資源逐一執行 !locks -v <位址>,找出列有獨佔等待者(NumberOfExclusiveWaiters)、持有者又停在等待的資源,再和各執行緒的狀態互相比對。
| 執行緒 | 持有的鎖(步驟四) | 是否在排隊取鎖(步驟五) | 狀態與模組(步驟六) |
|---|---|---|---|
| 甲 | 資源 A,獨佔 | 是 | WAIT,驅動程式 X |
| 乙 | 資源 B,獨佔 | 是 | WAIT,驅動程式 Y |
- 同時出現在持有者清單與排隊清單、狀態又是等待的執行緒,是頭號嫌疑者。
- 它等的資源不一定是別人獨佔的:依
ExAcquireResourceExclusiveLite的說明,只持有共用的執行緒再要求獨佔,會在自己持有的同一個資源上等待。比對候選時,要把它自己持有的資源一起列入(清單沒看到就加-d)。 - 候選不只一個時,依官方判斷原則先看「獨佔持有、持有者又停在等待」的資源;另依
ExAcquireResourceExclusiveLite對共用升級死結的說明推論,多條共用持有者都在排隊的資源也要一併檢查,再對這些資源執行!locks -v <位址>查看詳細資訊。 - 表格中的資源 A、B 與驅動程式 X、Y 只是示意,請換成自己傾印中的位址與模組名稱。
驗證方式:循環中的每條執行緒,都必須同時出現在 !locks 的持有者清單與 !stacks 的排隊清單,而且 !thread 顯示為等待狀態。任一條件不成立,就不是 ERESOURCE 的循環死結,回到步驟四最後一段的分支改查其他方向。確認死結後,記下堆疊中發出取鎖要求的模組名稱,查它的版本與廠商(做法見延伸閱讀的 lmvm 教學),再把傾印與這些資訊交給該驅動程式的廠商,或確認是否有更新版本。
🔎 判讀鎖狀態:從 !locks 輸出看出誰卡住誰
判讀 !locks 的原則來自官方:找一條沒有在執行、卻持有獨佔鎖的執行緒;本文依官方原則與死結定義整理,那個鎖正是另一條執行緒要取得的。官方同時提醒多數鎖是共用的,所以輸出裡一長串 Shared 不必逐一追查,先挑獨佔持有的資源。
| 輸出內容 | 意思(依官方參考頁) |
|---|---|
Resource @ 位址 | 這個 ERESOURCE 的位址,可以帶進 !locks -v、dt ERESOURCE 查更多資訊 |
Shared 4 owning threads | 以共用方式持有,持有的執行緒共 4 條 |
Threads: ff688da0-01<*> | 執行緒位址;-01 官方稱為 thread count,<*> 表示持有者之一 |
*** Actual Thread 80ed0020 | 執行緒位址含偏移時,另外標出的實際位址 |
2263 total locks, 3 locks currently held | 系統中鎖的總數,以及目前被持有的數量 |
- 共用或獨佔,決定這筆紀錄要不要優先追。
- 後續指令一律使用
Actual Thread標出的位址。 - 官方範例來自 32 位元系統,位址只有 8 位十六進位;x64 系統的位址長得多,例如
!thread官方 Windows 10 範例中的ffffcb088f0a4480。 !locks參考頁的範例只示範共用持有;官方〈Resource Time Outs〉頁的範例中,獨佔持有會標Exclusively owned,並列出Contention Count與NumberOfExclusiveWaiters(獨佔等待者數)。
官方的 !locks -v 範例,示範了「沒在執行的持有者」長什麼樣子。資源 0x80d8b0b0 由執行緒 80ed0020 以共用方式持有,這條執行緒的狀態是 WAIT: (WrQueue) KernelMode Non-Alertable,Start Address 欄位顯示 nt!ExpWorkerThread,堆疊最上面四個框架依序是 nt!KiSwapContext、nt!KiSwapThread、nt!KeRemoveQueue、nt!ExpWorkerThread。它握著鎖,自己卻處於等待狀態;差別在於這個例子是共用持有,要構成死結,還得有其他執行緒正在等它握住的鎖,而且彼此的等待能串成循環。
🔬 底層機制:一個鎖為什麼能讓整台電腦停住?
ERESOURCE 位在核心裡,由系統元件與驅動程式共同使用;資源被獨佔持有、或已有執行緒在等獨佔時,之後以 Wait 為 TRUE 要求同一個資源的執行緒會一條接一條進入等待。官方說明核心模式的死結,可能牽涉同一個行程或不同行程的執行緒,卡的是同一份核心資源。據此推論,不同應用程式只要呼叫到需要同一個資源的核心程式碼,就會一起被擋在同一個地方,外觀上就是整台電腦沒有反應。
獨佔等待者會讓後到的共用請求也排隊。依 ExAcquireResourceSharedLite 的說明,資源被共用持有、但已有執行緒在等獨佔存取時,新的共用請求會進入等待,呼叫端自己已持有者除外。據此推論,只要持有者停住不放,想要獨佔的執行緒會開始排隊,之後想要共用的執行緒也會被擋在它後面,排隊的範圍會越拉越大。
以 Wait 為 TRUE 取鎖卻取不到的執行緒,會真的進入等待。ExAcquireResourceSharedLite 的 Wait 參數為 TRUE 時,呼叫端會被放進等待狀態,直到能取得資源;官方另要求呼叫時 IRQL 小於等於 APC_LEVEL,呼叫前先以 KeEnterCriticalRegion 停用一般核心 APC,直到資源釋放後才以 KeLeaveCriticalRegion 恢復。據此推論,排隊中的執行緒會以等待狀態留在傾印裡,!stacks 與 !thread 才看得到它們停在取鎖常式。
系統替每個 ERESOURCE 記帳。官方說明系統會維護一份所有使用中 ERESOURCE 的清單,驅動程式不再需要某個 ERESOURCE 時,必須呼叫 ExDeleteResourceLite 把它從清單中刪除;而 !locks 不帶位址時,官方說明會顯示系統中所有 ERESOURCE 鎖的資訊。
💡 總結:進階玩法與底層邏輯
WinDbg !locks 回答「哪個 ERESOURCE 被誰握住」,把它和 !stacks、!thread 串起來,才能回答「誰卡住誰」。固定順序是 .bugcheck 確認來由、!locks 列持有者、!stacks 篩排隊的執行緒、!thread 看持有者,最後整理成表找循環,可以避免只看到一串 Shared 就下結論。
其他類型的鎖要換指令。官方列出的核心死結相關擴充指令,還有顯示所有佇列自旋鎖狀態的 !qlocks、顯示 KMDF 自旋鎖物件的 !wdfkd.wdfspinlock,以及搭配 Driver Verifier、偵測程式碼中可能造成死結之不一致用鎖的 !deadlock。!deadlock 屬於驅動程式開發端的工具,需要先啟用 Driver Verifier,不在本文流程內。
應用程式卡住是另一條路。使用者模式輸入 !locks 會執行 ntsdexts 版本,查的是目前行程的臨界區段與持有者執行緒;官方流程是用 ~ 列出執行緒,再用 kb 看它們是否在等其他臨界區段。
不適用的情況也要先知道。只有小型傾印檔時,其他執行緒的資料不足;卡在 ERESOURCE 以外的同步機制時,!locks 看不到;系統若停在高 IRQL,官方說明鍵盤強制當機會失效,拿不到傾印就無從分析。
本文依 Microsoft Learn 的 !locks 參考頁(頁面標示最後更新 2023-10-25)、Debugging a Deadlock、ERESOURCE 常式說明,以及 !stacks、!thread、傾印檔相關文件整理,2026-09-30 查證。
❓ 常見問題
Q:WinDbg !locks 跟 !process 差在哪?
出發點不同。!locks 從鎖出發,列出 ERESOURCE 被哪些執行緒持有;!process 從行程出發,顯示行程資訊與 EPROCESS 區塊,flags 設為 7 時會列出完整細節,包括每條執行緒的等待狀態與堆疊。查死結時先用 !locks 找出持有者,再用 !stacks 或 !process 0 7 看其他執行緒;官方說明 !stacks 可以取代 !process 快速掌握全系統狀態,特別適合資源衝突或死結這類多執行緒問題。
Q:怎麼從 !locks 看出誰卡住誰?
要把三個指令的結果對起來,而且得用核心、自動、主動或完整記憶體傾印。!locks 的 <*> 標出持有者;!stacks 2 AcquireResource 與 !stacks 2 AcquireShared 篩出正在排隊取鎖的執行緒;!thread 確認持有者是在執行還是等待。同時出現在持有者與排隊名單、狀態又是等待的執行緒,就是卡住別人的嫌疑者;再確認它們各自在等的資源彼此對上、形成循環,才是死結。
Q:只有在當機當下才能用 !locks 嗎?
不必等到當機當下。事後分析核心、自動、主動或完整記憶體傾印都能用。系統還能操作時,可以用 Sysinternals 的 LiveKd 在執行中的系統上跑 WinDbg 或 KD,官方說明它能執行所有可用於傾印檔的偵錯器指令,加上 -m 參數可以建立核心記憶體內容一致的鏡像傾印;目前版本支援 Windows 11 以上與 Windows Server 2016 以上。整台電腦已經卡死、沒辦法操作時,要靠「開始前的準備」提到的強制當機方式取得傾印,而那些設定必須在卡死前完成;以這三種強制當機方式而言,事前沒設定時,這次拿不到傾印,要先完成設定,等問題再次發生。
Q:!locks 什麼都沒列出,代表沒有死結嗎?
不代表。!locks 只顯示 ERESOURCE 鎖,佇列自旋鎖要用 !qlocks、KMDF 自旋鎖要用 !wdfkd.wdfspinlock 查。官方頁面對預設顯示範圍的說法也不一致,懷疑漏列時加 -d 看全部。仍然沒有線索時,用 !stacks 2 看被擋住的執行緒停在哪裡;執行緒卡在等待 I/O 請求時,改用 !irp 追查。
Q:小型傾印檔可以用 !locks 嗎?
不建議。依官方說明,小型傾印檔收錄的是錯誤檢查訊息與參數、當機那顆處理器的 PRCB、當機行程的 EPROCESS、當機執行緒的 ETHREAD 與核心堆疊、已載入驅動程式清單等,另加 Windows 判斷有用的額外記憶體頁;官方也提醒,不是由當機當下那條執行緒直接造成的錯誤,可能無法從這種檔案分析出來。死結牽涉的是其他執行緒,要改用核心、自動、主動或完整記憶體傾印。
Q:這個方法在舊版本也適用嗎?
要分兩台電腦看。分析用的電腦要符合 WinDbg 的需求,也就是 Windows 11 各版本或 Windows 10 1607 以上;產生傾印檔的電腦,處理器與 Windows 版本不必相同。!locks 的官方頁面以 Windows 2000 說明判斷原則,範例也是 32 位元輸出,x64(64 位元)系統的位址較長。自動記憶體傾印從 Windows 8、主動記憶體傾印從 Windows 10 開始提供,更早的系統改用核心或完整記憶體傾印;依官方說明,較舊版本 Windows 的傾印,可以改用 Debugging Tools for Windows 內的 WinDbg (classic)。
🔗 延伸閱讀
- WinDbg !analyze -v 完整教學:逐欄位讀懂當機分析報告,搞懂 STACK_TEXT 與 FAILURE_BUCKET_ID
- WinDbg lmvm 教學:一行指令看懂當機驅動模組與版本資訊
- DumpChk 教學:不開完整 WinDbg,先用微軟官方小工具快速檢查傾印檔是否完整
- WinDbg !irql 教學:一行指令讀出當機前一刻的中斷請求層級
📎 參考資料來源
📖 第一級|廠商官方:
- !locks (!kdext*.locks)(Microsoft Learn) — 2026-09-30 查證
- Debugging a Deadlock(Microsoft Learn) — 2026-09-30 查證
- ERESOURCE structure and routines(Microsoft Learn) — 2026-09-30 查證
- ExAcquireResourceSharedLite function(Microsoft Learn) — 2026-09-30 查證
- ExAcquireResourceExclusiveLite function(Microsoft Learn) — 2026-09-30 查證
- !thread(Microsoft Learn) — 2026-09-30 查證
- !stacks(Microsoft Learn) — 2026-09-30 查證
- !process(Microsoft Learn) — 2026-09-30 查證
- Analyze a Kernel-Mode Dump File by Using WinDbg(Microsoft Learn) — 2026-09-30 查證
- Small Memory Dump(Microsoft Learn) — 2026-09-30 查證
- Kernel Memory Dump(Microsoft Learn) — 2026-09-30 查證
- Automatic Memory Dump(Microsoft Learn) — 2026-09-30 查證
- Complete Memory Dump(Microsoft Learn) — 2026-09-30 查證
- Active Memory Dump(Microsoft Learn) — 2026-09-30 查證
- Install WinDbg(Microsoft Learn) — 2026-09-30 查證
- WinDbg keyboard shortcuts(Microsoft Learn) — 2026-09-30 查證
- WinDbg: Command-line startup options(Microsoft Learn) — 2026-09-30 查證
- Symbols for Windows debugging(Microsoft Learn) — 2026-09-30 查證
- .symfix (Set Symbol Store Path)(Microsoft Learn) — 2026-09-30 查證
- .reload (Reload Module)(Microsoft Learn) — 2026-09-30 查證
- Forcing a System Crash(Microsoft Learn) — 2026-09-30 查證
- Forcing a system crash from the keyboard(Microsoft Learn) — 2026-09-30 查證
- Forcing a System Crash with the Power Button(Microsoft Learn) — 2026-09-30 查證
- Forcing a System Crash from the Debugger(Microsoft Learn) — 2026-09-30 查證
- Bug Check 0xE2 MANUALLY_INITIATED_CRASH(Microsoft Learn) — 2026-09-30 查證
- Resource Time Outs(Microsoft Learn) — 2026-09-30 查證
- LiveKd v5.65(Microsoft Learn Sysinternals) — 2026-09-30 查證
⚠️ 本文核心事實以第一級為準。!locks 參考頁對預設顯示範圍的說明(-d、Address 參數與 Remarks)不一致,已於正文揭露;官方範例為 32 位元系統的輸出。
📅 本文查證戳記:2026-09-30 依據 Microsoft Learn 之 !locks 參考頁與 WinDbg、ERESOURCE、傾印檔相關文件查證撰寫,內容整理自官方文件,未在實機上執行。
若你在後續版本遇到步驟失效,歡迎在留言區回報。
