Stories

Detail Return Return

一次 Windows 內核卡住的調試經歷 - Stories Detail

一次 Windows 內核卡住的調試經歷

今年整了個電腦,本想提升生活遊戲體驗,卻被一個及其影響體驗的問題折磨了三個月,後來終於算是得到了解決(規避)。過程是曲折的,結果是比較抽象的。於是乎記錄一下這個問題的前因後果,畢竟也算是不影響體驗了,不算白費力氣。

語音聊天,神秘卡死

某知名聊天軟件,經常是大家水羣的好去處,有時,大家也會通過羣聊語音開黑。但是我卻發現一個神秘現象——系統卡死。毫無徵兆,隨時觸發,短則 2 秒,長則 2 分鐘,通常 20 秒多一點。

這裏説一下背景:上網是 Intel 的 AX210 無線網卡,同時顯卡是 AMD 的 9070GRE,二者都是走的 PCIe,其他就不説了,因為問題圍繞二者展開。

這個卡死呢也是很奇怪,如果 WiFi 連的是 5 GHz 的網,卡死的概率大一些,卡死的時間長一些 (基本都是很長的那種),如果是 2.4 GHz 的網,卡死概率就會低,而且通常都是卡 2 秒左右。所以懷疑到網卡上去了,畢竟 AX 系列網卡也是臭名昭著了,或者説,整個 Intel 的網卡都有口皆碑。尤其是還在系統日誌看到大量報錯,很難不讓人懷疑,是吧。

ax210_error

雖然還有一個詭異的現象:只有使用某軟件進入羣聊語音,且羣聊語音窗口界面在前台,即沒有最小化等情況。其他語音軟件倒是沒遇到這個問題。但首先主要懷疑到網卡上了,還跑去 Intel 的官方論壇發帖,得到的答覆主要也是更新系統、更新驅動、重置網卡等等。我還找了客服反饋,然後説改電源模式到高性能模式(這個記住,後面要考),不允許系統自動關閉網卡等,有一定緩解,但沒有本質的結果。當然這個事情我也求助了 D 老師(大名鼎鼎的 Deep Seek),不過也沒什麼有效的結果(雖然 D 老師後面立大功)。

眼看這個問題似乎無解,就這麼一直卡着(我是到了 8 月中旬才開始積極排查想要解決的,畢竟也不是不能規避,就是把窗口最小化,但這樣不是個事,尤其是屏幕共享還不能給最小化了,然後就非常容易卡死)。後面甚至懷疑到某軟件的 bug 去了,畢竟該軟件所屬公司的情況也是人盡皆知。

暫時擱置,記錄現象

但是由於短時間沒什麼進展,我就想能不能自動的記錄這個,避免人眼盯着看的麻煩。

直覺告訴我,這樣的卡死,一定是某個驅動導致了內核阻塞,那麼既然是內核阻塞,大概率會導致線程無法調度。所以我就想到寫一個小工具,在線程裏面檢測 Sleep 前後兩次 Tick 的差值,就可以知道是不是卡住了。判斷閾值是 100ms,Sleep 時間是 40ms,排除偶爾的高負載導致卡頓,基本不太可能會出現 Tick 差值大於 100ms 的情況。

事實也是如此。

detect_freezing

這是某一次記錄到的情況,是不是非常誇張,短短 15 分鐘出現了那麼多次。(這裏 125 的那次看似誤判,實則不然,因為後來問題解決後我長時間運行從來沒有遇到過類似誤判,説明就是極其短暫的卡頓 )

陷入僵局,無從下手

事實上,這個檢測工具是從我開始着手解決問題的時候寫的,而半個多月了幾乎沒有進展。難道這個問題就無法解決了嗎,難道我就要被這個問題一直困擾了嗎?也許哪一天,系統的一次更新、驅動的一次更新、軟件的一次更新,這個問題就自己消失了。

但我不知道那一天會是什麼時候。某一天,一次偶然的 BSOD(大家常説的藍屏,但是現在的 Windows 11 沒有“藍”屏了)打破了一切。我熟練的使用 WinDbg 打開了 Minidump 文件,找到了調用棧,根據 D 老師的指引,確定是一個死鎖問題。

# Child-SP          RetAddr               Call Site
00 ffffb100`d8033c28 fffff807`7abe4866     nt!KeBugCheckEx
01 ffffb100`d8033c30 fffff807`7abe3d9a     nt!KeAccumulateTicks+0x596
02 ffffb100`d8033ca0 fffff807`7ab55361     nt!KiUpdateRunTime+0xca
03 ffffb100`d8033e60 fffff807`7ab55cbb     nt!KeClockInterruptNotify+0x431
04 ffffb100`d8033f50 fffff807`7aea67be     nt!KiCallInterruptServiceRoutine+0x31b
05 ffffb100`d8033fb0 fffff807`7aea6fcc     nt!KiInterruptSubDispatchNoLockNoEtw+0x4e
06 ffff8385`1770f190 fffff807`7aa585ff     nt!KiInterruptDispatchNoLockNoEtw+0x3c
07 ffff8385`1770f320 fffff807`7abe290f     nt!KxWaitForLockOwnerShip+0x5f
08 ffff8385`1770f3a0 fffff807`7aa4940d     nt!KiAcquireThreadStateLockForWrite+0x14f
09 ffff8385`1770f410 fffff807`7aa4913a     nt!KiSetPriorityThread+0x5d
0a ffff8385`1770f4c0 fffff807`7aa4a62d     nt!KiAbCpuBoostOwners+0x2da
0b ffff8385`1770f570 fffff807`7ab31de2     nt!KiAbProcessThreadLocks+0x67d
0c ffff8385`1770f660 fffff807`7ab322e5     nt!KiAbPropagateBoosts+0x72
0d ffff8385`1770f6a0 fffff807`7aa0bee6     nt!KiExecuteAllDpcs+0x4e5
0e ffff8385`1770f8f0 fffff807`7aea564e     nt!KiRetireDpcList+0x326
0f ffff8385`1770fb80 00000000`00000000     nt!KiIdleLoop+0x9e

可惜當我想進一步追究的時候,我才發覺 Minidump 沒有那麼多的內存信息(後來我改成把活動內存 dump 下來了)。眾所周知,系統卡死,除了系統本身的原因,大概率就是驅動導致的了。加上這個死鎖現象,我懷疑卡死和藍屏有關。當然只是懷疑,畢竟如果真是死鎖,那應該經常藍屏了,但是也可以基本確定是某個驅動由於 bug 導致執行某個任務耗時過長了。

此時我想到,既然藍屏求之不得(從沒想過會這麼盼着藍屏到來),不如主動出擊,用 WinDbg 直接上機調試。説幹就幹。(順便誇一下,微軟商店那個新版的 WinDbg 比起祖傳的那個是好看又好用,強烈推薦)

初現端倪,主動出擊

我查了一下 WinDbg 調試物理機內核的方法,種種路徑最終指向這一篇官方文檔:設置內核模式調試 - Windows drivers | Microsoft Learn,官方推薦的是自動設置 KDNET 網絡內核調試 - Windows drivers | Microsoft Learn,但是由於我用工具檢測不支持,所以只好抱着試一試的心態,參考手動設置 KDNET 網絡內核調試 - Windows drivers | Microsoft Learn,手動試了一番。雖然過程有點曲折,但還是搞定了。(如果用 kdnet 檢測沒問題,建議使用 kdnet 一鍵完成配置,比手動配置方便的多;kdnet 需要單獨去 Windows SDK - Windows 應用開發 | Microsoft Developer 下載,然後安裝時勾選 Debugging Tool for Windows 才可以,默認路徑在 C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

首先準備一台主機和目標機,主機是運行 WinDbg 客户端的,目標機就是被調試的機器。使用以太網線將二者網口相連(可以通過交換機、路由器,也可以直連,簡單點就直連)。

使用 ipconfig 命令確定主機的 IP 地址,以我的為例,是 169.254.131.234,使用目標機去 ping 這個 IP,確保能通。保險起見,也可以查詢目標機的 IP 地址,用主機去 ping,確保雙方通信正常。如果不通可以檢查防火牆的入站規則 文件和打印機共享 (回顯請求 - ICMPv4-In),確保已啓用且允許連接,還不行再重啓。全程開啓 Wireshark 抓包也可以快速排查網絡問題導致的調試器連接失敗,大幅度提升體驗。

之後使用如下命令去設置調試模式和調試用的網卡。

bcdedit /debug on
bcdedit /dbgsettings net hostip:169.254.131.234 port:50000
bcdedit /set "{dbgsettings}" busparams 94.0.0
  • 第一行啓用調試,需要確保 BIOS 的安全啓動(Security Boot)是關掉的,否則是不能調試的
  • 第二行設置調試模式,hostip 就是前面確定的主機 IP,port 通常是 50000
  • 第三行設置 busparams,需要去設備管理器看看網卡對應的位置,比如我的是“PCI 總線 94、設備 0、功能 0”,那麼就是 94.0.0

設置完後可以用 bcdedit /dbgsettings 查看設置結果,類似下面這樣就説明 OK 了。

busparams               94.0.0
key                     1.2.3.4
debugtype               NET
hostip                  169.254.131.234
port                    50000
dhcp                    Yes
isolatedcontext         Yes

之後為了避免一些問題,先拔掉網線,然後重啓。重啓之後再看網卡屬性,可以看到“此設備已為 Windows 內核調試程序預留,以便在此啓動會話持續期間使用。 (代碼 53)”,説明已經啓用了調試。為什麼要拔掉網線呢,因為一旦網線接好了,重啓的時候內核就會加載調試模式了,如果哪裏出了問題,可能導致無法調試。

確認無誤後,接上網線,再重啓目標機,注意此時主機千萬別啓用調試,因為這個階段可能會 BSOD 導致沒法進系統(除非要調試有些驅動的初始化過程,但是我們肯定用不到)。這個時間會很久,開機個十分鐘很正常,看似卡死實際上是正常的,耐心等待就行了。

在開機後,任務管理器還能看到多了一個內核調試專用的網卡。這個時候可以檢查一下雙機之間的網絡通信情況,一般來説 IP 地址是不會變的,但是説不準,一定要確保主機的 IP 固定不變。

之後在主機使用 WinDbg 連接調試即可。

attach_to_kernel

選擇 Attach to kernel,只需要指定端口號和 Key 即可,剩下的會自動連接。

連接成功後,應該會打印日誌,此時就可以隨時按下快捷鍵中斷內核了。有一個小細節,此時如果在目標機按下截圖鍵,也會被中斷(當然對我來説沒什麼用,畢竟我需要中斷的時候系統已經卡死了)。

問題現場,中斷分析

基於前面的經驗,通常卡死時間會有 20 秒左右,所以當出現卡死時,心中默唸幾秒,發現還是卡住立刻按下快捷鍵中斷內核。此時現場就在眼前,秘密即將被揭開。

然而不想的時候總是來,想要的時候等不到。許多事情總是這樣,所以運氣也是很重要的一部分。功夫不負有心人,在等了好久之後,終於遇到了一次標準的卡死。果斷按下中斷快捷鍵,內核很快就停了下來。

00 ffff9105`2784e238 fffff800`0a5414a9 nt!DbgBreakPointWithStatus 
01 ffff9105`2784e240 fffff800`08700f52 kdnic!MPSendNetBufferLists+0x3e9 
02 ffff9105`2784e330 fffff800`08700e1e ndis+0x40f52 
03 ffff9105`2784e470 fffff800`086fc122 ndis+0x40e1e 
04 ffff9105`2784e4b0 fffff800`087b1bb2 ndis+0x3c122 
05 ffff9105`2784e5c0 fffff800`08ac474d ndis+0xf1bb2 
06 ffff9105`2784e840 fffff800`08ac3a3f tcpip!FlpNdisSendNbls+0xbd 
07 ffff9105`2784ea70 fffff800`08ac2d18 tcpip!FlpSendPacketsHelper+0xdf 
08 ffff9105`2784eb30 fffff800`08ac2147 tcpip!IppFragmentPackets+0x368 
09 ffff9105`2784ec80 fffff800`08ac0ce7 tcpip!IppDispatchSendPacketHelper+0x77 
0a ffff9105`2784edc0 fffff800`08ae43bb tcpip!IppPacketizeDatagrams+0x2e7 
0b ffff9105`2784efd0 fffff800`08b27b0d tcpip!IppSendDatagramsCommon+0x70b 
0c ffff9105`2784f180 fffff800`08ab468a tcpip!IppSendDatagrams+0x25 
0d ffff9105`2784f1c0 fffff800`08ab5611 tcpip!IppSendDirect+0xee 
0e ffff9105`2784f300 fffff800`08b2a951 tcpip!Ipv6pSendNeighborSolicitation+0x141 
0f ffff9105`2784f3c0 fffff800`08b26bbb tcpip!IppSendNeighborSolicitation+0xf9 
10 ffff9105`2784f420 fffff800`08b25bee tcpip!IppNeighborSetTimeout+0x1eb 
11 ffff9105`2784f580 fffff800`08b25369 tcpip!Ipv6pInterfaceSetTimeout+0x8e 
12 ffff9105`2784f5d0 fffff800`08b247e0 tcpip!IppCompartmentSetTimeout+0x129 
13 ffff9105`2784f630 fffff800`752eed92 tcpip!IppTimeout+0xb0 
14 ffff9105`2784f680 fffff800`752ef998 nt!KiProcessExpiredTimerList+0x502 
15 ffff9105`2784f7b0 fffff800`75319f36 nt!KiTimerExpiration+0x5d8 
16 ffff9105`2784f8f0 fffff800`756a09fe nt!KiRetireDpcList+0xc46 
17 ffff9105`2784fb80 00000000`00000000 nt!KiIdleLoop+0x9e

這是中斷現場的棧,很明顯,這是因為我們使用調試器下的中斷,所以應該使用 !running -it 來查看其他 CPU 核執行的情況。(執行命令的時候可能會很慢,此時左下角可以看到在從微軟的服務器下載符號表,耐心等待吧)

果然,發現一個可疑的棧:

5 ffffb6815e4d7180 ffff808deba73180 (16) ffff808da65bb280 ................ 
Unable to load image \SystemRoot\System32\DriverStore\FileRepository\u0417878.inf_amd64_cf56f0cbce08e931\B417693\amdkmdag.sys, Win32 error 0n2 
# Child-SP RetAddr Call Site 
00 ffff9105`28d05e78 fffff800`1f1b5247 amdkmdag+0x1987b14 
01 ffff9105`28d05e80 fffff800`1f63974f amdkmdag+0x14f5247 
02 ffff9105`28d05ef0 fffff800`1f656d74 amdkmdag+0x197974f 
03 ffff9105`28d06310 fffff800`1e22cb2a amdkmdag+0x1996d74 
04 ffff9105`28d069a0 fffff800`1e25ef79 amdkmdag+0x56cb2a 
05 ffff9105`28d069f0 fffff800`1e24310b amdkmdag+0x59ef79 
06 ffff9105`28d06a40 fffff800`1e25ddea amdkmdag+0x58310b 
07 ffff9105`28d06ab0 fffff800`1e1a8282 amdkmdag+0x59ddea 
08 ffff9105`28d06ae0 fffff800`1de88cd5 amdkmdag+0x4e8282 
09 ffff9105`28d06b30 fffff800`1dd00b01 amdkmdag+0x1c8cd5 
0a ffff9105`28d06be0 fffff800`1deb1edc amdkmdag+0x40b01 
0b ffff9105`28d06da0 fffff800`070c28b6 amdkmdag+0x1f1edc 
0c ffff9105`28d06f30 fffff800`0ad7a02d dxgkrnl!ADAPTER_DISPLAY_DdiSetVidPnSourceAddressWithMultiPlaneOverlay3+0x116 
0d ffff9105`28d07020 fffff800`0adee705 dxgmms2!VidSchiExecuteMmIoFlipAtPassiveLevel+0x13d 
0e ffff9105`28d07aa0 fffff800`0ade5361 dxgmms2!VidSchiRun_PriorityTable+0x205 
0f ffff9105`28d07af0 fffff800`75487c2a dxgmms2!VidSchiWorkerThread+0xe1 
10 ffff9105`28d07b30 fffff800`756a0b24 nt!PspSystemThreadStartup+0x5a 
11 ffff9105`28d07b80 00000000`00000000 nt!KiStartSystemThread+0x34

看起來就是從 Windows 的 DX 層一路調用到了 AMD 的顯卡驅動。儘管沒有符號,但基本可以看出來是在驅動裏面卡住了。這裏其實有一個很細節的點,也是後來 D 老師提醒的時候我才發現的,就是 0c 這一層的棧,函數名叫 ADAPTER_DISPLAY_DdiSetVidPnSourceAddressWithMultiPlaneOverlay3,後面的 MultiPlaneOverlay 就是所謂的 MPO,而網上一搜就可以看到多個顯卡驅動和 MPO 之間的兼容性問題導致的卡死問題,不過現象不太一樣,但本質還是這個。只是沒想到按照網上的説法這個 bug 應該已經修了,但實際上看起來並沒有完全解決。後面 AMD 顯卡驅動層的具體原因就沒法看了,但是應該不是死鎖,因為總是能恢復的,只是從幾秒到幾十秒不等,不知道是什麼邏輯耗時很長。

註冊表里加一個

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Dwm]
"OverlayTestMode"=dword:00000005

禁用 MPO 來規避這個問題。不過這裏我還懷疑是不是背後還有其他原因,畢竟只有使用某軟件才會出現,不使用就不會有問題,是不是某軟件觸發了什麼奇怪的東西。但是分析這個軟件比較棘手,而且可能會偏離方向(畢竟都是調用系統接口,不會直接操作網卡、顯卡的),就沒有繼續。

本來以為到這一步,問題總算是解決了。但是不料現實狠狠打臉,剛改完重啓,想着驗證一下結果,馬上就給我卡了幾次,不過都是很輕微的,只有幾百毫秒,而且也沒有出現長時間的卡頓。問題現象的不同,也説明是有效果的,但是還有地方沒有完全解決。

雙管齊下,煙消雲散

這裏還有一個細節,無線網卡和顯卡都是接在 PCIe 上面的,而二者通常是同時工作的,會不會互相影響了呢?這可能是一個可以排查的點。

首先,按照 D 老師指引,分析 IRQ 是否衝突,這個一看就並沒有。而第二點,PCIe 電源管理看着就很有可能了。打開控制面板,依次 系統和安全——電源選項——更改計劃設置——更改高級電源設置——PCI Express——鏈接狀態電源管理,可以看到默認是最大電源節省量,把它改成關閉(高性能模式是會關掉的),保險起見在網卡驅動設置裏面也把允許計算機關閉此設備以節約電源給關掉。

之後,重啓電腦,接着驗證。這次奇蹟發生了,經過長時間的驗證,確實沒有出現明顯卡頓,工具也檢測不到了。好幾天過後,依然沒有問題,説明基本已經解決了。然後鴿了倆月,還是確認沒有問題。

現象是這樣,原因就不得而知了,首先 A 卡驅動和 MPO 肯定是有兼容性問題的,然後是 Windows 的 PCIe 電源策略可能有問題,至於是和 A 卡還是和 Intel 的 AX 網卡哪個有問題,就不知道了。

好了好了,也不細究,規避掉沒問題了就行,牛馬打工夠累了,想玩個遊戲電腦還來這一出,😫

Add a new Comments

Some HTML is okay.