調(diào)試筆記:系統(tǒng)掛在DPC(上)

這是發(fā)生在我的筆記本電腦上的一次系統(tǒng)掛死,發(fā)生在喚醒過程中,屏幕沒有任何顯示,因?yàn)槲业碾娔X始終是啟用通過熱鍵(Ctrl+ScrollLock)來觸發(fā)藍(lán)屏的,所以可以通過熱鍵觸發(fā)藍(lán)屏和產(chǎn)生轉(zhuǎn)儲。

以下是分析轉(zhuǎn)儲文件的簡要過程,轉(zhuǎn)儲的類型是內(nèi)核轉(zhuǎn)儲。系統(tǒng)中只有一個(gè)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

操作系統(tǒng)是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

執(zhí)行!pcr命令觀察CPU的控制區(qū):

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 )隊(duì)列中有不少任務(wù)等待執(zhí)行,這是系統(tǒng)掛死的一個(gè)典型癥狀。DPC是Windows系統(tǒng)中一種極其重要的機(jī)制,DPC任務(wù)在DISPATCH_LEVEL執(zhí)行,高于用來執(zhí)行普通線程的PASSIVE_LEVEL。這意味著,如果一個(gè)CPU的DPC隊(duì)列中有任務(wù),那么這個(gè)CPU就不會去執(zhí)行普通的線程。如果長時(shí)間保持這種狀態(tài),那么系統(tǒng)便會表現(xiàn)出掛死的癥狀——界面沒有刷新,窗口不聽使喚。

仔細(xì)觀察上面顯示的DPC隊(duì)列,第一列是DPC對象的地址,也就是KDPC結(jié)構(gòu)的地址,第二列是這個(gè)DPC的對應(yīng)函數(shù),帶方括號的第三列是DPC的狀態(tài),第四列是DPC函數(shù)的符號表示。

可以使用dt命令來顯示DPC結(jié)構(gòu):

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任務(wù),那么當(dāng)前CPU按理說要么應(yīng)該在執(zhí)行某個(gè)DPC,要么應(yīng)該在執(zhí)行更高優(yōu)先級的中斷任務(wù)。不妨觀察一下棧回溯:

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函數(shù)來看,當(dāng)前CPU果真在勤懇的清理DPC任務(wù)。

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

分析到這里,我們知道這次系統(tǒng)掛死是與USB驅(qū)動程序設(shè)置的DPC任務(wù)有關(guān)的。這個(gè)DPC任務(wù)在與硬件通信來重置端口時(shí),遇到了意外,于是進(jìn)入怪圈,不斷循環(huán),鍥而不舍,孜孜不倦,沒完沒了......【呵呵,漢語的詞匯就是豐富】

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

 

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

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