📄 csdn技术中心 jiurl键盘驱动 2.htm
字号:
PUNICODE_STRING ObjectName 。然后调用 ZwCreateFile。ZwCreateFile
完成得到句柄的工作,最后通过传入的参数返回得到的句柄。win32k!RawInputThread
把得到的句柄保存起来,供后面的 ReadFile,
DeviceIoControl等使用。<BR><BR>ZwCreateFile 通过系统服务,调用内核中的
NtCreateFile。NtCreateFile 执行到 nt!IopParseDevice 中 ,<BR>调用
nt!IoGetAttachedDevice ,通过 PDO
的设备对象获得键盘设备栈最顶端的设备对象。用得到的这个设备对象的 +30 char StackSize 作为参数来
IoAllocateIrp,创建 IRP。调用 nt!ObCreateObject 创建文件对象。初始化这个文件对象,+04
struct _DEVICE_OBJECT *DeviceObject 赋值为键盘设备栈的 PDO。调用
nt!IopfCallDriver,将 IRP 发往驱动,让驱动进行相应的处理。之后一系列返回,回到
nt!ObOpenObjectByName。在 nt!ObOpenObjectByName 中继续执行,调用
nt!ObpCreateHandle
在进程(csrss.exe)的句柄表中创建一个新的句柄,这个句柄对应的对象是刚才创建初始化的那个文件对象,文件对象中的
DeviceObject 指向键盘设备栈的 PDO。在 nt!ObpCreateHandle 前后,我们使用命令
!handle 0 3 a0 (a0 为此时 csrss.exe进程的进程id),观察 csrss.exe进程
句柄表的前后变化,看到了多出来的那一个文件对象。<BR><BR>3.3 win32k!RawInputThread
如何从键盘驱动得到按键的数据<BR><BR> win32k!RawInputThread
在获得了句柄之后,会以这个句柄为参数,调用 nt!ZwReadFile,向键盘驱动要求读入数据。nt!ZwReadFile
中会创建一个 IRP_MJ_READ 的 IRP 发给键盘驱动,告诉键盘驱动要求读入数据。键盘驱动通常会使这个 IRP
Pending (通常情况下是这样,详细的情况我们在键盘驱动部分讨论)。也就是说这个 IRP_MJ_READ
不会被满足,它会一直被放在那里,等待着来自键盘的数据。而发出这个读请求的线程 win32k!RawInputThread
也会等待,等待着这个读操作的完成。<BR><BR> 命中注定,这个 IRP
匆匆的出现,然后用它一生中绝大部分时间,开始一个静静的等待,而当它等到的时候,它就会匆匆的消失。它的一生或许很短,或许很长,取决于它所等待着的出现。它在平静的等待着什么呢?<BR><BR>
它在等待着你,按下键盘上的键。我们来说明一下键盘数据的源头,键盘数据的源头就是键盘,当键盘上有键被按下时,就产生了那个
IRP_MJ_READ IRP 等待着的对象。<BR><BR>
当键盘上有键被按下时,将触发键盘的那个中断,引起中断服务例程的执行,键盘中断的中断服务例程由键盘驱动提供。键盘驱动从端口读取扫描码,经过一些列的处理之后,最后把从键盘得到的数据交给
IRP,然后结束这个 IRP。<BR><BR> 这个 IRP 的结束,将导致
win32k!RawInputThread 线程对这个读操作的等待结束。win32k!RawInputThread
线程将会对得到的数据作出处理,分发给合适的进程。一旦把输入数据处理完之后,win32k!RawInputThread
线程会立刻再调用一个
nt!ZwReadFile,向键盘驱动要求读入数据。于是又开始一个等待,等待着键盘上的键被按下。<BR><BR>
简单的说,win32k!RawInputThread 线程总是 nt!ZwReadFile
要求读入数据。然后等待键盘上的键被按下。当键盘上的键被按下,win32k!RawInputThread 处理
nt!ZwReadFile 得到的数据,然后再 nt!ZwReadFile
要求读入数据,再等待键盘上的键被按下。<BR><BR>
上面所介绍的内容,是当我在看到键盘驱动对于 IRP_MJ_READ 的处理,在
kbdclass!KeyboardClassRead 中,IRP 并没有获得数据,而是被 IoMarkIrpPending
时,想了想,了解到的。<BR><BR> 我简单的跟了一下
win32k!RawInputThread
从键盘驱动获得按键数据的过程,下面我对这个过程做一个简单的介绍。<BR><BR>
首先我们看看断在 kbdclass!KeyboardClassRead 时的 call stack ,看看引起
kbdclass!KeyboardClassRead 的整个调用过程。<BR><BR># ChildEBP RetAddr
Args to Child <BR>00 f90ef8dc 8041f54b fe4f5df0 fe43e9a8
fe43e9a8 kbdclass!KeyboardClassRead(struct _DEVICE_OBJECT *
Device = 0xfe4f5df0, struct _IRP * Irp = 0xfe43e9a8) (CONV:
stdcall)<BR>01 f90ef8f0 804ba5e8 fe43eacc fe43e9a8 00000000
nt!IopfCallDriver+0x35 (FPO: [0,0,2])<BR>02 f90ef904 804a2d4c
fe4f5df0 fe43e9a8 fe42d668 nt!IopSynchronousServiceTail+0x60
(FPO: [Non-Fpo])<BR>03 f90ef9d8 80461691 000000d4 00000000
a005c962 nt!NtReadFile+0x5f4 (FPO: [Non-Fpo])<BR>04 f90ef9d8
804011d5 000000d4 00000000 a005c962 nt!KiSystemService+0xc4
(FPO: [0,0] TrapFrame @ f90efa04)<BR>05 f90efa74 a005c91d
000000d4 00000000 a005c962 nt!ZwReadFile+0xb (FPO:
[9,0,0])<BR>06 f90efaa8 a005c991 e1971008 fe43e9e8 80430982
win32k!StartDeviceRead+0x8c (FPO: [1,0,3])<BR>07 f90efab4
80430982 e1971008 e1971028 00000000 win32k!InputApc+0x41 (FPO:
[3,0,1])<BR>08 f90efae8 80403a44 00000000 00000000 00000000
nt!KiDeliverApc+0xdb (FPO: [Non-Fpo])<BR>09 f90efb08 8042d33d
80400b46 00000001 00000000 nt!KiSwapThread+0xfc (FPO: [EBP
0xf90efb3c] [0,0,4])<BR>0a f90efb3c a000eaf5 00000004 fe42e5a8
00000001 nt!KeWaitForMultipleObjects+0x266 (FPO:
[Non-Fpo])<BR>0b f90efda8 804524f6 00000002 00000000 00000000
win32k!RawInputThread+0x3c2 (FPO: [Non-Fpo])<BR>0c f9dc
80465b62 a000e7cd f8d5f7d0 00000000
nt!PspSystemThreadStartup+0x69 (FPO: [Non-Fpo])<BR>0d 00000000
f000ff53 f000e2c3 f000ff53 f000ff53
nt!KiThreadStartup+0x16<BR>WARNING: Frame IP not in any known
module. Following frames may be wrong.<BR>0e f000ff53 00000000
00000000 00000000 00000000
0xf000ff53<BR><BR> 线程 win32k!RawInputThread
调用 nt!ZwReadFile
要求读入数据。<BR><BR>NTSTATUS <BR>ZwReadFile(<BR>IN HANDLE
FileHandle,<BR>IN HANDLE Event OPTIONAL,<BR>IN PIO_APC_ROUTINE
ApcRoutine OPTIONAL,<BR>IN PVOID ApcContext OPTIONAL,<BR>OUT
PIO_STATUS_BLOCK IoStatusBlock,<BR>OUT PVOID Buffer,<BR>IN
ULONG Length,<BR>IN PLARGE_INTEGER ByteOffset OPTIONAL,<BR>IN
PULONG Key OPTIONAL<BR>);<BR><BR>我们看到调用时,参数 FileHandle 正是前面
win32k!RawInputThread 用 ZwCreateFile 得到的句柄。而参数 ApcRoutine 为
win32k!InputApc 的入口地址。也就是说当 ReadFile 的 IRP 结束时,win32k!InputApc
将被调用。ZwReadFile 通过系统服务,最终调用
nt!NtReadFile。<BR><BR>nt!NtReadFile
中。作为参数传入的那个句柄,对应着一个文件对象,这个文件对象中保存着键盘设备栈中的 PDO
的设备对象的指针。以这个句柄为参数调用
ObReferenceObjectByHandle,获得句柄对应的文件对象。之后用得到的文件对象做参数调用
IoGetRelatedDeviceObject
,这将获得键盘设备栈中最顶端的设备对象的指针。用得到的键盘设备栈中最顶端的设备对象的 +30 char StackSize
作参数,调用 nt!IoAllocateIrp,构造 IRP ,然后初始化这个 IRP,用传入的参数设置这个
IRP。然后调用 IoCallDriver ,将这个 IRP 发给键盘驱动。<BR><BR>键盘驱动通常会调用
IoMarkIrpPending 使这个 IRP
Pending。通常情况下是这样,详细的情况我们在键盘驱动部分讨论。于是这个 IRP 就在那里等待。关于这个等待的 IRP
,我们可以使用 WinDbg 的 !irpfind 命令找到它。反过来这也解释了,我们使用 !irpfind
为什么总能看到一个 pending 的发给键盘设备栈栈顶的 IRP_MJ_READ 的 IRP。<BR><BR>kd>
!irpfind<BR>unable to get large pool allocation table - either
wrong symbols or pool tagging is disabled<BR><BR>Searching
NonPaged pool (fe313000 : fe52b000) for Tag: Irp?<BR><BR>Irp [
Thread ] irpStack: (Mj,Mn) DevObj [Driver]<BR>...<BR>fe439008
[fe427940] irpStack: ( 3, 0) fe4f5df0 [
\Driver\Kbdclass]<BR>...<BR><BR>这个 IRP 的地址为 fe439008
,我们看看它的详细情况<BR><BR>kd> !irp fe439008<BR>Irp is active with
6 stacks 6 is current (= 0xfe43912c)<BR>No Mdl System buffer =
fe426568 Thread fe427940: Irp stack trace. <BR>cmd flg cl
Device File Completion-Context<BR>[ 0, 0] 0 0 00000000
00000000 00000000-00000000 <BR><BR>Args: 00000000
00000000 00000000 00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000
00000000 00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000
00000000 00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000
00000000 00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000
00000000 00000000<BR>>[ 3, 0] 0 1 fe4f5df0 fe426608
00000000-00000000 pending<BR>\Driver\Kbdclass<BR>Args:
00000078 00000000 00000000 00000000<BR>看到了这个 IRP
pending。<BR><BR>这时 线程 win32k!RawInputThread 会在一个
nt!KeWaitForMultipleObjects 上等待,等待的对象之一就是,希望从键盘驱动中读数据的那个 IRP
。<BR><BR>当键盘上有键被按下时,引发中断,导致驱动从端口读取按键的扫描码。驱动经过一系列处理,最后调用
IoCompleteRequest 结束那个等待着的 IRP。<BR><BR>IRP 的结束会使得 线程
win32k!RawInputThread 在 nt!KeWaitForMultipleObjects
上对从键盘读取数据的等待的结束。这将使得前面 ZwReadFile 的传入参数 ApcRoutine 即
win32k!InputApc 被执行。<BR><BR>win32k!InputApc
中。调用两个函数,win32k!ProcessKeyboardInput,win32k!StartDeviceRead。win32k!ProcessKeyboardInput
负责处理刚才读到的输入数据,比如分发给应该得到这个键盘按键的进程。数据处理完之后,也就是
win32k!ProcessKeyboardInput 结束之后。win32k!StartDeviceRead
被调用,win32k!StartDeviceRead 会调用 nt!ZwReadFile
要求读入数据。<BR><BR>3.4 补充<BR><BR> win32k
实际是一个驱动程序,不属于应用程序,所以把 win32k!RawInputThread 叫做键盘驱动的使用层或许更合适。至于
win32k!RawInputThread 如何把得到的键盘上的按键分发给各个进程,我们不研究。曾经使我奇怪的是,为什么
ZwCreateFile 的参数,能找到的设备对象是键盘设备栈的 PDO,而 ZwCreateFile 产生的 IRP
却是发给键盘设备栈的栈顶。为什么 ZwReadFile 句柄所找到的设备对象是键盘设备栈的 PDO,而 ZwReadFile
产生的 IRP 却是发给键盘设备栈最顶端的设备对象。后来跟踪 NtCreateFile,NtReadFile
找到了原因。从中我们也可以看出,CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle
产生的 IRP 都是发给设备栈的栈顶的,然后 IRP
在设备栈上自上而下。<BR><BR><BR>欢迎交流,欢迎交朋友,<BR>欢迎访问<BR>主页 <A
href="http://jiurl.yeah.net/"
target=_blank>http://jiurl.yeah.net/</A> <A
href="http://jiurl.nease.net/"
target=_blank>http://jiurl.nease.net/</A> 论坛 <A
href="http://jiurl.cosoft.org.cn/forum"
target=_blank>http://jiurl.cosoft.org.cn/forum</A>
<P>f啊k,不带你们这样的啊,有好事不叫我。
<P>
<P> </P></TD></TR></TBODY></TABLE></DIV></SPAN><BR>
<DIV
style="FONT-SIZE: 14px; LINE-HEIGHT: 25px"><STRONG>作者Blog:</STRONG><A
id=ArticleContent1_ArticleContent1_AuthorBlogLink
href="http://blog.csdn.net/imquestion/"
target=_blank>http://blog.csdn.net/imquestion/</A></DIV>
<DIV
style="FONT-SIZE: 14px; COLOR: #900; LINE-HEIGHT: 25px"><STRONG>相关文章</STRONG></DIV>
<TABLE id=ArticleContent1_ArticleContent1_RelatedArticles
cellSpacing=0 border=0>
<TBODY>
<TR>
<TD><A
href="http://dev.csdn.net/article/25/25628.shtm">gzip原理与实现</A>
</TD></TR>
<TR>
<TD><A href="http://dev.csdn.net/article/25/25053.shtm">Gzip
Zlib PNG 压缩算法 Gzip源码详解</A> </TD></TR>
<TR>
<TD><A
href="http://dev.csdn.net/article/22/22763.shtm">JIURL键盘驱动
3</A> </TD></TR>
<TR>
<TD><A href="http://dev.csdn.net/article/22/22726.shtm">IRP
乱杂谈</A> </TD></TR>
<TR>
<TD><A
href="http://dev.csdn.net/article/22/22707.shtm">JIURL键盘驱动
2</A> </TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><A
name=#Comment></A>
<TABLE cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center
bgColor=#006699 border=0>
<TBODY>
<TR bgColor=#006699>
<TD id=white align=middle width=556 bgColor=#006699><FONT
color=#ffffff>对该文的评论</FONT> </TD></TR></TBODY></TABLE>
<DIV align=right><A id=CommnetList1_CommnetList1_Morelink
href="http://comment.csdn.net/Comment.aspx?c=2&s=22707">【评论】</A>
<A id=CommnetList1_CommnetList1_Hyperlink1
href="javascript:window.close();">【关闭】</A> <A
href="mailto:webmaster@csdn.net">【报告bug】</A>
</DIV><BR></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></FORM><!-- 版权 -->
<DIV align=center>
<SCRIPT language=JavaScript src="CSDN技术中心 JIURL键盘驱动 2.files/footer_gb.js"
type=text/javascript></SCRIPT>
</DIV><!-- /版权 -->
<SCRIPT>
document.write("<img src=http://count.csdn.net/count/pageview1.asp?columnid=4&itemid=11 border=0 width=0 height=0>");
</SCRIPT>
<SCRIPT>document.write("<img src=http://counter.csdn.net/pv.aspx?id=37 border=0 width=0 height=0>");</SCRIPT>
<SCRIPT language=JavaScript
src="CSDN技术中心 JIURL键盘驱动 2.files/common.htm"></SCRIPT>
</BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -