📄 jiurl键盘驱动 5.htm
字号:
CSHORT Size;<BR>/*004*/ LIST_ENTRY InterruptListEntry;<BR>/*00C*/
PKSERVICE_ROUTINE ServiceRoutine;<BR>/*010*/ PVOID
ServiceContext;<BR>/*014*/ KSPIN_LOCK SpinLock;<BR>/*018*/ ULONG
TickCount;<BR>/*01C*/ PKSPIN_LOCK ActualLock;<BR>/*020*/ PVOID
DispatchAddress;<BR>/*024*/ ULONG Vector;<BR>/*028*/ KIRQL
Irql;<BR>/*029*/ KIRQL SynchronizeIrql;<BR>/*02A*/ BOOLEAN
FloatingSave;<BR>/*02B*/ BOOLEAN Connected;<BR>/*02C*/ CHAR
Number;<BR>/*02D*/ UCHAR ShareVector;<BR>/*030*/ KINTERRUPT_MODE
Mode;<BR>/*034*/ ULONG ServiceCount;<BR>/*038*/ ULONG
DispatchCount;<BR>/*03C*/ ULONG DispatchCode[106];<BR>} KINTERRUPT,
*PKINTERRUPT;<BR><BR>我们从中断描述符表中看到的中断向量为 b3 的中断服务例程的入口地址fe4cf264,就是一个
KINTERRUPT 中的 DispatchCode 的地址。fe4cf264-3c 就是这个KINTERRUPT
的首地址。fe4cf264-3c+c 处就是 KINTERRUPT 中的 ServiceRoutine。我们看到 fe4cf264-3c+c 处为
fe1c1750,正是 i8042prt!I8042KeyboardInterruptService
的入口地址。<BR><BR>当键盘中断产生时,将会执行 KINTERRUPT 中的 DispatchCode,DispatchCode 会调用
nt!KiInterruptDispatch ,nt!KiInterruptDispatch 会调用 KINTERRUPT 结构中的
ServiceRoutine ,也就是 i8042prt!I8042KeyboardInterruptService。<BR><BR>7.4
按一个普通键-a<BR><BR>7.4.1 调试信息<BR><BR>i8042 isr (kb): enter<BR>i8042 isr (kb):
scanCode 0x1e<BR>i8042 isr (kb): real scan code<BR>i8042 isr (kb): MAKE
code<BR>8042: I8xWriteDataToKeyboardQueue: enter<BR>8042:
I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf710, DataOut
0xfe4cf710<BR>8042: I8xWriteDataToKeyboardQueue: InputCount 0<BR>8042:
I8xWriteDataToKeyboardQueue: new InputCount 1<BR>8042:
I8xWriteDataToKeyboardQueue: exit<BR>i8042 isr (kb): exit<BR><BR>i8042 isr
(kb): enter<BR>i8042 isr (kb): scanCode 0x9e<BR>i8042 isr (kb): real scan
code<BR>i8042 isr (kb): BREAK code<BR>8042: I8xWriteDataToKeyboardQueue:
enter<BR>8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf71c, DataOut
0xfe4cf710<BR>8042: I8xWriteDataToKeyboardQueue: InputCount 1<BR>8042:
I8xWriteDataToKeyboardQueue: new InputCount 2<BR>8042:
I8xWriteDataToKeyboardQueue: exit<BR>i8042 isr (kb): exit<BR><BR>8042:
I8042KeyboardIsrDpc: enter<BR><BR>8042: I8xDpcVariableOperation:
enter<BR>8042: Performing increment at 0xfe4f529c (current value
0xffffffff)<BR>8042: I8xDpcVariableOperation: exit with value
0x0<BR><BR>8042: I8xGetDataQueuePointer: enter<BR>8042:
I8xGetDataQueuePointer: keyboard<BR>8042: I8xGetDataQueuePointer: DataIn
0xfe4cf728, DataOut 0xfe4cf710<BR>8042: I8xGetDataQueuePointer:
exit<BR><BR>8042: I8042KeyboardIsrDpc: calling class callback<BR>8042:
I8042KeyboardIsrDpc: with Start 0xfe4cf710 and End
0xfe4cf728<BR><BR>KBDCLASS-KeyboardClassServiceCallback:
enter<BR>KBDCLASS-KeyboardClassServiceCallback: port queue length 0x18,
read length 0x78<BR>KBDCLASS-KeyboardClassServiceCallback: number of bytes
to move from port to SystemBuffer
0x18<BR>KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf710
to 0xfe385d88<BR>KBDCLASS-KeyboardClassServiceCallback: bytes remaining
after move to SystemBuffer 0x0<BR>KBDCLASS-KeyboardClassServiceCallback:
exit<BR><BR>8042: I8042KeyboardIsrDpc: Call callback consumed 2 items,
left 0<BR><BR>8042: I8xSetDataQueuePointer: enter<BR>8042:
I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf710, InputCount
2<BR>8042: I8xSetDataQueuePointer: Okay to log keyboard overflow<BR>8042:
I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf728, InputCount
0<BR>8042: I8xSetDataQueuePointer: exit<BR><BR>8042:
I8xDpcVariableOperation: enter<BR>8042: Performing decrement at 0xfe4f529c
(current value 0x0)<BR>8042: I8xDpcVariableOperation: exit with value
0xffffffff<BR><BR>8042: I8042KeyboardIsrDpc:
exit<BR><BR>KBDCLASS-KeyboardClassRead:
enter<BR>KBDCLASS-KeyboardClassStartIo:
enter<BR>KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5938, DataOut
0xfe4f5938<BR>KBDCLASS-KeyboardClassStartIo: entries in queue
0<BR>KBDCLASS-KeyboardClassStartIo: exit<BR>KBDCLASS-KeyboardClassRead:
exit<BR><BR>7.4.2 分析<BR><BR>按一个a键,按下时产生一个 Make Code,引发一个键盘中断,松开时产生一个 Break
Code,引发一个键盘中断。a键的 Scan Code Set 1 的 Make Code 为 0x1e,Break Code 为
0x9e。<BR><BR>按下时产生一个 Make Code,引发键盘中断,最终导致
i8042prt!I8042KeyboardInterruptService
被执行。i8042prt!I8042KeyboardInterruptService 中,<BR><BR>首先从0x64端口读出 i8042
的状态寄存器,确保i8042状态寄存器的 Bit0 为1,然后从0x60端口读出数据,这里是a键的Make Code 0x1e。<BR><BR>检查
deviceExtension->IsrHookCallback 是否为空,不为空就会调用
deviceExtension->IsrHookCallback。<BR>这说明了驱动 i8042prt
支持了hook,我们可以通过DeviceIoControl IOCTL_INTERNAL_I8042_HOOK_KEYBOARD 来设置这个
deviceExtension->IsrHookCallback 。<BR>我们这里
deviceExtension->IsrHookCallback 为空,没有hook。<BR><BR>判断是否是 RESEND 或者
ACKNOWLEDGE,RESEND 为 0xFE,ACKNOWLEDGE 为
0xFA。如果都不是的话,就认为是一个扫描码。我们这里就是扫描码。<BR><BR>判断
deviceExtension->CurrentScanState
中保存的状态。当从i8042读出扩展码的0xe0或者0xe1时,会设置这里,随后读入的数据,就知道是扩展码的一部分。<BR><BR>我们这里是普通状态,判断从i8042读入的数据是否为
0xe0 或者 0xe1,如果是的话,就设置 deviceExtension->CurrentScanState 为 GotE0 或者
GotE1。我们这里是 0x1e ,都不是。<BR><BR>判断从i8042读入的数据是否大于 0x7F,也就是判断是 Make Code 还是
Break Code。<BR><BR>我们这里是 0x1E 小于 0x7F,是 Make Code。<BR>对于 Make Code,仅仅是把这个
Make Code 保存在 CurrentInput 的 MakeCode 中。<BR>将
deviceExtension->CurrentScanState 设置为普通。<BR><BR>调用
I8xQueueCurrentKeyboardInput。<BR><BR>I8xQueueCurrentKeyboardInput
中,<BR><BR>调用 I8xWriteDataToKeyboardQueue 将 CurrentInput 放入 i8042prt
的输入数据队列。<BR>调用 KeInsertQueueDpc ,将 deviceExtension->KeyboardIsrDpc
排队,以待执行。deviceExtension->KeyboardIsrDpc 的 DeferredRoutine
在键盘驱动的初始化过程中已经被赋值为
i8042prt!I8042KeyboardIsrDpc。<BR><BR>按下时引发键盘中断到此结束。<BR><BR>松开时产生一个 Break
Code,引发键盘中断,最终导致 i8042prt!I8042KeyboardInterruptService
被执行。i8042prt!I8042KeyboardInterruptService 中,<BR>大致和 Make Code
的情况相同直到,<BR><BR>判断从i8042读入的数据是否大于 0x7F,也就是判断是 Make Code 还是 Break
Code。<BR><BR>我们这里是 0x9E 大于 0x7F,是 Break Code。<BR>对于 Break Code,把这个 Break
Code & 0x7F,也就是最高位清0,然后保存在 CurrentInput 的 MakeCode 中,然后设置 CurrentInput
的 Flags 为 KEY_BREAK,表明这是个 Break Code。<BR><BR>然后将 CurrentInput 放入 i8042prt
的输入数据队列。将 deviceExtension->KeyboardIsrDpc 也就是
i8042prt!I8042KeyboardIsrDpc 的那个 DPC
排队,以待执行。<BR>松开时引发键盘中断到此结束。 <BR><BR>当从中断的 IRQL 降下来时,刚才排队的
DPC(延迟过程调用)i8042prt!I8042KeyboardIsrDpc
就有机会被执行了。<BR><BR>i8042prt!I8042KeyboardIsrDpc
中,以i8042prt的设备扩展中的使用i8042prt的输入数据队列的那些指针为参数,调用保存在
deviceExtension->ConnectData.ClassService 中的回调例程
kbdclass!KeyboardClassServiceCallback。键盘驱动初始化过程中,kbdclass 告诉 i8042prt ,它使用
kbdclass!KeyboardClassServiceCallback 处理输入数据,于是 i8042prt把这个函数的入口地址保存在了
deviceExtension->ConnectData.ClassService
中。kbdclass!KeyboardClassServiceCallback 会从 i8042prt的输入数据队列中取走数据。从
kbdclass!KeyboardClassServiceCallback 返回之后,根据取走数据的情况,设置i8042prt的设备扩展中的
DataOut 和
InputCount。整个按a键的驱动处理过程结束。<BR><BR>kbdclass!KeyboardClassServiceCallback
中,<BR>判断kbdclass的 deviceExtension->RequestIsPending ,如果为TRUE,说明已经有
IRP_MJ_READ 的 IRP 在等待数据,就满足这个 IRP。绝大多数情况都有一个 IRP 在等待数据。<BR><BR>我们这里就有一个
IRP_MJ_READ 的 IRP 在等待数据,而我们这时i8042prt的输入数据队列中有两个数据,一个a键的Make
Code,一个a键的Break Code。一个 IRP_MJ_READ 的 IRP
最多一次可以读10个数据,所以我们现在的这个情况,可以一次读完。于是调用 RtlMoveMemory 把i8042prt中的两个数据复制到
irp->AssociatedIrp.SystemBuffer 中,之后会调用 IoCompleteRequest 结束这个
IRP。<BR><BR>而对于deviceExtension->RequestIsPending ,为FALSE,说明上一个 IRP
结束后,应用层在处理中,还没有再发一个 IRP 到键盘驱动中。或者是i8042prt的输入数据队列中的数据很多,一个IRP读完之后,还有剩余。那么
i8042prt 的输入数据队列中剩余的数据将被 kbdclass!KeyboardClassServiceCallback 放入 kbdclass
的输入数据队列。<BR><BR>按a键的驱动处理过程中有不少代码是用来解决使用输入数据队列的问题的,关于输入数据队列的问题我们在后面讨论。<BR><BR>7.4.3
示意图<BR><BR>按a之前。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic1.gif" border=0>
<P>处理,按下a产生的MakeCode,的i8042prt!I8042KeyboardInterruptService结束之后。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic2.gif" border=0>
<P>处理,松开a产生的BreakCode,的i8042prt!I8042KeyboardInterruptService结束之后。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic3.gif" border=0>
<P>kbdclass!KeyboardClassServiceCallback, 中将 i8042prt
的输入数据队列中的数据复制到irp->AssociatedIrp.SystemBuffer之后。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic4.gif" border=0>
<P>kbdclass!KeyboardClassServiceCallback 中调用 IoCompleteRequest 完成这个 IRP
之后。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic5.gif" border=0>
<P>i8042prt!I8042KeyboardIsrDpc 设置 DataOut 和 InputCount 之后。
<P align=center><IMG src="JIURL键盘驱动 5.files/aPic6.gif" border=0>
<P>
<P>7.5 按一个扩展键-向上箭头<BR><BR>7.5.1 调试信息<BR><BR>i8042 isr (kb): enter<BR>i8042
isr (kb): scanCode 0xe0<BR>i8042 isr (kb): real scan code<BR>i8042 isr
(kb): change state to GotE0<BR>i8042 isr (kb): exit<BR><BR>i8042 isr (kb):
enter<BR>i8042 isr (kb): scanCode 0x48<BR>i8042 isr (kb): real scan
code<BR>i8042 isr (kb): MAKE code<BR>8042: I8xWriteDataToKeyboardQueue:
enter<BR>8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf60c, DataOut
0xfe4cf60c<BR>8042: I8xWriteDataToKeyboardQueue: InputCount 0<BR>8042:
I8xWriteDataToKeyboardQueue: new InputCount 1<BR>8042:
I8xWriteDataToKeyboardQueue: exit<BR>i8042 isr (kb): exit<BR><BR>i8042 isr
(kb): enter<BR>i8042 isr (kb): scanCode 0xe0<BR>i8042 isr (kb): real scan
code<BR>i8042 isr (kb): change state to GotE0<BR>i8042 isr (kb):
exit<BR><BR>i8042 isr (kb): enter<BR>i8042 isr (kb): scanCode
0xc8<BR>i8042 isr (kb): real scan code<BR>i8042 isr (kb): BREAK
code<BR>8042: I8xWriteDataToKeyboardQueue: enter<BR>8042:
I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf618, DataOut
0xfe4cf60c<BR>8042: I8xWriteDataToKeyboardQueue: InputCount 1<BR>8042:
I8xWriteDataToKeyboardQueue: new InputCount 2<BR>8042:
I8xWriteDataToKeyboardQueue: exit<BR>i8042 isr (kb): exit<BR><BR>8042:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -