过滤是在不影响上层和下层的接口,在windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真实驱动程序,就加入了新的功能。
1、用到的Windows API
IoAttachDevice——绑定到真实设备上(有名称的设备)
/*
IoAttachDevice例程将调用方的设备对象附加到命名的目标设备对象,以便将绑定到目标设备的I / O请求首先路由到调用方。
*/
NTSTATUS IoAttachDevice(
PDEVICE_OBJECT SourceDevice,
PUNICODE_STRING TargetDevice,
PDEVICE_OBJECT *AttachedDevice
);
SourceDevice
[in]指向调用者创建的设备对象的指针。
TargetDevice
[in]指向缓冲区的指针,该缓冲区包含要附加指定SourceDevice的设备对象的名称。
AttachedDevice
[out]指向指向呼叫者的存储的指针。 返回时,如果附件成功,则包含指向目标设备对象的指针。
注意:必须是有名字的设备才能用这个API
IoAttachDeviceToDeviceStack——绑定到真实的设备上(设备对象)
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
PDEVICE_OBJECT SourceDevice,
PDEVICE_OBJECT TargetDevice
);
IoAttachDeviceToDeviceStackSafe——绑定到真实的设备上(更安全)
NTSTATUS IoAttachDeviceToDeviceStackSafe(
PDEVICE_OBJECT SourceDevice,
PDEVICE_OBJECT TargetDevice,
PDEVICE_OBJECT *AttachedToDeviceObject
);
IoCreateDevice——创建设备
//创建一个驱动程序使用的设备对象
NTSTATUS IoCreateDevice(
PDRIVER_OBJECT DriverObject,
ULONG DeviceExtensionSize,
PUNICODE_STRING DeviceName,
DEVICE_TYPE DeviceType,
ULONG DeviceCharacteristics,
BOOLEAN Exclusive,
PDEVICE_OBJECT *DeviceObject
);
IoGetDeviceObjectPointer——从名称获取设备对象
NTSTATUS IoGetDeviceObjectPointer(
PUNICODE_STRING ObjectName,
ACCESS_MASK DesiredAccess,
PFILE_OBJECT *FileObject,
PDEVICE_OBJECT *DeviceObject
);
IoDetachDevice——将绑定的设备解除绑定
void IoDetachDevice(
PDEVICE_OBJECT TargetDevice
);
IoDeleteDevice——删除掉生成的设备,回收内存
void IoDeleteDevice(
PDEVICE_OBJECT DeviceObject
);
2、实现代码
#include <ntddk.h>
#include <ntstrsafe.h>
#define CCP_MAX_COM_ID 32 ///> 假设计算机上最大的串口数
#define DELAY_ONE_MICROSECOND (-10) ///> 微妙
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND * 1000) ///> 毫秒
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND * 1000) ///> 秒
static PDEVICE_OBJECT gs_fltObj[CCP_MAX_COM_ID] = { 0 }; ///> 保存所有过滤设备指针
static PDEVICE_OBJECT gs_nextObj[CCP_MAX_COM_ID] = { 0 }; ///> 保存所有真实设备指针
/*
生成设备,然后绑定
*/
NTSTATUS ccpAttachDevice(
PDRIVER_OBJECT driver,
PDEVICE_OBJECT oldobj,
PDEVICE_OBJECT *fltobj,
PDEVICE_OBJECT *next
)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT tobdev = NULL;
///> 生成设备,然后绑定
status = IoCreateDevice(driver,
0,
NULL,
oldobj->DeviceType,
0,
FALSE,
fltobj);
if (STATUS_SUCCESS != status)
{
return status;
}
///> 拷贝重要标识位
if (oldobj->Flags & DO_BUFFERED_IO)
{
(*fltobj)->Flags |= DO_BUFFERED_IO;
}
if (oldobj->Flags & DO_DIRECT_IO)
{
(*fltobj)->Flags |= DO_DIRECT_IO;
}
if (oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
{
(*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
}
(*fltobj)->Flags |= DO_POWER_PAGABLE;
///> 将一个设备绑定到另一个设备上
status = IoAttachDeviceToDeviceStackSafe(*fltobj,
oldobj,
&tobdev);
if (STATUS_SUCCESS != status)
{
///> 绑定失败了,则删除设备
IoDeleteDevice(*fltobj);
*fltobj = NULL;
return status;
}
*next = tobdev;
///> 设置设备已经启动
(*fltobj)->Flags = (*fltobj)->Flags & (~DO_DEVICE_INITIALIZING);
return STATUS_SUCCESS;
}
/*
打开一个端口设备
*/
PDEVICE_OBJECT ccpOpenCom(
ULONG id,
NTSTATUS *status
)
{
///> 输入的为串口id,需要转成字符串形式
UNICODE_STRING name_str = { 0 };
static WCHAR name[32] = { 0 };
PFILE_OBJECT fileObj = NULL;
PDEVICE_OBJECT devObj = NULL;
///> 根据id转成字符串
memset(name, 0, sizeof(WCHAR) * 32);
RtlStringCchPrintfW(
name,
32,
L"\\Device\\Serial%d",
id
);
RtlInitUnicodeString(&name_str, name);
///> 根据名称获取设备对象
*status = IoGetDeviceObjectPointer(
&name_str,
FILE_ALL_ACCESS,
&fileObj,
&devObj
);
///> 成功了,需要对文件对象进行解引用,否则会内存泄漏
if (STATUS_SUCCESS == *status)
{
ObDereferenceObject(fileObj);
}
///> 返回设备对象
return devObj;
}
/*
绑定所有的串口
*/
void ccpAttachAllCom(PDRIVER_OBJECT driver)
{
ULONG i = 0;
PDEVICE_OBJECT com_obj;
NTSTATUS status = STATUS_SUCCESS;
for (i = 0; i < CCP_MAX_COM_ID; i++)
{
///> 获取object引用
com_obj = ccpOpenCom(i, &status);
if (NULL == com_obj)
{
continue;
}
///> 绑定
ccpAttachDevice(driver, com_obj, &gs_fltObj[i], &gs_nextObj[i]);
}
}
/*
完整的分发函数
处理所有串口的写请求,所有从串口输出的数据打印
*/
NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
///> 首先要知道发送给了哪个设备。设备最多一共有CCP_MAX_COM_ID个,是前面的代码保存好的,都在gs_fltObj
for (ULONG i = 0; i < CCP_MAX_COM_ID; ++i)
{
if (gs_fltObj[i] == device)
{
switch (irpsp->MajorFunction)
{
case IRP_MJ_POWER:
{
///> 所有电源操作,全部直接放过
///> 直接发送,然后返回说已经被处理了
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(gs_nextObj[i], irp);
break;
}
case IRP_MJ_WRITE:
{
///> 此外我们只过滤写请求。写请求,获得缓冲区及其长度,然后打印。
///> 如果是写,先获得长度
ULONG len = irpsp->Parameters.Write.Length;
///> 获取缓冲区
PUCHAR buf = NULL;
if (irp->MdlAddress)
{
buf =
(PUCHAR)MmGetSystemAddressForMdlSafe(
irp->MdlAddress, NormalPagePriority);
}
else
{
buf = (PUCHAR)irp->UserBuffer;
}
if (NULL == buf)
{
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
}
for (ULONG j = 0; j < len; j++)
{
DbgPrint("comcap: Send Data: %2x\r\n", buf[j]);
}
}
default:
{
///> 这些请求直接下发执行即可,我们并不禁止或者改变他
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(gs_nextObj[i], irp);
}
}
}
}
///> 如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
void ccpUnload(PDRIVER_OBJECT drviver)
{
LARGE_INTEGER interval;
///> 首先解除绑定
for (ULONG i = 0; i < CCP_MAX_COM_ID; i++)
{
if (gs_nextObj[i])
{
///> 将绑定的设备解除绑定
IoDetachDevice(gs_nextObj[i]);
}
}
///> 睡眠5秒。等待所有IRP处理结束,单位是100纳秒
interval.QuadPart = (5 * DELAY_ONE_SECOND);
KeDelayExecutionThread(KernelMode, FALSE, &interval);
///> 删除这些设备
for (ULONG i = 0; i < CCP_MAX_COM_ID; i++)
{
if (gs_fltObj[i])
{
///> 删除掉生成的设备,回收内存
IoDeleteDevice(gs_fltObj[i]);
gs_fltObj[i] = NULL;
}
}
}
void DriverUnload(PDRIVER_OBJECT pdriver)
{
DbgPrint("Unload\n");
ccpUnload(pdriver);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING reg_path)
{
NTSTATUS status = STATUS_SUCCESS;
///> 所有的分发函数都设置成一样的
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pdriver->MajorFunction[i] = ccpDispatch;
}
///> 支持动态卸载
pdriver->DriverUnload = DriverUnload;
///> 绑定所有的串口
ccpAttachAllCom(pdriver);
return status;
}
3、笔记
IRP请求中的三个缓冲区
irp->AssociatedIrp.SystemBuffer:一般用于比较简单且不追求效率的情况下的解决方案,把应用层中内存空间中的缓冲数据拷贝到内核空间。
irp->UserBuffer:是最追求效率的解决方案。应用层的缓冲区地址直接放入UserBuffer中,在内核空间中访问,需要保证当前进程和发送请求进程一致。
irp->MdlAddress:是一个MDL(内存描述符链)指针,可以读出一个内核空间的虚拟地址,这样可以实现将应用层的地址空间映射到内核空间