关于驱动读写异步超时的处理,网络上的资料相对稀少,正好最近在工作上遇到了这个问题,所以就研究了一下,发现还是有些门道的。如果完全按照应用层读写超时的处理逻辑来处理驱动层的话就会出现蓝屏等问题
只要涉及到读写超时,那么我们第一印象肯定会想到事件和事件等待相关操作与函数的调用,那么我们来看一下驱动的几个文件操作函数声明:
首先是打开操作
NTSTATUS ZwCreateFile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_opt_ PVOID EaBuffer,
_In_ ULONG EaLength
);
根据应用层的经验,如果要实现超时读写,我们需要以异步的方式打开文件,而驱动默认即是异步打开操作,如果你需要同步打开,需要设置下面的值(两个需要同时设置)
DesiredAccess:SYNCHRONIZE
CreateOptions:FILE_SYNCHRONOUS_IO_NONALERT
然后是读写函数,两个类似
NTSTATUS ZwReadFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_ PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
NTSTATUS ZwWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
这里根据应用层的经验,我们会找关于事件(Event)的参数,根据文档声明,正好第二个参数涉及到了事件,查阅文档后确定该参数可以用于异步超时等待。这个时候我们需要创建一个事件,关于驱动中创建事件的函数我们首先会想到 KeInitializeEvent ,但是此处需要的是 HANDLE 类型,所以我们需要使用下面的方法来创建:
ZwCreateEvent(&g_hEvent, GENERIC_ALL, NULL, SynchronizationEvent, FALSE);
然后我们自然想到以Zw开头来搜索相关的等待函数
status = ZwWaitForSingleObject(g_hEvent, FALSE, &time);
这个时候如果status返回的是 STATUS_TIMEOUT ,那么就代表文件读写超时,这时候我们就关闭事件句柄和文件句柄然后返回。如果你真的这么做了,那么等数据真正的返回的时候,就会出现蓝屏问题。
经过分析发现是因为我们在使用 ZwClose 函数关闭句柄后,windows并没有停止对文件的操作,由于是是异步的,所以后台仍然在等待数据的返回,如果这时候我们销毁了资源,但是数据在超时后返回,那么就会出现 0xc0000005 这种内存访问错误蓝屏。那么我们理所当然想到是否有一个函数用来取消文件IO操作,答案是有的,但是是未文档化函数,函数声明如下:
typedef
NTSTATUS
(NTAPI *MyZwCancelIoFile)(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock);
这个函数即可用来取消文件IO,当我们读写超时后,我们先要取消文件后台IO操作,然后再关闭相关句柄。我们可以这样获得该函数地址:
UNICODE_STRING funcname = RTL_CONSTANT_STRING(L"ZwCancelIoFile");
ZwCancelIoFile = (MyZwCancelIoFile)MmGetSystemRoutineAddress(&funcname);
if (ZwCancelIoFile == NULL)
{
return FALSE;
}
完毕