<2022年5月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

文章分类

导航

订阅

调试笔记:系统挂在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

 

 

posted on 2009年8月10日 22:47 由 Raymond

# re: 调试笔记:系统挂在DPC(上) @ 2009年8月12日 16:47

期待下一篇~~~;)

nightxie

# 系统挂在DPC(上) @ 2009年8月13日 15:13

dear Raymond:看过你的boot系列的文章后感觉启发很多,有一个小问题想问一下,就是在第4篇调试操作系统加载阶段的故障那篇文章里,在bootMgr.exe接管控制权但没有开启分页机制时,您有一段用winbdg查看cr0的操作,但是那时系统的调试内核还没有加载呢,您是怎样断到windbg中的,还有就是windbg最早能断到系统启动的哪个阶段里?谢谢!

yicaolove

# re: 调试笔记:系统挂在DPC(上) @ 2009年8月13日 19:15

ls的问题可以分两个方面回答。
一种是Vista之前的系统,系统用NTLDR来引导。如果有一份check版的NTLDR,那就可以在内核加载前进行调试。如果没用check版的NTLDR。貌似就只能断在内核的以下代码处。
//
// If break after symbol load is specified, then break into the
// debugger.
//

if (KdBreakAfterSymbolLoad != FALSE) {
DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C);
}


//
// Turn on the headless terminal now, if we are of a sufficiently
// new vintage of loader
//
if (LoaderBlock->Extension->Size >= sizeof (LOADER_PARAMETER_EXTENSION)) {
HeadlessInit(LoaderBlock);
}

当然,不过还可以用bochs调试就是了。

另一方面,如果在Vista这样的系统上,断在内核启动之前就相对简单了。
使用bcdedit /set {bootmgr} bootdebug on这样的命令就行了。这样就可以断在内核加载之前了。

nightxie

# re: 调试笔记:系统挂在DPC(上) @ 2009年8月14日 9:19

哦,明白了,谢谢nightxie

yicaolove

Powered by Community Server Powered by CnForums.Net