wdm驱动程序采用分层的结构模型,最下层的设备对象称为物理设备对象(Physical Device Object)简称为PDO
,在中间层有一个设备对象称为功能设备对象(Functional Device Object)简称FDO
。
通常来说一个物理设备有两个设备对象:PDO是系统的代表,FDO是设备驱动程序中的代表。
在PDO
中创建一个FDO
,并进行相关的通讯。
可能上面的概念比较难于理解,我们可以通过一个简单的例子来理解上面的意思。
新建一个空的wdm
工程,driver.cpp
有如下的代码:
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
DbgPrint("DriverEntry\r\n");
pDriverObject->DriverUnload = DriverUnload;//注册驱动卸载函数
//注册默认派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = DefDispatchRoutine;
//注册设备控制派遣函数
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatchRoutine;
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称的字符串
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDC");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲I/O设备,关于缓冲I/O设备将会在下一篇博文中讲!
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\MyDC_link");
pDevExt->SymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
return STATUS_SUCCESS;
}
PDRIVER_OBJECT
结构体在这里又被用到了,而且用的还挺多,这里我们主要关心两个,一在注册派遣函数时,对于一些当前不需要的函数,我们采取默认的方式,默认为同一个函数,二对IRP_MJ_DEVICE_CONTROL
是我们需要处理 的派遣函数,他是用来跟上层程序进行通讯的。
接下来我们通过IoCreateDevice
创建了设备对象,并把这个设备对象关联为名MyDC_link
,这样我们上层程序就能够找到他。
既然我们处理了IRP_MJ_DEVICE_CONTROL
,看看我们做了什么事情。
extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("Enter IoctlDispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
//得到I/O堆栈的当前这一层,也就是IO_STACK_LOCATION结构的指针
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength;//得到输入缓冲区的大小
ULONG out_size = stack->Parameters.DeviceIoControl.OutputBufferLength;//得到输出缓冲区的大小
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;//得到控制码
PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到缓冲区指针
const char src[50] = "hi, from wdm";
switch (code)
{ // process request
case IOCTL1:
DbgPrint("[GO2CODING] Get ioctl code 1\r\n");
//显示输入缓冲区数据
DbgPrint("[GO2CODING] input:%s",(PCSTR)buffer);
//将输出缓冲区填充字符
memcpy(buffer, src, strlen(src) + 1);
out_size = strlen(src) + 1;
break;
default:
status = STATUS_INVALID_VARIANT;
//如果是没有处理的IRP,则返回STATUS_INVALID_VARIANT,这意味着用户模式的I/O函数失败,但并不会设置GetLastError
}
// 完成IRP
pIrp->IoStatus.Status = status;//设置IRP完成状态,会设置用户模式下的GetLastError
pIrp->IoStatus.Information = out_size;//设置操作的字节
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP,不增加优先级
return status;
}
代码很简单,也就是打印出上层程序输入来的字符串,并把输出区都填充为0xAA
,发给上层程序。
在上层程序中,如何进行通讯呢?
在windows 中采用DeviceIoControl
向某个已经打开的设备的驱动程序的派遣函数发送IRP:IRP_MJ_DEVICE_CONTROL
。
DeviceIoControl
的原型如下:
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_opt_ LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_opt_ LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
第一个是打开的设备,第二个是控制码,这里的控制码必须按照这种格式进行#define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x001,METHOD_BUFFERED,FILE_ANY_ACCESS)
,接下来的是输入输出的缓存区内存和字节数。
我们的上层程序,可以这样来写:
int main()
{
HANDLE handle = CreateFileA("\\\\.\\MyDC_link", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
std::cout << "open device fail" << std::endl;
return 0;
}
unsigned char buffer[50] = { 0 };
unsigned char buffer2[50] = { 0 };
DWORD len;
sprintf((char*)buffer, "hello, driver\r\n");
if (DeviceIoControl(handle, IOCTL1, buffer, strlen((char*)buffer), buffer2, 49, &len, NULL)) {
printf("outbuffer: %s\n", buffer2);
}
getchar();
CloseHandle(handle);
}
驱动程序接收到字符串,log
上有显示:
上层程序打印出来之驱动的消息:
在这个例子中我们创建了一个设备对象,使用DeviceIoControl
来发送IRP
,实现了上层程序和驱动之间的通讯。
如果需要 demo的源代码,可以私信我。