之前用的驱动例子是一个功能型驱动,只不过它操作的是一个虚拟设备。这个驱动创建的FDO(功能设备对象)是附在虚拟总线驱动创建的虚拟PDO之上的。这次来介绍一下不同于功能型驱动的过滤驱动。过滤驱动可以在功能型驱动的上面,称之为上层过滤驱动,或者高层,反正就这个意思。过滤驱动在功能型驱动下面,称之为下层过滤驱动。看示意图:
从名字上就可以知道过滤驱动是干啥用的。就是起过滤作用。比如:
1. 可以对写入硬盘的数据做加密,然后读取的时候解密,这样对于用户来说,根本不知道有加密解密的过程,然后存在硬盘上的数据是加密的。
2. 可以对已有驱动做一些扩展,或者改变已有驱动的功能。比如已有驱动一次只能写1024字节的数据,那么过滤驱动可以扩展到任何长度,然后分段调用已有驱动就是了。
我们来尝试给之前用的驱动加个过滤驱动。这个过滤驱动代码,我是从前面的驱动copy过来的,然后修改一下。
其实过滤驱动跟功能型驱动从代码上看也没有太多不同,需要注意的是:
1. 因为过滤驱动是附在FDO上的,那么通常过滤驱动都不需要一个名字,因为caller不会直接调用过滤驱动;
2. 因为不知道下层驱动是缓冲模式还是直接模式操作内存,那么通常过滤驱动会同时支持缓冲和直接方式。
看一下AddDevice里面创建过滤驱动设备对象的代码:
NTSTATUS CreateFDO(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo) { NTSTATUS status; PDEVICE_OBJECT fido; //创建FDO(Function Device Object) status = IoCreateDevice( DriverObject, sizeof(DEVICE_EXTENSION), NULL,//过滤驱动,无需名字 FILE_DEVICE_UNKNOWN, 0, FALSE, &fido); if( !NT_SUCCESS(status)) return status; KdPrint(("Create filter device\n")); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fido->DeviceExtension; pdx->fdo = fido; //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。 pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fido, pdo); if (pdx->NextStackDevice == NULL) { KdPrint(("attach failed\n")); IoDeleteDevice(fido); return STATUS_DEVICE_REMOVED; } fido->Flags |= DO_BUFFERED_IO | DO_DIRECT_IO| DO_POWER_PAGABLE;// fido->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保证设备初始化完毕,必须的。 return STATUS_SUCCESS; }
看上去代码很简单,这就够了。主要就是创建一个设备,然后附在传进来的pdo之上。
我们在过滤驱动里面只做一件事情,把传进来的buffer的第一个字节改成x。
看code:
NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { KdPrint(("EX Enter HelloWDMIOControl\n")); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); //得到IOCTRL码 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; NTSTATUS status; ULONG info = 0; switch (code) { case IOCTL_ENCODE: { PDEVICE_OBJECT pFdo = fdo->DriverObject->DeviceObject; do { pdx = (PDEVICE_EXTENSION)pFdo->DeviceExtension; KdPrint(("EX Device: %x, PDX::NextStackDevice: %x\n", pFdo, pdx->NextStackDevice)); pFdo = pFdo->NextDevice; } while (pFdo != NULL); //过滤驱动里面修改一个输入缓冲的数据。 char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer; inBuf[0] = 'x'; KdPrint(("Try to call lower driver Irp: %x\n", Irp)); pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension; IoCopyCurrentIrpStackLocationToNext(Irp); status = IoCallDriver(pdx->NextStackDevice, Irp); KdPrint(("Finished calling lower driver, Irp: %x", Irp)); } break; default: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(pdx->NextStackDevice, Irp); break; } KdPrint(("EX Leave HelloWDMIOControl\n")); return status; }
如果是IOCTL_ENCODE的irp,那么把input buffer的第一个字节改成x,然后继续调用下层驱动。如果是其他irp直接调用下层驱动。
代码就这么简单,现在来看看inf文件:
[Version] Signature=$CHICAGO$ Provider=%MFGNAME% [DestinationDirs] DefaultDestDir=10,system32\drivers FiltJectCopyFiles=11 [SourceDisksFiles] HelloWDM_Ex.sys=1 [SourceDisksNames] 1=%INSTDISK%,,,MyFilter_Check ;------------------------------------------------------------------------------ ; Windows 2000 Sections ;------------------------------------------------------------------------------ [DefaultInstall.ntx86] CopyFiles=DriverCopyFiles,FiltJectCopyFiles [DriverCopyFiles] HelloWDM_Ex.sys,,,0x60 ; replace newer, suppress dialog [DefaultInstall.ntx86.services] AddService=HelloWDM_Filter,,FilterService [FilterService] ServiceType=1 StartType=3 ErrorControl=1 ServiceBinary=%10%\system32\drivers\HelloWDM_Ex.sys ;------------------------------------------------------------------------------ ; String Definitions ;------------------------------------------------------------------------------ [Strings] MFGNAME="Kevin filter" INSTDISK="Kevin disk" DESCRIPTION="Kevin - Sample Filter Driver"
比功能型驱动简单很多,没什么花头,就是要注意一下AddService=HelloWDM_Filter,,FilterService,这里的HelloWDM_Filter后面会被用到。
这个inf文件,可以直接右键点击,然后安装。
安装这个还不够,我们还需要改一下注册表,来指明过滤驱动应该对哪些设备起作用。之前那个驱动的ID是ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0},那么我们可以在注册表里面找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{EF2962F0-0D55-4BFF-B8AA-2221EE8A79B0},给它加个子键,比如:
UpperFilters指上层过滤驱动(反之,下层驱动是LowerFilters),HelloWDM_Filter来自于inf文件的AddService=HelloWDM_Filter,,FilterService
一旦我们创建的这个子键后,我们可以从设备管理器上看到:
重启电脑,用devicetree可以看到:
因为功能驱动HelloWDM创建了3个设备对象,那么过滤驱动将会附在再上层的FDO上面。从上图可以看到附在MyWDMDevice3的上面。这个时候设备栈类似于:
filter device -> MyWDMDevice3 -> MyWDMDevice2 -> MyWDMDevice -> PDO(虚拟总线驱动创建的)
ok,测试一下。测试代码很简单,就是打开设备\\.\HelloWDM(MyWDMDevice),然后发一个IOCTL_ENCODE请求(DeviceIoControl)。
// TestWDMDriver.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <process.h> #define DEVICE_NAME L"\\\\.\\HelloWDM" #define DEVICE_NAME2 L"\\\\.\\HelloWDM2" #define DEVICE_EX L"\\\\.\\HelloWDM_EX" void Test(void* pParam) { int index = (int)pParam; //设置overlapped标志,表示异步打开 HANDLE hDevice; hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL); wprintf(L"CreateFile, name: %s, ret: %x\n", DEVICE_NAME, hDevice); if (hDevice != INVALID_HANDLE_VALUE) { char inbuf[100] = {0}; sprintf(inbuf, "hello world %d", index); char outbuf[100] = {0}; DWORD dwBytes = 0; DWORD dwStart = GetTickCount(); printf("input buffer: %s\n", inbuf); OVERLAPPED ol = {0}; ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS), inbuf, strlen(inbuf), outbuf, 100, &dwBytes, &ol); printf("DeviceIoControl thread %d, returns %d, last error: %d, used: %d ms, input: %s\n", index, b, GetLastError(), GetTickCount() - dwStart, inbuf); WaitForSingleObject(ol.hEvent, INFINITE); DWORD dwEnd = GetTickCount(); //将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。 for (int i = 0; i < strlen(inbuf); i++) { outbuf[i] = outbuf[i] ^ 'm'; } printf("Verify thread %d, outbuf: %s, used: %d ms\n", index, outbuf, dwEnd - dwStart); CloseHandle(hDevice); } else printf("CreateFile failed, err: %x\n", GetLastError()); } int _tmain(int argc, _TCHAR* argv[]) { HANDLE t1 = (HANDLE)_beginthread(Test, 0, (void*)0); WaitForSingleObject(t1, INFINITE); printf("Test ends\n"); return 0; }
看输出:
输进去hello world 0, 输出变成了xello world 0,说明过滤驱动起作用了。看看debugview的输出:
嘿嘿,确实有过滤驱动的输出,成功。
总结,看起来过滤驱动还是蛮简单的,当然这只是一个极其简单的例子,实际情况怕是没那么简单了。要点:
1. 代码和功能驱动没什么大分别,如果无需过滤,则直接调用下层驱动。
2. INF文件需要相应修改,看上面的例子。
3. 需要改一下注册表,找到相应的ID, 如HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{EF2962F0-0D55-4BFF-B8AA-2221EE8A79B0},然后增加子键UpperFilters或者LowerFilters,看上面的例子。(其实INF文件可以增加这个子键,在INF文件里面改下就好)
有关这个例子,如果我们设置为LowerFilters,那么过滤驱动就不会被调用到,为什么?很简单,因为这个例子的功能驱动直接把IRP完成了,也就不会往下走了。
代码:http://download.csdn.net/detail/zj510/4906910
WDK(7600) 编译驱动, VS2008编译测试例子。过滤驱动可以直接右键inf文件安装。