dpc与线程切换

中断服务例程 延迟过程调用 线程切换 键盘信号传输

1. 背景

我一般用ctrl+alt+del能否呼出winlogon桌面作为Windows卡死(hang)还是个别程序卡死的鉴别手段。因为一则用户态的程序没办法干扰这个呼出流程,二则如果不能呼出任务管理器来终止进程或者呼出windbg等工具进行观察调试的话,其实排查的方法也跟windows卡死是一致的——触发scrolllock蓝屏。所以探究ctrl+alt+del呼出winlogon桌面的流程,就是分析这类卡死的第一步。

同样是按键后的反应,为什么在ctrl+alt+del不能呼出的场景下,scrolllock蓝屏还能够触发,也是一个获取关键知识的方向。

2. 键盘信号从按下到应用程序的窗口

大致分为中断服务例程,DPC, csrss,具体应用程序这四个阶段。

2.1 中断服务例程(ISR)

2.1.1 什么是中断服务例程

诸如按下键盘按下或处理器时钟产生的这类设备中断,Windows设置了对应函数(中断服务例程)来处理。Windows在启动的早期阶段先设置好中断号码和ISR的对应关系。当设备中断触发时,Windows先把当前线程的当前状态保存起来,然后用这个线程去执行对应的ISR。执行完毕后再从保存数据恢复那个线程。

2.1.2 查看PS2键盘信号对应的中断服务例程

直接用windbg的!idt查找对应关系

0: kd> .reload;!idt

Dumping IDT: fffff8033ea8e000

00:        fffff8033c351100 nt!KiDivideErrorFaultShadow
01:        fffff8033c351180 nt!KiDebugTrapOrFaultShadow        Stack = 0xFFFFF8033EA929D0
02:        fffff8033c351200 nt!KiNmiInterruptShadow        Stack = 0xFFFFF8033EA927D0
03:        fffff8033c351280 nt!KiBreakpointTrapShadow

……
90:        fffff8033c352700 i8042prt!I8042KeyboardInterruptService (KINTERRUPT ffffbb007e792a00)

……

上文的90对应的函数i8042prt!I8042KeyboardInterruptService就是90中断号对应的ISR。给这个函数下断点,

C
bp i8042prt!I8042KeyboardInterruptService "!thread"

只有当ps2键盘按下或松开时才会执行这个函数。

具体如何通过90和idt寄存器计算出90对应的isr地址fffff803 3c35 2700的,请看下图:

0: kd> dt nt!_kidtentry64 @idtr+(90*10)
   +0x000 OffsetLow        : 0x2700
   +0x002 Selector         : 0x10
   +0x004 IstIndex         : 0y000
   +0x004 Reserved0        : 0y00000 (0)
   +0x004 Type             : 0y01110 (0xe)
   +0x004 Dpl              : 0y00
   +0x004 Present          : 0y1
   +0x006 OffsetMiddle     : 0x3c35
   +0x008 OffsetHigh       : 0xfffff803
   +0x00c Reserved1        : 0
   +0x000 Alignment        : 0x3c358e00`00102700

bp i8042prt!I8042KeyboardInterruptService"k";g

断点触发时,可以看到此时是idel线程fffff8033c591400调用了这个isr。

C
THREAD fffff8033c591400  Cid 0000.0000  Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 0
Not impersonating
DeviceMap                 ffff9388274154e0
Owning Process            fffff8033c58e9c0       Image:         Idle
……
Call Site
i8042prt!I8042KeyboardInterruptService
nt!KiCallInterruptServiceRoutine+0xa5
nt!KiInterruptSubDispatch+0x11f
nt!KiInterruptDispatch+0x37
hal!HalProcessorIdle+0xf
nt!PpmIdleDefaultExecute+0x1b
nt!PpmIdleExecuteTransition+0x70c
nt!PoIdle+0x36e
nt!KiIdleLoop+0x48

这个isr内部会创建一个延迟过程调用(dpc),然后迅速结束isr。为的是确保响应硬件中断的接手工作尽可能快完结,而具体的事项留给其它时机处理这个dpc的时候来做。

2.2 延迟过程调用(DPC)

2.2.1 什么是延迟过程调用(DPC)

类似于一种延迟的任务。先尽快登记上这个任务——把任务插入排队的队列中。之后其它空闲的线程再扫描队列时候发现有任务未处理,那就会来处理它。dpc的有如下两个特点能满足操作系统的要求:

一般对于硬件中断的处理这类需求,需要尽快完成,则先用KeInsertQueueDpc函数登记上dpc,然后完成接手工作。dpc里面登记了个函数地址DeferredRoutine,等其它空闲线程发现有dpc要处理时,则执行此函数。

Windows抽象出中断请求级别(IRQL)的概念,在处理高级别的IRQL时,小于等于它的中断请求就不会处理了。大部分代码运行在被动级别上,线程切换流程里的某个阶段和dpc的DeferredRoutine处理是运行在dpc级别上,硬件中断都更高。dpc的第二个特点就是DeferredRoutine的IRQL又比一般的代码高,所以能尽快不被打扰地处理完毕。

dpc与线程切换_第1张图片

2.2.2 查看PS2键盘ISR对应的DPC

接2.1.2,在该线程给函数nt!KeInsertQueueDpc下断点,这个函数的参数一就是DPC变量的结构体nt!_KDPC

C
bp /t  fffff8033c591400 nt!KeInsertQueueDpc "dt nt!_KDPC @rcx"

断点触发时,能看到DPC结构体里的数据,尤其是DefferedRoutine:

C
+0x000 TargetInfoAsUlong : 0x113
   +0x000 Type             : 0x13 ''
   +0x001 Importance       : 0x1 ''
   +0x002 Number           : 0
   +0x008 DpcListEntry     : _SINGLE_LIST_ENTRY
   +0x010 ProcessorHistory : 1
   +0x018 DeferredRoutine  : 0xfffff8033fe45fb0     void  i8042prt!I8042KeyboardIsrDpc+0
   +0x020 DeferredContext  : 0xffffd0036c0d1040 Void
   +0x028 SystemArgument1  : (null)
   +0x030 SystemArgument2  : (null)
   +0x038 DpcData          : (null)

再给这个DeferredRoutine下断点:

C
bd 0,1;bp I8042KeyboardIsrDpc "!thread"

断点触发时,内核调用KiRetireDpcList->KiExecuteAllDpcs->DeferredRoutine:

C
THREAD fffff8033c591400  Cid 0000.0000  Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 0
Not impersonating
DeviceMap                 ffff9388274154e0
Owning Process            fffff8033c58e9c0       Image:         Idle
……
Call Site
i8042prt!I8042KeyboardIsrDpc
nt!KiExecuteAllDpcs+0x305
nt!KiRetireDpcList+0x1ef
nt!KiIdleLoop+0x84

2.3 DeferredRoutine调用kbdclass.sys进一步处理

执行i8042prt!I8042KeyboardIsrDpc函数,该函数调用i8042prt!I8xGetDataQueuePointer获取键盘端口驱动保存在设备扩展的按键信息队列指针,调用kbdclass!KeyboardClassServiceCallback完成按键信息的交付。然后调用i8042prt!I8xSetDataQueuePointer更新设备扩展的按键信息队列。kbdclass!KeyboardClassServiceCallback类驱动函数处理过程。将键盘信息从端口驱动的键盘信息队列中复制到类驱动的队列中。wdk的例子中有该函数源代码。[2]

将上述函数下断点,可以看到他们依此触发了:

C
bd 2;
bp /t fffff8033c591400 i8042prt!I8xGetDataQueuePointer "k";
bp /t fffff8033c591400 kbdclass!KeyboardClassServiceCallback "k";
bp /t fffff8033c591400 i8042prt!I8xSetDataQueuePointer "k"

C
i8042prt!I8xGetDataQueuePointer
nt!KeSynchronizeExecution+0x48
i8042prt!I8042KeyboardIsrDpc+0x109
nt!KiExecuteAllDpcs+0x305
nt!KiRetireDpcList+0x1ef
nt!KiIdleLoop+0x84
……
kbdclass!KeyboardClassServiceCallback
i8042prt!I8042KeyboardIsrDpc+0x2f8
nt!KiExecuteAllDpcs+0x305
nt!KiRetireDpcList+0x1ef
nt!KiIdleLoop+0x84
……
i8042prt!I8xSetDataQueuePointer
nt!KeSynchronizeExecution+0x48
i8042prt!I8042KeyboardIsrDpc+0x397
nt!KiExecuteAllDpcs+0x305
nt!KiRetireDpcList+0x1ef
nt!KiIdleLoop+0x84

2.3 csrss

csrss.exe进程一般有两个,0号session的和1号session的

C
0: kd> !process 0 0 csrss.exe
PROCESS ffffca057479e080
    SessionId: 0  Cid: 019c    Peb: 162aa55000  ParentCid: 0194
    DirBase: 01fba002  ObjectTable: ffffdf0200b584c0  HandleCount: 537.
    Image: csrss.exe

PROCESS ffffca0574b59080
    SessionId: 1  Cid: 01f4    Peb: 8216b90000  ParentCid: 01e0
    DirBase: 131bc7002  ObjectTable: ffffdf0200bfce40  HandleCount: 352.
    Image: csrss.exe

这两个进程里都各有一个线程调用win32kfull!RawInputThread,姑且称这两个线程为RawInputThread

C
THREAD ffffca0574b6a080
Owning Process            ffffca057479e080       Image:         csrss.exe
Call Site
nt!KiSwapContext+0x76
nt!KiSwapThread+0xbfd
nt!KiCommitThreadWait+0x144
nt!KeWaitForMultipleObjects+0x287
win32kbase!LegacyInputDispatcher::WaitAndDispatch+0x8b
win32kfull!RawInputThread+0x95e
win32kbase!xxxCreateSystemThreads+0xa3
win32kfull!NtUserCallNoParam+0x6f
nt!KiSystemServiceCopyEnd+0x28 (TrapFrame @ fffff20cb6217a80)
0x00007ffca6f81144
……
THREAD ffffca0574b8c080
Owning Process            ffffca0574b59080       Image:         csrss.exe
Call Site
nt!KiSwapContext+0x76
nt!KiSwapThread+0xbfd
nt!KiCommitThreadWait+0x144
nt!KeWaitForMultipleObjects+0x287
win32kbase!LegacyInputDispatcher::WaitAndDispatch+0x8b
win32kfull!RawInputThread+0x95e
win32kbase!xxxCreateSystemThreads+0xa3
win32kfull!NtUserCallNoParam+0x6f
nt!KiSystemServiceCopyEnd+0x28 (TrapFrame @ fffff20c`b6407a80)
0x00007ffc`a6f81144

3. 为什么hang witch dpc时,整个操作系统都挂起(卡死)了

因为线程切换的代码也是在dpc级别执行的。所以此时没有机会执行线程切换的代码。所以windows里大部分线程都无法工作。

你可能感兴趣的:(windows,键盘)