storage R&D guy.
全部博文(1000)
分类: 服务器与存储
2015-02-12 18:42:39
21.1.5 DeviceIoControl函数与IoControlCode
打开驱动设备后,Ring3还要和驱动进行通讯或调用驱动的派遣例程,这需要用到一个非常重要的函数:DeviceIoControl。
- BOOL DeviceIoControl(
- HANDLE hDevice, //设备句柄
- DWORD dwIoControlCode, //Io控制号
- LPVOID lpInBuffer, //输入缓冲区指针
- DWORD nInBufferSize, //输入缓冲区字节数
- LPVOID lpOutBuffer, //输出缓冲区指针
- DWORD nOutBufferSize, //输出缓冲区字节数
- LPDWORD lpBytesReturned, //返回输出字节数
- LPOVERLAPPED lpOverlapped //异步调用时指向的OVERLAPPED指针
- );
该函数共有8个参数,hDevice是要通信的设备句柄;dwIoControlCode是Io控制号;lpInBuffer是输入缓冲区指针;nInBufferSize是输入缓冲区字节数;lpOutBuffer是输出缓冲区指针;nOutBufferSize是输出缓冲区字节数;lpBytesReturned是返回输出字节数;lpOverlapped是异步调用时指向的OVERLAPPED指针。
其中的第二个参数IoControlCode尤为重要,由宏CTL_CODE构造而成:
- #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
- ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
IoControlCode由四部分组成:DeviceType、Access、Function、Method,如图21.1.11所示。
图21.1.11 IoControlCode的四个组成部分 |
DeviceType表示设备类型;
Access表示对设备的访问权限;
Function表示设备IoControl的功能号,0~0x7ff为微软保留,0x800~0xfff由程序员自己定义;
Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:
- #define METHOD_BUFFERED 0
- #define METHOD_IN_DIRECT 1
- #define METHOD_OUT_DIRECT 2
- #define METHOD_NEITHER 3
最值得关注的也就是Method,如果使用了METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。
如果使用了METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。
但是如果使用了METHOD_NEITHER方式,虽然通信的效率提高了,但是不够安全。驱动的派遣函数中可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。
21.1.5节中提到了Ring3/Ring0通信的四种内存访问方式分别为:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT和METHOD_NEITHER。
METHOD_BUFFERED可称为"缓冲方式",是指Ring3指定的输入、输出缓冲区的内存读和写都是经过系统的"缓冲",具体过程如图21.1.12所示。
这种方式下,首先系统会将Ring3下指定的输入缓冲区(UserInputBuffer)数据,按指定的输入长度(InputBufferLen)复制到Ring0中事先分配好的缓冲内存(SystemBuffer,通过pIrp->AssociatedIrp.SystemBuffer得到)中。驱动程序就可以将SystemBuffer视为输入数据进行读取,当然也可以将SystemBuffer视为输出数据的缓冲区,也就是说SystemBuffer既可以读也可以写。驱动程序处理完后,系统会按照pIrp->IoStatus->Information指定的字节数,将SystemBuffer上的数据复制到Ring3指定的输出缓冲区(UserOutputBuffer)中。可见这个过程是比较安全的,避免了驱动程序在内核态直接操作用户态内存地址的问题,这种方式是推荐使用的方式。
(点击查看大图)图21.1.12 METHOD_BUFFERED方式的内存访问 |
METHOD_NEITHER可称为"其他方式",这种方式与METHOD_BUFFERED方式正好相反。METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲,而METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,如图21.1.13所示。
图21.1.13 METHOD_NEITHER方式的内存访问 |
驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。
由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。
这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
图21.1.14 METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问 |