Windows驱动开发WDM (13)- 过滤驱动

之前用的驱动例子是一个功能型驱动,只不过它操作的是一个虚拟设备。这个驱动创建的FDO(功能设备对象)是附在虚拟总线驱动创建的虚拟PDO之上的。这次来介绍一下不同于功能型驱动的过滤驱动。过滤驱动可以在功能型驱动的上面,称之为上层过滤驱动,或者高层,反正就这个意思。过滤驱动在功能型驱动下面,称之为下层过滤驱动。看示意图:

Windows驱动开发WDM (13)- 过滤驱动_第1张图片

从名字上就可以知道过滤驱动是干啥用的。就是起过滤作用。比如:

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},给它加个子键,比如:

Windows驱动开发WDM (13)- 过滤驱动_第2张图片
UpperFilters指上层过滤驱动(反之,下层驱动是LowerFilters),HelloWDM_Filter来自于inf文件的AddService=HelloWDM_Filter,,FilterService

一旦我们创建的这个子键后,我们可以从设备管理器上看到:

Windows驱动开发WDM (13)- 过滤驱动_第3张图片

重启电脑,用devicetree可以看到:

Windows驱动开发WDM (13)- 过滤驱动_第4张图片

因为功能驱动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;
}

看输出:

Windows驱动开发WDM (13)- 过滤驱动_第5张图片
输进去hello world 0, 输出变成了xello world 0,说明过滤驱动起作用了。看看debugview的输出:

Windows驱动开发WDM (13)- 过滤驱动_第6张图片

嘿嘿,确实有过滤驱动的输出,成功。

 

总结,看起来过滤驱动还是蛮简单的,当然这只是一个极其简单的例子,实际情况怕是没那么简单了。要点:

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文件安装。

 

你可能感兴趣的:(过滤,驱动,WDM)