Windows驱动开发学习记录-客户端与驱动交换数据之挂起IRP后客户端无法退出问题
- 景背
最近学习TDI过滤,做一个简单的网络连接控制。当前的需求时驱动层进行了过滤,想把相应的过滤做成事件通知给应用层并显示。
- 思路
没有特别好的方法,本人采用的思路是驱动创建一个设备进行通讯,客户启动后创建一个线程,该线程调用 DeviceIoControl,驱动层将该操作的IRP挂起,然后有数据时再将数据复制到IRP对应的Irp->AssociatedIrp.SystemBuffer中并完成操作。
- 驱动关键的大致代码
相关定义
#define IOCTL_CMD_REQUIRE_LOG CTL_CODE(FILE_DEVICE_TDI_FW, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CMD_CLEAR_PENDDING CTL_CODE(FILE_DEVICE_TDI_FW, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_TRANSFER_TYPE(ioctl) ((ioctl) & 3)
typedef struct _RULE_COMMUNICATION_INFO
{
ULONG nStructSize;
GUID guid;
WCHAR szRuleInfoName[128];
WCHAR szRuleProcessPath[256];
ULONG nAction;
ULONG nProtocol;
ULONG nDirection;
ULONG nRequestType;
ULONG ulAddressFrom;
ULONG ulMaskFrom;
USHORT uPortFrom;
USHORT uPort2From;
ULONG ulAddressTo;
ULONG ulMaskTo;
USHORT uPortTo;
USHORT uPort2To;
ULONGnLog;
ULONG nResult;
BOOLEAN bEnable;
}RULE_COMMUNICATION_INFO, * PRULE_COMMUNICATION_INFO;
typedef struct _LOG_IRP_LIST
{
LIST_ENTRY listEntry;
PIRP pIrp;
HANDLE hPid;
}LOG_IRP_LIST, *PLOG_IRP_LIST;
PLOG_IRP_LIST g_pLogIrpList = NULL;
KSPIN_LOCK g_LogIrpListGuard;
挂起IRP
NTSTATUS ProcessUserRequest(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
UNREFERENCED_PARAMETER(pDeviceObject);
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
PIO_STACK_LOCATION pIosp = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulControlCode = pIosp->Parameters.DeviceIoControl.IoControlCode;
ULONG ulInputBufferLength = pIosp->Parameters.DeviceIoControl.InputBufferLength;
ULONG ulOutputBufferLength = pIosp->Parameters.DeviceIoControl.OutputBufferLength;
PLOG_IRP_LIST pLogList = NULL;
KIRQL irql = PASSIVE_LEVEL;
switch (ulControlCode)
{
case IOCTL_CMD_REQUIRE_LOG:
if (IOCTL_TRANSFER_TYPE(ulControlCode) == METHOD_NEITHER)
{
ntStatus = STATUS_INFO_LENGTH_MISMATCH;
break;
}
if ((ulInputBufferLength < sizeof(RULE_COMMUNICATION_INFO)) ||
((ulOutputBufferLength < sizeof(RULE_COMMUNICATION_INFO))))
{
ntStatus = STATUS_INFO_LENGTH_MISMATCH;
break;
}
if (g_pLogIrpList == NULL)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
pLogList = (PLOG_IRP_LIST)ExAllocatePoolWithTag(
NonPagedPool, sizeof(LOG_IRP_LIST), MmTagTypeLogIRPList);
if (!pLogList)
{
KDPRINTEX("【TDIFilter】", "Allocate Log List Failed!\r\n");
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
pLogList->pIrp = pIrp;
pLogList->hPid = PsGetCurrentProcessId();
🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟这里是关键🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟🠟
IoSetCancelRoutine(pIrp, LogIRPCancelRoutine);
☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝
KDPRINTEX("【TDIFilter】", "Pendding Log Irp \r\n");
KeAcquireSpinLock(&g_LogIrpListGuard, &irql);
InsertTailList(&g_pLogIrpList->listEntry, &pLogList->listEntry);
KeReleaseSpinLock(&g_LogIrpListGuard, irql);
IoMarkIrpPending(pIrp);
ntStatus = STATUS_PENDING;
pIrp->IoStatus.Status = STATUS_PENDING;
break;
default:
ntStatus = STATUS_NOT_SUPPORTED;
break;
}
return ntStatus;
}
相关任务完成为:
VOID LogToClient(PFILTER_REQUEST pRequest, RULE_ACTION_TYPE nAction)
{
if (pRequest)
{
ULONG ulBufferSize = sizeof(RULE_COMMUNICATION_INFO);
PRULE_COMMUNICATION_INFO pRuleCom =
(PRULE_COMMUNICATION_INFO)ExAllocatePoolWithTag(
NonPagedPool, ulBufferSize, MmTagTypeLogInfo);
KIRQL irql = PASSIVE_LEVEL;
if (pRuleCom)
{
RtlZeroMemory(pRuleCom, ulBufferSize);
......
while (!IsListEmpty(pListEntry))
{
PLIST_ENTRY pTemp = pListEntry->Flink;
RemoveHeadList(pListEntry);
PLOG_IRP_LIST pLogIrp = CONTAINING_RECORD(pTemp, LOG_IRP_LIST, listEntry);
__try
{
if (pLogIrp)
{
if (pLogIrp->pIrp)
{
RtlCopyMemory(pLogIrp->pIrp->AssociatedIrp.SystemBuffer, pRuleCom, ulBufferSize);
pLogIrp->pIrp->IoStatus.Information = ulBufferSize;
pLogIrp->pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pLogIrp->pIrp, IO_NO_INCREMENT);
KDPRINTEX("【TDIFilter】", "添加日志\r\n");
}
ExFreePoolWithTag(pLogIrp, MmTagTypeLogIRPList);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
KDPRINTEX("【TDIFilter】", "IRP Exception \r\n");
}
}
if (pRuleCom)
{
ExFreePoolWithTag(pRuleCom, MmTagTypeLogInfo);
}
KeReleaseSpinLock(&g_LogIrpListGuard, irql);
}
else
{
KDPRINTEX("【TDIFilter】", "ExAllocatePoolWithTag failed \r\n");
}
}
}
- 实际遇到的问题
第二段代码中的关键部分在一开始并没有,也就是说开始没有设置IRP的取消例程,这样在客户端关闭时IRP还是处于挂起状态,导致客户端退不出后。
后来加上取消例程就就没有问题了 ,取消函数也简单,代码如下:
VOID LogIRPCancelRoutine(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
UNREFERENCED_PARAMETER(pDeviceObject);
IoReleaseCancelSpinLock(pIrp->CancelIrql);
pIrp->IoStatus.Status = STATUS_CANCELLED;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
写取消函数也遇到了点问题,详见: