<2017年8月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

文章分类

导航

订阅

戏说IRQL(2)

做软件是多么好的一个工作啊,但有时,做软件又是多么差的一个工作啊!哈哈,脑海中突然冒出这么两句,权作这个IRQL系列的第二集的开篇吧!

书接上回,继续讲上次的试验。请出WinDBG,附加到目标系统,开始内核调试,kn:

kd> knL
 # ChildEBP RetAddr 
00 8af1fa50 83093bfb nt!RtlpBreakWithStatusInstruction
01 8af1fa58 83093bcd nt!KdCheckForDebugBreak+0x22
02 8af1fa88 83093a5b nt!KeUpdateRunTime+0x164
03 8af1fae0 830983e3 nt!KeUpdateSystemTime+0x613
04 8af1fae0 9574b43c nt!KeUpdateSystemTimeAssist+0x13
05 8af1fb6c 9574baf0 RealBug!RoamAtIRQL+0x1c
06 8af1fb90 9574bcf0 RealBug!RealBugDeviceControl+0xc0
07 8af1fbdc 8335e6c3 RealBug!RealBugDispatch+0x90
08 8af1fc00 83069efb nt!IovCallDriver+0x258
09 8af1fc14 8323f2e7 nt!IofCallDriver+0x1b
0a 8af1fc34 832416da nt!IopSynchronousServiceTail+0x1f8
0b 8af1fcd0 83248727 nt!IopXxxControlFile+0x6aa
0c 8af1fd04 8307079a nt!NtDeviceIoControlFile+0x2a
0d 8af1fd04 775c64f4 nt!KiFastCallEntry+0x12a

栈帧4和5之间明显有中断的痕迹,其实就是著名的时钟中断,#5就是我们上期提到的RoamAtIRQLRoamAtIRQL函数,在中断发生前,CPU在那里转圈,但尽管是在26这样的高IRQL转圈,因为时钟中断具有更高的IRQL(28),所以还是将RoamAtIRQLRoamAtIRQL函数打断了,令CPU跳出循环去执行时钟中断。

执行!pcr命令观察CPU的控制区:

kd> !pcr
KPCR for Processor 0 at 83140c00:
    Major 1 Minor 1
 NtTib.ExceptionList: 8aec130c
     NtTib.StackBase: 00000000
    NtTib.StackLimit: 00000000
  NtTib.SubSystemTib: 801c8000
       NtTib.Version: 0001ca27
   NtTib.UserPointer: 00000001
       NtTib.SelfTib: 7ffdf000

             SelfPcr: 83140c00
                Prcb: 83140d20
                Irql: 0000001f
                 IRR: 00000004
                 IDR: ffff2070
       InterruptMode: 00000000
                 IDT: 80b95400
                 GDT: 80b95000
                 TSS: 801c8000

       CurrentThread: 910a3030
          NextThread: 00000000
          IdleThread: 8314a240

           DpcQueue:

注意其中的IRQL字段,这就是记录在CPU控制区中的IRQL值,也是常常把IRQL说成是CPU属性的一个原因。

                Irql: 0000001f       

但是细心的读者可能立刻生出一个疑问,为什么是1f(31),而不是26呢?

原因是,现在已经停在调试器了,在中断到调试器时,内核调试引擎会提升IRQL到HIGH_LEVEL(X86上也就是31),最高级别。因为这个原因,在软件调试器中观察时,CPU控制区中记录的IRQL永远是31。为了解决这个问题,可以观察调试引擎提升IRQL之前的本来IRQL,从Server 2003开始,会故意将老的IRQL值保存在PCR的DebuggerSavedIRQL字段中,并可以通过!irql这个扩展命令将其读出来:

执行!irql命令:

kd> !irql
Debugger saved IRQL for processor 0x0 -- 26

直接观察,也可以看到:

kd> dd 83140d20+4c4
831411e4  0000001a

那么为什么CPU在IRQL 26兜圈时,系统就表现出挂死的症状呢?原因是鼠标键盘中断对应的IRQL都是属于设备IRQL范围,都是低于26的,这意味着鼠标键盘中断都被屏蔽了。另一个重要的原因是普通线程的IRQL是0,因此绘制窗口这样的代码根本没机会执行。

IRQL 0有很多个别名,其中之一叫PASSIVE_LEVEL,被动级别,何谓被动级别,意思是它只能被动等待被执行,从来没有机会去主动抢夺CPU的执行权。IRQL 0的另一个常见别名叫LOW_LEVEL,与最高级别的HIGH_LEVEL相对应。

是时候把所有的IRQL定义请出来了,在WDK的头文件中就可以找到它们:

// wdm.h

#define PASSIVE_LEVEL 0             // Passive release level
#define LOW_LEVEL 0                 // Lowest interrupt level
#define APC_LEVEL 1                 // APC interrupt level
#define DISPATCH_LEVEL 2            // Dispatcher level
#define CMCI_LEVEL 5                // CMCI handler level

#define PROFILE_LEVEL 27            // timer used for profiling.
#define CLOCK1_LEVEL 28             // Interval clock 1 level - Not used on x86
#define CLOCK2_LEVEL 28             // Interval clock 2 level
#define IPI_LEVEL 29                // Interprocessor interrupt level
#define POWER_LEVEL 30              // Power failure level
#define HIGH_LEVEL 31               // Highest interrupt level

#define CLOCK_LEVEL                 (CLOCK2_LEVEL)

看这个列表,通过前面的实验,我们对HIGH_LEVEL、CLOCK_LEVEL和一般分给硬件设备中断的IRQL 26已经有所认识了。 对于前几个可能还有一些疑问,尤其是2和1这两个级别,它们被统称为软件中断级别,我们下一次继续讲。

 

posted on 2013年9月14日 21:34 由 Raymond

Powered by Community Server Powered by CnForums.Net