調試筆記:系統掛在DPC(上)

這是發生在我的筆記本電腦上的一次系統掛死,發生在喚醒過程中,屏幕沒有任何顯示,因為我的電腦始終是啟用通過熱鍵(Ctrl+ScrollLock)來觸發藍屏的,所以可以通過熱鍵觸發藍屏和產生轉儲。

以下是分析轉儲文件的簡要過程,轉儲的類型是內核轉儲。系統中只有一個CPU。

kd> !cpuinfo
CP  F/M/S Manufacturer  MHz PRCB Signature    MSR 8B Signature Features
 0  6,13,8 GenuineIntel 1862 0000002000000000                   a0033fff
                      Cached Update Signature 0000002000000000
                     Initial Update Signature 0000002000000000

操作系統是XP SP2:

kd> version
Windows XP Kernel Version 2600 (Service Pack 2) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp_sp2_qfe.070227-2300

執行!pcr命令觀察CPU的控制區:

kd> !pcr
KPCR for Processor 0 at ffdff000:
    Major 1 Minor 1
 NtTib.ExceptionList: 80548fec
     NtTib.StackBase: 805492f0
    NtTib.StackLimit: 80546500
  NtTib.SubSystemTib: 00000000
       NtTib.Version: 00000000
   NtTib.UserPointer: 00000000
       NtTib.SelfTib: 00000000

             SelfPcr: ffdff000
                Prcb: ffdff120
                Irql: 00000000
                 IRR: 00000000
                 IDR: ffffffff
       InterruptMode: 00000000
                 IDT: 8003f400
                 GDT: 8003f000
                 TSS: 80042000

       CurrentThread: 80551d20
          NextThread: 89b512e8
          IdleThread: 80551d20

           DpcQueue:  0x80552380 0x804ff5b8 [Normal] nt!KiTimerExpiration
                      0x8abcfed4 0xba7843e8 [Normal] ACPI!ACPIInterruptServiceRoutineDPC
                      0x8a6560cc 0xba50fdf0 [Normal] NDIS!ndisMDpcX
                      0x8a899eec 0xbaa28650 [Normal] i8042prt!I8042KeyboardIsrDpc
                      0x8a899e90 0xbaa2b0ef [Normal] i8042prt!I8xKeyboardSysButtonEventDpc
                      0x8a899dd0 0xbaa2a163 [Normal] i8042prt!I8042ErrorLogDpc
                      0xb87ffa4c 0xb87e8020 [Normal] SynTP
                      0xb87ffa24 0xb87e7f60 [Normal] SynTP

顯然,DPC(Deferred Procedure Call )隊列中有不少任務等待執行,這是系統掛死的一個典型癥狀。DPC是Windows系統中一種極其重要的機制,DPC任務在DISPATCH_LEVEL執行,高于用來執行普通線程的PASSIVE_LEVEL。這意味著,如果一個CPU的DPC隊列中有任務,那么這個CPU就不會去執行普通的線程。如果長時間保持這種狀態,那么系統便會表現出掛死的癥狀——界面沒有刷新,窗口不聽使喚。

仔細觀察上面顯示的DPC隊列,第一列是DPC對象的地址,也就是KDPC結構的地址,第二列是這個DPC的對應函數,帶方括號的第三列是DPC的狀態,第四列是DPC函數的符號表示。

可以使用dt命令來顯示DPC結構:

kd> dt _KDPC 0x80552380
nt!_KDPC
   +0x000 Type             : 19
   +0x002 Number           : 0 ''
   +0x003 Importance       : 0x1 ''
   +0x004 DpcListEntry     : _LIST_ENTRY [ 0x8abcfed8 - 0xffdff980 ]
   +0x00c DeferredRoutine  : 0x804ff5b8     void  nt!KiTimerExpiration+0
   +0x010 DeferredContext  : (null)
   +0x014 SystemArgument1  : 0x007af640
   +0x018 SystemArgument2  : (null)
   +0x01c Lock             : 0xffdff9c0  -> 0

有這么多DPC任務,那么當前CPU按理說要么應該在執行某個DPC,要么應該在執行更高優先級的中斷任務。不妨觀察一下棧回溯:

kd> kvn
 # ChildEBP RetAddr  Args to Child             
00 80548f98 baa2a7fa 000000e2 00000000 00000000 nt!KeBugCheckEx+0x1b (FPO: [5,0,0])
01 80548fb4 baa2a032 00899d40 01ffffc6 00000000 i8042prt!I8xProcessCrashDump+0x237 (FPO: [3,0,0])
02 80548ffc 80540add 89d6cd98 8a899c88 00010009 i8042prt!I8042KeyboardInterruptService+0x21c (FPO: [Non-Fpo])
03 80548ffc 806d0d50 89d6cd98 8a899c88 00010009 nt!KiInterruptDispatch+0x3d (FPO: [0,2] TrapFrame @ 80549020)
04 805490a4 bac3d183 00000046 8a84c028 882e97c8 hal!HalpPmTimerStallExecProc+0x60 (FPO: [1,5,0])
05 805490c4 b8cb8b50 bafde064 882e981c b8cb8ad6 usbehci!EHCI_RH_PortResetComplete+0x61 (FPO: [2,2,4])
06 805490e4 804ff550 882e97f8 4d547961 33b06716 USBPORT!USBPORT_AsyncTimerDpc+0x7a (FPO: [4,1,4])
07 80549200 804ff667 80551f80 80551d20 ffdff000 nt!KiTimerListExpire+0x122 (FPO: [0,62,0])
08 8054922c 8054111d 80552380 00000000 007af63f nt!KiTimerExpiration+0xaf (FPO: [4,6,0])
09 80549250 80541096 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46 (FPO: [0,0,0])
0a 80549254 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26 (FPO: [0,0,0])

從#06棧幀的USBPORT!USBPORT_AsyncTimerDpc函數來看,當前CPU果真在勤懇的清理DPC任務。

#04棧幀中的HalpPmTimerStallExecProc函數是所謂的忙等待(Busy Wait)函數,它會讓CPU在此循環一定時間,這通常是不得以而為之的做法,因為調用這樣的函數會讓CPU白白空轉。從#05棧幀的函數名來看,這個函數是負責管理USB 2.0設備的EHCI的Root Hub用來重置USB端口的,通常的做法是寫某個硬件寄存器,然后讀取同一個或者不同的寄存器,等待硬件的確認。這種等待通常時間很短。但是從本例的情況來看,可能出意外了,等了很久,很可能是反復讀,然后等待,在反復等的過程中,系統收到了強制藍屏的熱鍵。

分析到這里,我們知道這次系統掛死是與USB驅動程序設置的DPC任務有關的。這個DPC任務在與硬件通信來重置端口時,遇到了意外,于是進入怪圈,不斷循環,鍥而不舍,孜孜不倦,沒完沒了......【呵呵,漢語的詞匯就是豐富】

那么這個DPC任務到底遇到什么意外了呢? 要想繼續追究就要看代碼了,源代碼不公開,就考驗看匯編的能力了。好在偉大的x86匯編有著超強的表達力,很容易理解,下一篇文章中我們將繼續分析。^_^

 

BTW. 對于DPC,理解的再深刻也不過分,有空時可以反復讀一下Mark Russinovich的介紹:

http://technet.microsoft.com/en-us/sysinternals/bb963898.aspx