3.9 设备对象
虽然驱动程序对象看上去是跟客户程序通信的不错候选,但实际情况并不是这样。客户程序与驱动程序实际进行通信的端点是设备对象。设备对象是半文档化的DEVICE_OBJECT
结构的实例。没有设备对象就无法进行通信。所以驱动程序至少得创建一个设备对象并给它起个名字,然后客户程序才能跟它联系。
CreateFile
函数(以及它的变体)接受的第一个参数叫作“文件名”,但事实上它应该指向设备对象的名称,而真正的文件只是一个特例而已。CreateFile
这个名字有点误导性—“文件”这个词在这里的意思其实是文件对象。打开文件或者设备的句柄会创建内核结构FILE_OBJECT
的一个实例,这个FILE_OBJECT
又是一个半文档化的结构。
更准确地说,CreateFile
接受一个符号链接。符号链接是一个内核对象,它知道如何指向另外一个内核对象。(可以用文件系统中的快捷方式与之进行类比。)所有能够用在用户模式的CreateFile
或者CreateFile2
调用中的符号链接都位于对象管理器中名为??的目录下。用Sysinternals的工具WinObj就能看到它们。图3-3显示了这个目录(在WinObj里叫作Global??)。
图3-3 WinObj中的符号链接目录
这里有些名称看起来很熟悉,比如C:、Aux、Con等。事实上,它们都是合法的“文件名”,可以被CreateFile
调用。其他有些名称看上去像又长又神秘的字符串,其实它们是基于硬件的驱动程序调用了IoRegisterDeviceInterface
API之后,I/O系统自动生成的。这种类型的符号链接对本书的目标来说没什么用。
??目录里的多数符号链接指向Device目录下的内部设备名称。用户模式调用者不能直接访问这个目录下面的名称,但是内核模式调用者可以通过IoGetDeviceObjectPointer
API访问它们。
Process Explorer的驱动程序就是个典型的例子。当用管理员权限启动Process Explorer时,它会安装一个驱动程序。相比于从用户模式API所能获得的信息,这个驱动程序给予了Process Explorer更强的能力,即使用户模式提升了权限之后也是如此。比如,Process Explorer在进程所属的线程对话框里,能够显示出该线程完整的调用栈,包括在内核模式中的函数。此类信息不可能从用户模式得到,它的驱动程序造成了信息缺失。
Process Explorer的驱动程序创建了单一的设备对象,这样Process Explorer就能打开此设备的句柄并向它发送请求。这就意味着这个设备对象必须是已命名的,而且必须在??目录中有符号链接。在目录中我们可以看到该符号链接,名称为PROCEXP152,可能表示驱动程序的版本是15.2(在写作此书时)。图3-4显示了WinObj里的这个符号链接。
图3-4 WinObj中的Process Explorer的符号链接
注意Process Explorer设备的符号链接指向\Device\PROCEXP152
,这是设备的内部名称,只能被内核调用者访问。Process Explorer(或者别的客户端程序)用符号链接调用CreateFile
时,此调用必须冠以\\.\
前缀。这个前缀是必需的,能让对象管理器的解析程序不把字符串“PROCEXP152”当作当前目录下的文件名对待。下面的代码列出了Process Explorer是怎样打开其设备的句柄的(注意是两个反斜杠,因为一个反斜杠表示转义):
驱动程序使用IoCreateDevice
函数创建设备对象。这个函数分配并初始化一个设备对象结构,并将其指针返回给调用者。设备对象的实例保存在DRIVER_OBJECT
结构的DeviceObject
字段中。如果创建了多个设备对象,它们就组成一个单链表,用DEVICE_OBJECT
的NextDevice
字段指向下一个设备对象。注意新创建的设备对象是从头部插入这个链表的,因此第一个创建的设备对象保存在链表的最后,它的NextDevice
为NULL
。它们之间的关系如图3-5所示。
图3-5 驱动程序和设备对象